deathindicator: add permabones (#1829)
* Add api support for faking ground items and add permabones * Remove unused method * fixed loading/keeping loaded, fixed calendar thingy, probably forgot something * Actually commit the calendar tsuff
This commit is contained in:
@@ -12,6 +12,11 @@ public interface ItemDefinition
|
|||||||
*/
|
*/
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the items name.
|
||||||
|
*/
|
||||||
|
void setName(String name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the items ID.
|
* Gets the items ID.
|
||||||
*
|
*
|
||||||
@@ -86,6 +91,7 @@ public interface ItemDefinition
|
|||||||
* Returns whether or not the item can be sold on the grand exchange.
|
* Returns whether or not the item can be sold on the grand exchange.
|
||||||
*/
|
*/
|
||||||
boolean isTradeable();
|
boolean isTradeable();
|
||||||
|
void setTradeable(boolean yes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an array of possible right-click menu actions the item
|
* Gets an array of possible right-click menu actions the item
|
||||||
@@ -114,4 +120,11 @@ public interface ItemDefinition
|
|||||||
* default value.
|
* default value.
|
||||||
*/
|
*/
|
||||||
void resetShiftClickActionIndex();
|
void resetShiftClickActionIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With this you can make certain (ground) items look like different ones.
|
||||||
|
*
|
||||||
|
* @param id The itemID of the item with desired model
|
||||||
|
*/
|
||||||
|
void setModelOverride(int id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.api;
|
package net.runelite.api;
|
||||||
|
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the entire 3D scene
|
* Represents the entire 3D scene
|
||||||
*/
|
*/
|
||||||
@@ -36,6 +38,16 @@ public interface Scene
|
|||||||
*/
|
*/
|
||||||
Tile[][][] getTiles();
|
Tile[][][] getTiles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an item to the scene
|
||||||
|
*/
|
||||||
|
void addItem(int id, int quantity, WorldPoint point);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an item from the scene
|
||||||
|
*/
|
||||||
|
void removeItem(int id, int quantity, WorldPoint point);
|
||||||
|
|
||||||
int getDrawDistance();
|
int getDrawDistance();
|
||||||
void setDrawDistance(int drawDistance);
|
void setDrawDistance(int drawDistance);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.api.events;
|
package net.runelite.api.events;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.runelite.api.MenuEntry;
|
import net.runelite.api.MenuEntry;
|
||||||
@@ -33,7 +34,7 @@ import lombok.Data;
|
|||||||
* An event where a menu has been opened.
|
* An event where a menu has been opened.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class MenuOpened implements Event
|
public class MenuOpened implements Event, Iterable<MenuEntry>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* This should be set to true if anything about the menu
|
* This should be set to true if anything about the menu
|
||||||
@@ -70,4 +71,25 @@ public class MenuOpened implements Event
|
|||||||
{
|
{
|
||||||
this.modified = true;
|
this.modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<MenuEntry> iterator()
|
||||||
|
{
|
||||||
|
return new Iterator<MenuEntry>()
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
return index < menuEntries.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MenuEntry next()
|
||||||
|
{
|
||||||
|
return menuEntries[index++];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package net.runelite.client.plugins.deathindicator;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.runelite.api.Scene;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import static net.runelite.client.plugins.deathindicator.DeathIndicatorPlugin.HIJACKED_ITEMID;
|
||||||
|
import net.runelite.client.util.ColorUtil;
|
||||||
|
import net.runelite.client.util.MiscUtils;
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
class Bone
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
private WorldPoint loc;
|
||||||
|
private Instant time;
|
||||||
|
|
||||||
|
void addToScene(Scene scene)
|
||||||
|
{
|
||||||
|
scene.addItem(HIJACKED_ITEMID, 1, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFromScene(Scene scene)
|
||||||
|
{
|
||||||
|
scene.removeItem(HIJACKED_ITEMID, 1, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName()
|
||||||
|
{
|
||||||
|
return ColorUtil.colorStartTag(0xff9040) + "Bones (" + name + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
String getExamine()
|
||||||
|
{
|
||||||
|
return name + " died here " + MiscUtils.formatTimeAgo(Duration.between(time, Instant.now()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package net.runelite.client.plugins.deathindicator;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.Scene;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import static net.runelite.http.api.RuneLiteAPI.GSON;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Bones
|
||||||
|
{
|
||||||
|
private static final String CONFIG_GROUP = "deathIndicator";
|
||||||
|
private static final String BONES_PREFIX = "bones_";
|
||||||
|
|
||||||
|
private ImmutableMap<Integer, Map<WorldPoint, List<Bone>>> map;
|
||||||
|
private boolean changed = false;
|
||||||
|
|
||||||
|
void init(Client client, ConfigManager configManager)
|
||||||
|
{
|
||||||
|
// Clone is important here as the normal array changes
|
||||||
|
int[] regions = client.getMapRegions().clone();
|
||||||
|
Bone[][] bones = getBones(configManager, regions);
|
||||||
|
if (log.isDebugEnabled())
|
||||||
|
{
|
||||||
|
log.debug("Regions are now {}", Arrays.toString(regions));
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
for (Bone[] ar : bones)
|
||||||
|
{
|
||||||
|
n += ar.length;
|
||||||
|
}
|
||||||
|
log.debug("Loaded {} Bones", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
initMap(regions, bones);
|
||||||
|
|
||||||
|
showBones(client.getScene());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bone[][] getBones(ConfigManager configManager, int[] regions)
|
||||||
|
{
|
||||||
|
Bone[][] bones = new Bone[regions.length][];
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.length; i++)
|
||||||
|
{
|
||||||
|
int region = regions[i];
|
||||||
|
bones[i] = getBones(configManager, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bone[] getBones(ConfigManager configManager, int regionId)
|
||||||
|
{
|
||||||
|
String json = configManager.getConfiguration(CONFIG_GROUP, BONES_PREFIX + regionId);
|
||||||
|
if (json == null)
|
||||||
|
{
|
||||||
|
return new Bone[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return GSON.fromJson(json, Bone[].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void initMap(int[] regions, Bone[][] bones)
|
||||||
|
{
|
||||||
|
ImmutableMap.Builder<Integer, Map<WorldPoint, List<Bone>>> builder = ImmutableMap.builder();
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.length; i++)
|
||||||
|
{
|
||||||
|
int region = regions[i];
|
||||||
|
Bone[] boneA = bones[i];
|
||||||
|
if (boneA.length == 0)
|
||||||
|
{
|
||||||
|
builder.put(region, Collections.EMPTY_MAP);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<WorldPoint, List<Bone>> map = new HashMap(boneA.length);
|
||||||
|
for (Bone b : boneA)
|
||||||
|
{
|
||||||
|
List<Bone> list = map.computeIfAbsent(b.getLoc(), wp -> new ArrayList<>());
|
||||||
|
list.add(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.put(region, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBones(Scene scene)
|
||||||
|
{
|
||||||
|
this.forEach(bone -> bone.addToScene(scene));
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(ConfigManager configManager)
|
||||||
|
{
|
||||||
|
if (this.map == null || !changed)
|
||||||
|
{
|
||||||
|
this.changed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, Map<WorldPoint, List<Bone>>> entry : this.map.entrySet())
|
||||||
|
{
|
||||||
|
final String key = BONES_PREFIX + entry.getKey();
|
||||||
|
final Map<WorldPoint, List<Bone>> map = entry.getValue();
|
||||||
|
if (map.size() == 0)
|
||||||
|
{
|
||||||
|
configManager.unsetConfiguration(CONFIG_GROUP, key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Bone> list = new ArrayList<>(map.values().size());
|
||||||
|
for (List<Bone> lb : map.values())
|
||||||
|
{
|
||||||
|
list.addAll(lb);
|
||||||
|
}
|
||||||
|
|
||||||
|
String val = GSON.toJson(list.toArray(new Bone[0]));
|
||||||
|
configManager.setConfiguration(CONFIG_GROUP, key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean add(Bone bone)
|
||||||
|
{
|
||||||
|
if (this.map == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changed = true;
|
||||||
|
final int region = bone.getLoc().getRegionID();
|
||||||
|
final Map<WorldPoint, List<Bone>> map = this.map.get(region);
|
||||||
|
final List<Bone> list = map.computeIfAbsent(bone.getLoc(), wp -> new ArrayList<>());
|
||||||
|
list.add(bone);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(Bone bone)
|
||||||
|
{
|
||||||
|
this.changed = true;
|
||||||
|
final int region = bone.getLoc().getRegionID();
|
||||||
|
final Map<WorldPoint, List<Bone>> map = this.map.get(region);
|
||||||
|
final List<Bone> list = map.get(bone.getLoc());
|
||||||
|
list.remove(bone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(Scene scene)
|
||||||
|
{
|
||||||
|
if (map == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.forEach(bone -> bone.removeFromScene(scene));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bone get(WorldPoint point, int i)
|
||||||
|
{
|
||||||
|
return get(point).get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Bone> get(WorldPoint point)
|
||||||
|
{
|
||||||
|
final int reg = point.getRegionID();
|
||||||
|
final Map<WorldPoint, List<Bone>> map = this.map.get(reg);
|
||||||
|
if (map == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return map.get(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forEach(Consumer<Bone> consumer)
|
||||||
|
{
|
||||||
|
for (Map<WorldPoint, List<Bone>> map : this.map.values())
|
||||||
|
{
|
||||||
|
for (Map.Entry<WorldPoint, List<Bone>> entry : map.entrySet())
|
||||||
|
{
|
||||||
|
for (Bone bone : entry.getValue())
|
||||||
|
{
|
||||||
|
consumer.accept(bone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,4 +152,14 @@ public interface DeathIndicatorConfig extends Config
|
|||||||
description = ""
|
description = ""
|
||||||
)
|
)
|
||||||
void timeOfDeath(Instant timeOfDeath);
|
void timeOfDeath(Instant timeOfDeath);
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "permaBones",
|
||||||
|
name = "Permanent bones",
|
||||||
|
description = "Show right clickable bones with the name of who died permanently, after seeing someone die"
|
||||||
|
)
|
||||||
|
default boolean permaBones()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,18 +30,32 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.ChatMessageType;
|
||||||
import net.runelite.api.Client;
|
import net.runelite.api.Client;
|
||||||
import net.runelite.api.GameState;
|
import net.runelite.api.GameState;
|
||||||
|
import net.runelite.api.ItemDefinition;
|
||||||
import net.runelite.api.ItemID;
|
import net.runelite.api.ItemID;
|
||||||
|
import net.runelite.api.MenuEntry;
|
||||||
|
import net.runelite.api.MenuOpcode;
|
||||||
|
import net.runelite.api.Player;
|
||||||
import net.runelite.api.coords.WorldPoint;
|
import net.runelite.api.coords.WorldPoint;
|
||||||
import net.runelite.api.events.ConfigChanged;
|
import net.runelite.api.events.ConfigChanged;
|
||||||
import net.runelite.api.events.GameStateChanged;
|
import net.runelite.api.events.GameStateChanged;
|
||||||
import net.runelite.api.events.GameTick;
|
import net.runelite.api.events.GameTick;
|
||||||
|
import net.runelite.api.events.ItemDespawned;
|
||||||
import net.runelite.api.events.LocalPlayerDeath;
|
import net.runelite.api.events.LocalPlayerDeath;
|
||||||
|
import net.runelite.api.events.MenuEntryAdded;
|
||||||
|
import net.runelite.api.events.MenuOpened;
|
||||||
|
import net.runelite.api.events.MenuOptionClicked;
|
||||||
|
import net.runelite.api.events.PlayerDeath;
|
||||||
|
import net.runelite.api.events.PostItemDefinition;
|
||||||
|
import net.runelite.api.util.Text;
|
||||||
|
import net.runelite.client.callback.ClientThread;
|
||||||
import net.runelite.client.config.ConfigManager;
|
import net.runelite.client.config.ConfigManager;
|
||||||
import net.runelite.client.eventbus.EventBus;
|
import net.runelite.client.eventbus.EventBus;
|
||||||
import net.runelite.client.game.ItemManager;
|
import net.runelite.client.game.ItemManager;
|
||||||
@@ -61,6 +75,10 @@ import net.runelite.client.util.ImageUtil;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class DeathIndicatorPlugin extends Plugin
|
public class DeathIndicatorPlugin extends Plugin
|
||||||
{
|
{
|
||||||
|
private static final Object BONES = new Object();
|
||||||
|
// A random number, that jagex probably won't actually use in the near future
|
||||||
|
static final int HIJACKED_ITEMID = 0x69696969;
|
||||||
|
|
||||||
private static final Set<Integer> RESPAWN_REGIONS = ImmutableSet.of(
|
private static final Set<Integer> RESPAWN_REGIONS = ImmutableSet.of(
|
||||||
12850, // Lumbridge
|
12850, // Lumbridge
|
||||||
11828, // Falador
|
11828, // Falador
|
||||||
@@ -88,6 +106,14 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
@Inject
|
@Inject
|
||||||
private EventBus eventBus;
|
private EventBus eventBus;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ConfigManager configManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ClientThread clientThread;
|
||||||
|
|
||||||
|
private final Bones bones = new Bones();
|
||||||
|
|
||||||
private BufferedImage mapArrow;
|
private BufferedImage mapArrow;
|
||||||
|
|
||||||
private Timer deathTimer;
|
private Timer deathTimer;
|
||||||
@@ -95,7 +121,7 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
private WorldPoint lastDeath;
|
private WorldPoint lastDeath;
|
||||||
private Instant lastDeathTime;
|
private Instant lastDeathTime;
|
||||||
private int lastDeathWorld;
|
private int lastDeathWorld;
|
||||||
|
private int despawnIdx = 0;
|
||||||
@Provides
|
@Provides
|
||||||
DeathIndicatorConfig deathIndicatorConfig(ConfigManager configManager)
|
DeathIndicatorConfig deathIndicatorConfig(ConfigManager configManager)
|
||||||
{
|
{
|
||||||
@@ -129,12 +155,18 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||||
worldMapPointManager.add(new DeathWorldMapPoint(new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane()), this));
|
worldMapPointManager.add(new DeathWorldMapPoint(new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane()), this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.permaBones() && client.getGameState() == GameState.LOGGED_IN)
|
||||||
|
{
|
||||||
|
clientThread.invokeLater(this::initBones);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutDown()
|
protected void shutDown()
|
||||||
{
|
{
|
||||||
eventBus.unregister(this);
|
eventBus.unregister(this);
|
||||||
|
eventBus.unregister(BONES);
|
||||||
|
|
||||||
if (client.hasHintArrow())
|
if (client.hasHintArrow())
|
||||||
{
|
{
|
||||||
@@ -148,6 +180,24 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||||
|
|
||||||
|
clientThread.invokeLater(this::clearBones);
|
||||||
|
saveBones();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initBones()
|
||||||
|
{
|
||||||
|
bones.init(client, configManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveBones()
|
||||||
|
{
|
||||||
|
bones.save(configManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearBones()
|
||||||
|
{
|
||||||
|
bones.clear(client.getScene());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSubscriptions()
|
private void addSubscriptions()
|
||||||
@@ -156,6 +206,100 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
eventBus.subscribe(LocalPlayerDeath.class, this, this::onLocalPlayerDeath);
|
eventBus.subscribe(LocalPlayerDeath.class, this, this::onLocalPlayerDeath);
|
||||||
eventBus.subscribe(GameTick.class, this, this::onGameTick);
|
eventBus.subscribe(GameTick.class, this, this::onGameTick);
|
||||||
eventBus.subscribe(GameStateChanged.class, this, this::onGameStateChanged);
|
eventBus.subscribe(GameStateChanged.class, this, this::onGameStateChanged);
|
||||||
|
if (config.permaBones())
|
||||||
|
{
|
||||||
|
addBoneSubs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBoneSubs()
|
||||||
|
{
|
||||||
|
eventBus.subscribe(ItemDespawned.class, BONES, this::onItemDespawn);
|
||||||
|
eventBus.subscribe(PlayerDeath.class, BONES, this::onPlayerDeath);
|
||||||
|
eventBus.subscribe(MenuEntryAdded.class, BONES, this::onMenuEntryAdded);
|
||||||
|
eventBus.subscribe(MenuOptionClicked.class, BONES, this::onMenuOptionClicked);
|
||||||
|
eventBus.subscribe(MenuOpened.class, BONES, this::onMenuOpened);
|
||||||
|
eventBus.subscribe(PostItemDefinition.class, BONES, this::onPostItemDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPostItemDefinition(PostItemDefinition def)
|
||||||
|
{
|
||||||
|
ItemDefinition itemDef = def.getItemDefinition();
|
||||||
|
if (itemDef.getId() == HIJACKED_ITEMID)
|
||||||
|
{
|
||||||
|
itemDef.setModelOverride(ItemID.BONES);
|
||||||
|
itemDef.setName("Bones");
|
||||||
|
// This is so never hide untradeables doesn't not hide it
|
||||||
|
itemDef.setTradeable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPlayerDeath(PlayerDeath death)
|
||||||
|
{
|
||||||
|
Player p = death.getPlayer();
|
||||||
|
Bone b = new Bone();
|
||||||
|
|
||||||
|
b.setName(Text.sanitize(p.getName()));
|
||||||
|
b.setTime(Instant.now());
|
||||||
|
b.setLoc(p.getWorldLocation());
|
||||||
|
|
||||||
|
while (!bones.add(b))
|
||||||
|
{
|
||||||
|
initBones();
|
||||||
|
}
|
||||||
|
|
||||||
|
b.addToScene(client.getScene());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMenuEntryAdded(MenuEntryAdded event)
|
||||||
|
{
|
||||||
|
if (event.getIdentifier() == HIJACKED_ITEMID)
|
||||||
|
{
|
||||||
|
if (event.getOpcode() == MenuOpcode.GROUND_ITEM_THIRD_OPTION.getId())
|
||||||
|
{
|
||||||
|
client.setMenuOptionCount(client.getMenuOptionCount() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMenuOpened(MenuOpened event)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
for (MenuEntry entry : event)
|
||||||
|
{
|
||||||
|
if (entry.getIdentifier() != HIJACKED_ITEMID)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only entries with appropriate identifier here will be examine so that's easy
|
||||||
|
// Add idx to id field, so we can find that back from clicked event
|
||||||
|
entry.setIdentifier(HIJACKED_ITEMID + idx);
|
||||||
|
|
||||||
|
Bone bone = bones.get(
|
||||||
|
WorldPoint.fromScene(client, entry.getParam0(), entry.getParam1(), client.getPlane()),
|
||||||
|
idx++
|
||||||
|
);
|
||||||
|
|
||||||
|
entry.setTarget(bone.getName());
|
||||||
|
event.setModified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMenuOptionClicked(MenuOptionClicked event)
|
||||||
|
{
|
||||||
|
if (event.getIdentifier() >= HIJACKED_ITEMID
|
||||||
|
&& event.getOpcode() == MenuOpcode.EXAMINE_ITEM_GROUND.getId())
|
||||||
|
{
|
||||||
|
Bone b = bones.get(
|
||||||
|
WorldPoint.fromScene(client, event.getParam0(), event.getParam1(), client.getPlane()),
|
||||||
|
event.getIdentifier() - HIJACKED_ITEMID
|
||||||
|
);
|
||||||
|
|
||||||
|
client.addChatMessage(ChatMessageType.ITEM_EXAMINE, "", b.getExamine(), "");
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onLocalPlayerDeath(LocalPlayerDeath death)
|
private void onLocalPlayerDeath(LocalPlayerDeath death)
|
||||||
@@ -238,6 +382,29 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
if (event.getGroup().equals("deathIndicator"))
|
if (event.getGroup().equals("deathIndicator"))
|
||||||
{
|
{
|
||||||
|
if ("permaBones".equals(event.getKey()))
|
||||||
|
{
|
||||||
|
if (config.permaBones())
|
||||||
|
{
|
||||||
|
addBoneSubs();
|
||||||
|
|
||||||
|
if (client.getGameState() == GameState.LOGGED_IN)
|
||||||
|
{
|
||||||
|
clientThread.invokeLater(this::initBones);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eventBus.unregister(BONES);
|
||||||
|
|
||||||
|
if (client.getGameState() == GameState.LOGGED_IN)
|
||||||
|
{
|
||||||
|
clientThread.invokeLater(this::clearBones);
|
||||||
|
saveBones();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!config.showDeathHintArrow() && hasDied())
|
if (!config.showDeathHintArrow() && hasDied())
|
||||||
{
|
{
|
||||||
client.clearHintArrow();
|
client.clearHintArrow();
|
||||||
@@ -267,32 +434,62 @@ public class DeathIndicatorPlugin extends Plugin
|
|||||||
|
|
||||||
private void onGameStateChanged(GameStateChanged event)
|
private void onGameStateChanged(GameStateChanged event)
|
||||||
{
|
{
|
||||||
if (!hasDied())
|
switch (event.getGameState())
|
||||||
{
|
{
|
||||||
return;
|
case LOADING:
|
||||||
}
|
clearBones();
|
||||||
|
saveBones();
|
||||||
if (event.getGameState() == GameState.LOGGED_IN)
|
break;
|
||||||
{
|
case LOGGED_IN:
|
||||||
if (client.getWorld() == config.deathWorld())
|
if (config.permaBones())
|
||||||
{
|
|
||||||
WorldPoint deathPoint = new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane());
|
|
||||||
|
|
||||||
if (config.showDeathHintArrow())
|
|
||||||
{
|
{
|
||||||
client.setHintArrow(deathPoint);
|
initBones();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.showDeathOnWorldMap())
|
if (!hasDied())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.getWorld() == config.deathWorld())
|
||||||
|
{
|
||||||
|
WorldPoint deathPoint = new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane());
|
||||||
|
|
||||||
|
if (config.showDeathHintArrow())
|
||||||
|
{
|
||||||
|
client.setHintArrow(deathPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.showDeathOnWorldMap())
|
||||||
|
{
|
||||||
|
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||||
|
worldMapPointManager.add(new DeathWorldMapPoint(deathPoint, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
||||||
worldMapPointManager.add(new DeathWorldMapPoint(deathPoint, this));
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onItemDespawn(ItemDespawned event)
|
||||||
|
{
|
||||||
|
if (event.getItem().getId() == HIJACKED_ITEMID)
|
||||||
|
{
|
||||||
|
List<Bone> list = bones.get(event.getTile().getWorldLocation());
|
||||||
|
if (list == null)
|
||||||
{
|
{
|
||||||
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
|
return;
|
||||||
}
|
}
|
||||||
|
if (list.size() <= despawnIdx)
|
||||||
|
{
|
||||||
|
despawnIdx = 0;
|
||||||
|
}
|
||||||
|
Bone bone = list.get(despawnIdx++);
|
||||||
|
bone.addToScene(client.getScene());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package net.runelite.client.util;
|
package net.runelite.client.util;
|
||||||
|
|
||||||
import java.awt.Polygon;
|
import java.awt.Polygon;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import net.runelite.api.Client;
|
import net.runelite.api.Client;
|
||||||
import net.runelite.api.Player;
|
import net.runelite.api.Player;
|
||||||
import net.runelite.api.WorldType;
|
import net.runelite.api.WorldType;
|
||||||
@@ -16,6 +18,17 @@ public class MiscUtils
|
|||||||
private static final Polygon abovePoly = new Polygon(abovePointsX, abovePointsY, abovePointsX.length);
|
private static final Polygon abovePoly = new Polygon(abovePointsX, abovePointsY, abovePointsX.length);
|
||||||
private static final Polygon belowPoly = new Polygon(belowPointsX, belowPointsY, belowPointsX.length);
|
private static final Polygon belowPoly = new Polygon(belowPointsX, belowPointsY, belowPointsX.length);
|
||||||
|
|
||||||
|
private static final ChronoUnit[] ORDERED_CHRONOS = new ChronoUnit[]
|
||||||
|
{
|
||||||
|
ChronoUnit.YEARS,
|
||||||
|
ChronoUnit.MONTHS,
|
||||||
|
ChronoUnit.WEEKS,
|
||||||
|
ChronoUnit.DAYS,
|
||||||
|
ChronoUnit.HOURS,
|
||||||
|
ChronoUnit.MINUTES,
|
||||||
|
ChronoUnit.SECONDS
|
||||||
|
};
|
||||||
|
|
||||||
//test replacement so private for now
|
//test replacement so private for now
|
||||||
private static boolean inWildy(WorldPoint point)
|
private static boolean inWildy(WorldPoint point)
|
||||||
{
|
{
|
||||||
@@ -86,4 +99,75 @@ public class MiscUtils
|
|||||||
|
|
||||||
//return getWildernessLevelFrom(client, localPlayer.getWorldLocation()) > 0;
|
//return getWildernessLevelFrom(client, localPlayer.getWorldLocation()) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatTimeAgo(Duration dur)
|
||||||
|
{
|
||||||
|
long dA = 0, dB = 0, rm;
|
||||||
|
ChronoUnit cA = null, cB = null;
|
||||||
|
for (int i = 0; i < ORDERED_CHRONOS.length; i++)
|
||||||
|
{
|
||||||
|
cA = ORDERED_CHRONOS[i];
|
||||||
|
dA = dur.getSeconds() / cA.getDuration().getSeconds();
|
||||||
|
rm = dur.getSeconds() % cA.getDuration().getSeconds();
|
||||||
|
if (dA <= 0)
|
||||||
|
{
|
||||||
|
cA = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 < ORDERED_CHRONOS.length)
|
||||||
|
{
|
||||||
|
cB = ORDERED_CHRONOS[i + 1];
|
||||||
|
dB = rm / cB.getDuration().getSeconds();
|
||||||
|
|
||||||
|
if (dB <= 0)
|
||||||
|
{
|
||||||
|
cB = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cA == null)
|
||||||
|
{
|
||||||
|
return "just now.";
|
||||||
|
}
|
||||||
|
|
||||||
|
String str = formatUnit(cA, dA);
|
||||||
|
|
||||||
|
if (cB != null)
|
||||||
|
{
|
||||||
|
str += " and " + formatUnit(cB, dB);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + " ago.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatUnit(ChronoUnit chrono, long val)
|
||||||
|
{
|
||||||
|
boolean multiple = val != 1;
|
||||||
|
String str;
|
||||||
|
if (multiple)
|
||||||
|
{
|
||||||
|
str = val + " ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str = "a" + (chrono == ChronoUnit.HOURS ? "n " : " ");
|
||||||
|
}
|
||||||
|
str += chrono.name().toLowerCase();
|
||||||
|
if (!multiple)
|
||||||
|
{
|
||||||
|
if (str.charAt(str.length() - 1) == 's')
|
||||||
|
{
|
||||||
|
str = str.substring(0, str.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (str.charAt(str.length() - 1) != 's')
|
||||||
|
{
|
||||||
|
str += "s";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import net.runelite.api.mixins.Replace;
|
|||||||
import net.runelite.api.mixins.Shadow;
|
import net.runelite.api.mixins.Shadow;
|
||||||
import net.runelite.rs.api.RSClient;
|
import net.runelite.rs.api.RSClient;
|
||||||
import net.runelite.rs.api.RSItemDefinition;
|
import net.runelite.rs.api.RSItemDefinition;
|
||||||
|
import net.runelite.rs.api.RSModel;
|
||||||
|
|
||||||
@Mixin(RSItemDefinition.class)
|
@Mixin(RSItemDefinition.class)
|
||||||
public abstract class RSItemDefinitionMixin implements RSItemDefinition
|
public abstract class RSItemDefinitionMixin implements RSItemDefinition
|
||||||
@@ -21,6 +22,16 @@ public abstract class RSItemDefinitionMixin implements RSItemDefinition
|
|||||||
@Inject
|
@Inject
|
||||||
private int shiftClickActionIndex = DEFAULT_CUSTOM_SHIFT_CLICK_INDEX;
|
private int shiftClickActionIndex = DEFAULT_CUSTOM_SHIFT_CLICK_INDEX;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private int modelOverride = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Inject
|
||||||
|
public void setModelOverride(int id)
|
||||||
|
{
|
||||||
|
modelOverride = id;
|
||||||
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
RSItemDefinitionMixin()
|
RSItemDefinitionMixin()
|
||||||
{
|
{
|
||||||
@@ -64,4 +75,18 @@ public abstract class RSItemDefinitionMixin implements RSItemDefinition
|
|||||||
event.setItemDefinition(this);
|
event.setItemDefinition(this);
|
||||||
client.getCallbacks().post(PostItemDefinition.class, event);
|
client.getCallbacks().post(PostItemDefinition.class, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Copy("getModel")
|
||||||
|
public abstract RSModel rs$getModel(int quantity);
|
||||||
|
|
||||||
|
@Replace("getModel")
|
||||||
|
public RSModel getModel(int quantity)
|
||||||
|
{
|
||||||
|
if (modelOverride == -1)
|
||||||
|
{
|
||||||
|
return rs$getModel(quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.getItemDefinition(modelOverride).getModel(quantity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import net.runelite.api.Perspective;
|
|||||||
import net.runelite.api.TileModel;
|
import net.runelite.api.TileModel;
|
||||||
import net.runelite.api.TilePaint;
|
import net.runelite.api.TilePaint;
|
||||||
import net.runelite.api.Tile;
|
import net.runelite.api.Tile;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
import net.runelite.api.hooks.DrawCallbacks;
|
import net.runelite.api.hooks.DrawCallbacks;
|
||||||
import net.runelite.api.mixins.Copy;
|
import net.runelite.api.mixins.Copy;
|
||||||
import net.runelite.api.mixins.Inject;
|
import net.runelite.api.mixins.Inject;
|
||||||
@@ -38,6 +39,8 @@ import net.runelite.api.mixins.Shadow;
|
|||||||
import net.runelite.rs.api.RSBoundaryObject;
|
import net.runelite.rs.api.RSBoundaryObject;
|
||||||
import net.runelite.rs.api.RSClient;
|
import net.runelite.rs.api.RSClient;
|
||||||
import net.runelite.rs.api.RSFloorDecoration;
|
import net.runelite.rs.api.RSFloorDecoration;
|
||||||
|
import net.runelite.rs.api.RSNodeDeque;
|
||||||
|
import net.runelite.rs.api.RSTileItem;
|
||||||
import net.runelite.rs.api.RSTileItemPile;
|
import net.runelite.rs.api.RSTileItemPile;
|
||||||
import net.runelite.rs.api.RSScene;
|
import net.runelite.rs.api.RSScene;
|
||||||
import net.runelite.rs.api.RSTile;
|
import net.runelite.rs.api.RSTile;
|
||||||
@@ -716,4 +719,72 @@ public abstract class RSSceneMixin implements RSScene
|
|||||||
client.setSelectedSceneTileX(targetX);
|
client.setSelectedSceneTileX(targetX);
|
||||||
client.setSelectedSceneTileY(targetY);
|
client.setSelectedSceneTileY(targetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Inject
|
||||||
|
public void addItem(int id, int quantity, WorldPoint point)
|
||||||
|
{
|
||||||
|
final int sceneX = point.getX() - client.getBaseX();
|
||||||
|
final int sceneY = point.getY() - client.getBaseY();
|
||||||
|
final int plane = point.getPlane();
|
||||||
|
|
||||||
|
if (sceneX < 0 || sceneY < 0 || sceneX >= 104 || sceneY >= 104)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSTileItem item = client.newTileItem();
|
||||||
|
item.setId(id);
|
||||||
|
item.setQuantity(quantity);
|
||||||
|
RSNodeDeque[][][] groundItems = client.getGroundItemDeque();
|
||||||
|
|
||||||
|
if (groundItems[plane][sceneX][sceneY] == null)
|
||||||
|
{
|
||||||
|
groundItems[plane][sceneX][sceneY] = client.newNodeDeque();
|
||||||
|
}
|
||||||
|
|
||||||
|
groundItems[plane][sceneX][sceneY].addFirst(item);
|
||||||
|
|
||||||
|
if (plane == client.getPlane())
|
||||||
|
{
|
||||||
|
client.updateItemPile(sceneX, sceneY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Inject
|
||||||
|
public void removeItem(int id, int quantity, WorldPoint point)
|
||||||
|
{
|
||||||
|
final int sceneX = point.getX() - client.getBaseX();
|
||||||
|
final int sceneY = point.getY() - client.getBaseY();
|
||||||
|
final int plane = point.getPlane();
|
||||||
|
|
||||||
|
if (sceneX < 0 || sceneY < 0 || sceneX >= 104 || sceneY >= 104)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RSNodeDeque items = client.getGroundItemDeque()[plane][sceneX][sceneY];
|
||||||
|
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (RSTileItem item = (RSTileItem) items.last(); item != null; item = (RSTileItem) items.previous())
|
||||||
|
{
|
||||||
|
if (item.getId() == id && quantity == 1)
|
||||||
|
{
|
||||||
|
item.unlink();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.last() == null)
|
||||||
|
{
|
||||||
|
client.getGroundItemDeque()[plane][sceneX][sceneY] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.updateItemPile(sceneX, sceneY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1100,4 +1100,13 @@ public interface RSClient extends RSGameShell, Client
|
|||||||
*/
|
*/
|
||||||
@Import("Login_promptCredentials")
|
@Import("Login_promptCredentials")
|
||||||
void promptCredentials(boolean clearPass);
|
void promptCredentials(boolean clearPass);
|
||||||
}
|
|
||||||
|
@Construct
|
||||||
|
RSTileItem newTileItem();
|
||||||
|
|
||||||
|
@Construct
|
||||||
|
RSNodeDeque newNodeDeque();
|
||||||
|
|
||||||
|
@Import("updateItemPile")
|
||||||
|
void updateItemPile(int localX, int localY);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ public interface RSItemDefinition extends ItemDefinition
|
|||||||
@Override
|
@Override
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
@Import("name")
|
||||||
|
@Override
|
||||||
|
void setName(String name);
|
||||||
|
|
||||||
@Import("id")
|
@Import("id")
|
||||||
@Override
|
@Override
|
||||||
int getId();
|
int getId();
|
||||||
@@ -41,6 +45,10 @@ public interface RSItemDefinition extends ItemDefinition
|
|||||||
@Override
|
@Override
|
||||||
boolean isTradeable();
|
boolean isTradeable();
|
||||||
|
|
||||||
|
@Import("isTradable")
|
||||||
|
@Override
|
||||||
|
void setTradeable(boolean yes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You probably want {@link #isStackable}
|
* You probably want {@link #isStackable}
|
||||||
* <p>
|
* <p>
|
||||||
@@ -60,4 +68,7 @@ public interface RSItemDefinition extends ItemDefinition
|
|||||||
@Import("getShiftClickIndex")
|
@Import("getShiftClickIndex")
|
||||||
@Override
|
@Override
|
||||||
int getShiftClickActionIndex();
|
int getShiftClickActionIndex();
|
||||||
|
|
||||||
|
@Import("getModel")
|
||||||
|
RSModel getModel(int quantity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,13 @@ public interface RSNodeDeque
|
|||||||
|
|
||||||
@Import("sentinel")
|
@Import("sentinel")
|
||||||
RSNode getHead();
|
RSNode getHead();
|
||||||
|
|
||||||
|
@Import("last")
|
||||||
|
RSNode last();
|
||||||
|
|
||||||
|
@Import("previous")
|
||||||
|
RSNode previous();
|
||||||
|
|
||||||
|
@Import("addFirst")
|
||||||
|
void addFirst(RSNode val);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,7 @@ public interface RSScene extends Scene
|
|||||||
|
|
||||||
@Import("minPlane")
|
@Import("minPlane")
|
||||||
int getMinLevel();
|
int getMinLevel();
|
||||||
|
|
||||||
|
@Import("newGroundItemPile")
|
||||||
|
void newGroundItemPile(int plane, int x, int y, int hash, RSEntity var5, long var6, RSEntity var7, RSEntity var8);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user