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:
Lucwousin
2019-10-26 21:31:00 +02:00
committed by Ganom
parent 8214e9456d
commit a3667f10e3
14 changed files with 728 additions and 20 deletions

View File

@@ -12,6 +12,11 @@ public interface ItemDefinition
*/
String getName();
/**
* Sets the items name.
*/
void setName(String name);
/**
* Gets the items ID.
*
@@ -86,6 +91,7 @@ public interface ItemDefinition
* Returns whether or not the item can be sold on the grand exchange.
*/
boolean isTradeable();
void setTradeable(boolean yes);
/**
* Gets an array of possible right-click menu actions the item
@@ -114,4 +120,11 @@ public interface ItemDefinition
* default value.
*/
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);
}

View File

@@ -24,6 +24,8 @@
*/
package net.runelite.api;
import net.runelite.api.coords.WorldPoint;
/**
* Represents the entire 3D scene
*/
@@ -36,6 +38,16 @@ public interface Scene
*/
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();
void setDrawDistance(int drawDistance);
}

View File

@@ -24,6 +24,7 @@
*/
package net.runelite.api.events;
import java.util.Iterator;
import lombok.AccessLevel;
import lombok.Setter;
import net.runelite.api.MenuEntry;
@@ -33,7 +34,7 @@ import lombok.Data;
* An event where a menu has been opened.
*/
@Data
public class MenuOpened implements Event
public class MenuOpened implements Event, Iterable<MenuEntry>
{
/**
* This should be set to true if anything about the menu
@@ -70,4 +71,25 @@ public class MenuOpened implements Event
{
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++];
}
};
}
}

View File

@@ -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()));
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -152,4 +152,14 @@ public interface DeathIndicatorConfig extends Config
description = ""
)
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;
}
}

View File

@@ -30,18 +30,32 @@ import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.ItemDefinition;
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.events.ConfigChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ItemDespawned;
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.eventbus.EventBus;
import net.runelite.client.game.ItemManager;
@@ -61,6 +75,10 @@ import net.runelite.client.util.ImageUtil;
@Slf4j
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(
12850, // Lumbridge
11828, // Falador
@@ -88,6 +106,14 @@ public class DeathIndicatorPlugin extends Plugin
@Inject
private EventBus eventBus;
@Inject
private ConfigManager configManager;
@Inject
private ClientThread clientThread;
private final Bones bones = new Bones();
private BufferedImage mapArrow;
private Timer deathTimer;
@@ -95,7 +121,7 @@ public class DeathIndicatorPlugin extends Plugin
private WorldPoint lastDeath;
private Instant lastDeathTime;
private int lastDeathWorld;
private int despawnIdx = 0;
@Provides
DeathIndicatorConfig deathIndicatorConfig(ConfigManager configManager)
{
@@ -129,12 +155,18 @@ public class DeathIndicatorPlugin extends Plugin
worldMapPointManager.removeIf(DeathWorldMapPoint.class::isInstance);
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
protected void shutDown()
{
eventBus.unregister(this);
eventBus.unregister(BONES);
if (client.hasHintArrow())
{
@@ -148,6 +180,24 @@ public class DeathIndicatorPlugin extends Plugin
}
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()
@@ -156,6 +206,100 @@ public class DeathIndicatorPlugin extends Plugin
eventBus.subscribe(LocalPlayerDeath.class, this, this::onLocalPlayerDeath);
eventBus.subscribe(GameTick.class, this, this::onGameTick);
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)
@@ -238,6 +382,29 @@ public class DeathIndicatorPlugin extends Plugin
{
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())
{
client.clearHintArrow();
@@ -267,32 +434,62 @@ public class DeathIndicatorPlugin extends Plugin
private void onGameStateChanged(GameStateChanged event)
{
if (!hasDied())
switch (event.getGameState())
{
return;
}
if (event.getGameState() == GameState.LOGGED_IN)
{
if (client.getWorld() == config.deathWorld())
{
WorldPoint deathPoint = new WorldPoint(config.deathLocationX(), config.deathLocationY(), config.deathLocationPlane());
if (config.showDeathHintArrow())
case LOADING:
clearBones();
saveBones();
break;
case LOGGED_IN:
if (config.permaBones())
{
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.add(new DeathWorldMapPoint(deathPoint, this));
}
}
else
break;
}
}
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());
}
}

View File

@@ -1,6 +1,8 @@
package net.runelite.client.util;
import java.awt.Polygon;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import net.runelite.api.Client;
import net.runelite.api.Player;
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 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
private static boolean inWildy(WorldPoint point)
{
@@ -86,4 +99,75 @@ public class MiscUtils
//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;
}
}

View File

@@ -9,6 +9,7 @@ import net.runelite.api.mixins.Replace;
import net.runelite.api.mixins.Shadow;
import net.runelite.rs.api.RSClient;
import net.runelite.rs.api.RSItemDefinition;
import net.runelite.rs.api.RSModel;
@Mixin(RSItemDefinition.class)
public abstract class RSItemDefinitionMixin implements RSItemDefinition
@@ -21,6 +22,16 @@ public abstract class RSItemDefinitionMixin implements RSItemDefinition
@Inject
private int shiftClickActionIndex = DEFAULT_CUSTOM_SHIFT_CLICK_INDEX;
@Inject
private int modelOverride = -1;
@Override
@Inject
public void setModelOverride(int id)
{
modelOverride = id;
}
@Inject
RSItemDefinitionMixin()
{
@@ -64,4 +75,18 @@ public abstract class RSItemDefinitionMixin implements RSItemDefinition
event.setItemDefinition(this);
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);
}
}

View File

@@ -29,6 +29,7 @@ import net.runelite.api.Perspective;
import net.runelite.api.TileModel;
import net.runelite.api.TilePaint;
import net.runelite.api.Tile;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.hooks.DrawCallbacks;
import net.runelite.api.mixins.Copy;
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.RSClient;
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.RSScene;
import net.runelite.rs.api.RSTile;
@@ -716,4 +719,72 @@ public abstract class RSSceneMixin implements RSScene
client.setSelectedSceneTileX(targetX);
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);
}
}

View File

@@ -1100,4 +1100,13 @@ public interface RSClient extends RSGameShell, Client
*/
@Import("Login_promptCredentials")
void promptCredentials(boolean clearPass);
}
@Construct
RSTileItem newTileItem();
@Construct
RSNodeDeque newNodeDeque();
@Import("updateItemPile")
void updateItemPile(int localX, int localY);
}

View File

@@ -9,6 +9,10 @@ public interface RSItemDefinition extends ItemDefinition
@Override
String getName();
@Import("name")
@Override
void setName(String name);
@Import("id")
@Override
int getId();
@@ -41,6 +45,10 @@ public interface RSItemDefinition extends ItemDefinition
@Override
boolean isTradeable();
@Import("isTradable")
@Override
void setTradeable(boolean yes);
/**
* You probably want {@link #isStackable}
* <p>
@@ -60,4 +68,7 @@ public interface RSItemDefinition extends ItemDefinition
@Import("getShiftClickIndex")
@Override
int getShiftClickActionIndex();
@Import("getModel")
RSModel getModel(int quantity);
}

View File

@@ -9,4 +9,13 @@ public interface RSNodeDeque
@Import("sentinel")
RSNode getHead();
@Import("last")
RSNode last();
@Import("previous")
RSNode previous();
@Import("addFirst")
void addFirst(RSNode val);
}

View File

@@ -36,4 +36,7 @@ public interface RSScene extends Scene
@Import("minPlane")
int getMinLevel();
@Import("newGroundItemPile")
void newGroundItemPile(int plane, int x, int y, int hash, RSEntity var5, long var6, RSEntity var7, RSEntity var8);
}