Add Kourend Library plugin

This commit is contained in:
Max Weber
2018-02-17 01:38:12 -07:00
parent db9866a13e
commit fe16a0537e
40 changed files with 1846 additions and 0 deletions

View File

@@ -103,6 +103,7 @@ public final class AnimationID
public static final int HERBLORE_POTIONMAKING = 363; //used for both herb and secondary
public static final int MAGIC_CHARGING_ORBS = 726;
public static final int BURYING_BONES = 827;
public static final int LOOKING_INTO = 832;
// NPC animations
public static final int TZTOK_JAD_MAGIC_ATTACK = 2656;

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.api;
import lombok.Value;
import net.runelite.api.Point;
@Value
public class Point3D
{
private final int X, Y, Z;
public Point toPoint()
{
return new Point(X, Y);
}
}

View File

@@ -66,6 +66,7 @@ public class WidgetID
public static final int PUZZLE_BOX_GROUP_ID = 306;
public static final int NIGHTMARE_ZONE_GROUP_ID = 202;
public static final int BLAST_FURNACE_GROUP_ID = 474;
public static final int LIBRARY_FIND_GROUP_ID = 193;
static class WorldMap
{
@@ -287,4 +288,10 @@ public class WidgetID
{
static final int VISIBLE_BOX = 4;
}
static class DialogLibrary
{
static final int ITEM = 0;
static final int TEXT = 1;
}
}

View File

@@ -120,6 +120,9 @@ public enum WidgetInfo
COMBAT_SPELL_ICON(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.SPELL_ICON),
COMBAT_SPELL_TEXT(WidgetID.COMBAT_GROUP_ID, WidgetID.Combat.SPELL_TEXT),
DIALOG_LIBRARY_FIND(WidgetID.LIBRARY_FIND_GROUP_ID, WidgetID.DialogLibrary.ITEM),
DIALOG_LIBRARY_FIND_TEXT(WidgetID.LIBRARY_FIND_GROUP_ID, WidgetID.DialogLibrary.TEXT),
DIALOG_NPC_NAME(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.NAME),
DIALOG_NPC_TEXT(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.TEXT),
DIALOG_NPC_HEAD_MODEL(WidgetID.DIALOG_NPC_GROUP_ID, WidgetID.DialogNPC.HEAD_MODEL),

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import lombok.Getter;
import net.runelite.api.ItemID;
public enum Book
{
DARK_MANUSCRIPT_13514(ItemID.DARK_MANUSCRIPT),
DARK_MANUSCRIPT_13515(ItemID.DARK_MANUSCRIPT_13515),
DARK_MANUSCRIPT_13516(ItemID.DARK_MANUSCRIPT_13516),
DARK_MANUSCRIPT_13517(ItemID.DARK_MANUSCRIPT_13517),
DARK_MANUSCRIPT_13518(ItemID.DARK_MANUSCRIPT_13518),
DARK_MANUSCRIPT_13519(ItemID.DARK_MANUSCRIPT_13519),
DARK_MANUSCRIPT_13520(ItemID.DARK_MANUSCRIPT_13520),
DARK_MANUSCRIPT_13521(ItemID.DARK_MANUSCRIPT_13521),
DARK_MANUSCRIPT_13522(ItemID.DARK_MANUSCRIPT_13522),
DARK_MANUSCRIPT_13523(ItemID.DARK_MANUSCRIPT_13523),
RADAS_CENSUS(ItemID.RADAS_CENSUS, "Rada's Census", "Census of King Rada III, by Matthias Vorseth."),
RICKTORS_DIARY_7(ItemID.RICKTORS_DIARY_7, "Ricktor's Diary 7", "Diary of Steklan Ricktor, volume 7."),
EATHRAM_RADA_EXTRACT(ItemID.EATHRAM__RADA_EXTRACT, "Eathram & Rada extract", "An extract from Eathram & Rada, by Anonymous."),
KILLING_OF_A_KING(ItemID.KILLING_OF_A_KING, "Killing of a King", "Killing of a King, by Griselle."),
HOSIDIUS_LETTER(ItemID.HOSIDIUS_LETTER, "Hosidius Letter", "A letter from Lord Hosidius to the Council of Elders."),
WINTERTODT_PARABLE(ItemID.WINTERTODT_PARABLE, "Wintertodt Parable", "The Parable of the Wintertodt, by Anonymous."),
TWILL_ACCORD(ItemID.TWILL_ACCORD, "Twill Accord", "The Royal Accord of Twill."),
BYRNES_CORONATION_SPEECH(ItemID.BYRNES_CORONATION_SPEECH, "Byrnes Coronation Speech", "Speech of King Byrne I, on the occasion of his coronation."),
IDEOLOGY_OF_DARKNESS(ItemID.IDEOLOGY_OF_DARKNESS, "The Ideology of Darkness", "The Ideology of Darkness, by Philophaire."),
RADAS_JOURNEY(ItemID.RADAS_JOURNEY, "Rada's Journey", "The Journey of Rada, by Griselle."),
TRANSVERGENCE_THEORY(ItemID.TRANSVERGENCE_THEORY, "Transvergence Theory", "The Theory of Transvergence, by Amon Ducot."),
TRISTESSAS_TRAGEDY(ItemID.TRISTESSAS_TRAGEDY, "Tristessa's Tradgedy", "The Tragedy of Tristessa."),
TREACHERY_OF_ROYALTY(ItemID.TREACHERY_OF_ROYALTY, "The Treachery of Royalty", "The Treachery of Royalty, by Professor Answith."),
TRANSPORTATION_INCANTATIONS(ItemID.TRANSPORTATION_INCANTATIONS, "Transportation Incantations", "Transportation Incantations, by Amon Ducot."),
SOUL_JORUNEY(ItemID.SOUL_JOURNEY, "Soul Journey", "The Journey of Souls, by Aretha."),
VARLAMORE_ENVOY(ItemID.VARLAMORE_ENVOY, "Varlamore Envoy", "The Envoy to Varlamore, by Deryk Paulson.");
private static final Map<Integer, Book> BY_ID = buildById();
private static final Map<String, Book> BY_NAME = buildByName();
private static Map<Integer, Book> buildById()
{
HashMap<Integer, Book> byId = new HashMap<>();
for (Book b : Book.values())
{
byId.put(b.item, b);
}
return Collections.unmodifiableMap(byId);
}
private static final Map<String, Book> buildByName()
{
HashMap<String, Book> byName = new HashMap<>();
for (Book b : Book.values())
{
if (!b.isDarkManuscript)
{
byName.put(b.name, b);
}
}
return Collections.unmodifiableMap(byName);
}
public static Book byId(int id)
{
return BY_ID.get(id);
}
public static Book byName(String name)
{
return BY_NAME.get(name);
}
@Getter
private final int item;
@Getter
private final String name;
@Getter
private final String shortName;
@Getter
private final BufferedImage icon;
@Getter
private final boolean isDarkManuscript;
Book(int id, String shortName, String name)
{
this.item = id;
this.isDarkManuscript = false;
this.shortName = shortName;
this.name = name;
this.icon = getImage(Integer.toString(id));
}
Book(int id)
{
this.item = id;
this.isDarkManuscript = true;
this.name = "Dark Manuscript";
this.shortName = "Dark Manuscript";
this.icon = getImage(Integer.toString(id));
}
private static BufferedImage getImage(String name)
{
try
{
return ImageIO.read(Book.class.getResourceAsStream("items/" + name + ".png"));
}
catch (IOException | IllegalArgumentException e)
{
throw new RuntimeException("Cannot load book " + name, e);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import java.awt.Color;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.runelite.client.ui.FontManager;
class BookPanel extends JPanel
{
private JLabel location = new JLabel();
BookPanel(Book b)
{
GroupLayout layout = new GroupLayout(this);
this.setLayout(layout);
JLabel image = new JLabel(new ImageIcon(b.getIcon()));
JLabel name = new JLabel(b.getShortName());
location.setFont(FontManager.getRunescapeSmallFont());
layout.setVerticalGroup(layout.createParallelGroup()
.addComponent(image)
.addGroup(layout.createSequentialGroup()
.addComponent(name)
.addComponent(location)
)
);
layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(image)
.addGap(8)
.addGroup(layout.createParallelGroup()
.addComponent(name)
.addComponent(location)
)
);
// AWT's Z order is weird. This put image at the back of the stack
setComponentZOrder(image, getComponentCount() - 1);
}
public void setLocation(String location)
{
this.location.setText(location);
}
public void setIsTarget(boolean target)
{
location.setForeground(target ? Color.GREEN : Color.WHITE);
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import net.runelite.api.Point3D;
class Bookcase
{
public Bookcase(Point3D location)
{
this.location = location;
this.index = new ArrayList<>();
}
@Getter
private final Point3D location;
@Getter
private final List<Integer> index;
@Getter
private boolean isBookSet;
/**
* Book in this bookcase as found by the player.
* Will be correct as long as isBookSet is true, unless the library has reset;
*/
@Getter
private Book book;
/**
* Books that can be in this slot. Will only be populated if library.state != SolvedState.NO_DATA
*/
@Getter
private Set<Book> possibleBooks = new HashSet<>();
public void clearBook()
{
book = null;
isBookSet = false;
}
public void setBook(Book book)
{
this.book = book;
this.isBookSet = true;
}
public String getLocationString()
{
StringBuilder b = new StringBuilder();
boolean north = location.getY() > 3815;
boolean west = location.getX() < 1625;
if (north && west)
{
b.append("Northwest");
}
else if (north)
{
b.append("Northeast");
}
else if (west)
{
b.append("Southwest");
}
else
{
b.append("Center");
}
b.append(" ");
switch (location.getZ())
{
case 0:
b.append("ground floor");
break;
case 1:
b.append("middle floor");
break;
case 2:
b.append("top floor");
break;
}
if (KourendLibraryPlugin.debug)
{
b.append(" ").append(index.stream().map(Object::toString).collect(Collectors.joining(", ")));
}
return b.toString();
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import com.google.inject.Inject;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import net.runelite.api.Client;
import net.runelite.api.Perspective;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.Point3D;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
import static net.runelite.api.Perspective.getCanvasTilePoly;
public class KourendLibraryOverlay extends Overlay
{
private final static Point LIBRARY_CENTER = new Point(1632, 3807);
private final static int MAXIMUM_DISTANCE = 24;
private final static int ROUGH_ENABLE_DISTANCE = 45;
private final Library library;
private final Client client;
@Inject
KourendLibraryOverlay(Library library, Client client)
{
this.library = library;
this.client = client;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
}
@Override
public Dimension render(Graphics2D g, java.awt.Point parent)
{
Player player = client.getLocalPlayer();
if (player == null)
{
return null;
}
Point playerLoc = player.getWorldLocation();
if (playerLoc.distanceTo(LIBRARY_CENTER) > ROUGH_ENABLE_DISTANCE)
{
return null;
}
List<Bookcase> allBookcases = library.getBookcasesOnLevel(client.getPlane());
if (allBookcases == null)
{
return null;
}
for (Bookcase bookcase : allBookcases)
{
// AABB
Point3D caseLoc = bookcase.getLocation();
if (Math.abs(playerLoc.getX() - caseLoc.getX()) > MAXIMUM_DISTANCE
|| Math.abs(playerLoc.getY() - caseLoc.getY()) > MAXIMUM_DISTANCE)
{
continue;
}
Point localBookcase = Perspective.worldToLocal(client, caseLoc.toPoint());
Point screenBookcase = Perspective.worldToCanvas(client, localBookcase.getX(), localBookcase.getY(), caseLoc.getZ(), 25);
if (screenBookcase != null)
{
boolean bookIsKnown = bookcase.isBookSet();
Book book = bookcase.getBook();
Set<Book> possible = bookcase.getPossibleBooks();
if (bookIsKnown && book == null)
{
for (Book b : possible)
{
if (b != null && b.isDarkManuscript())
{
book = b;
break;
}
}
}
if (!bookIsKnown && possible.size() == 1)
{
book = possible.iterator().next();
bookIsKnown = true;
}
Color color = bookIsKnown ? Color.ORANGE : Color.WHITE;
// Render the poly on the floor
if (!(bookIsKnown && book == null) && (library.getState() == SolvedState.NO_DATA || book != null || possible.size() > 0))
{
Polygon poly = getCanvasTilePoly(client, localBookcase);
if (poly != null)
{
OverlayUtil.renderPolygon(g, poly, color);
}
}
int height = 0;
// If the book is singled out, render the text and the book's icon
if (bookIsKnown)
{
if (book != null)
{
FontMetrics fm = g.getFontMetrics();
Rectangle2D bounds = fm.getStringBounds(book.getShortName(), g);
height = (int) bounds.getHeight() + book.getIcon().getHeight() + 6;
Point textLoc = new Point(
(int) (screenBookcase.getX() - (bounds.getWidth() / 2)),
screenBookcase.getY() - (height / 2) + (int) bounds.getHeight()
);
OverlayUtil.renderTextLocation(g, textLoc, book.getShortName(), color);
g.drawImage(
book.getIcon(),
screenBookcase.getX() - (book.getIcon().getWidth() / 2),
screenBookcase.getY() + (height / 2) - book.getIcon().getHeight(),
null
);
}
}
else
{
// otherwise render up to 9 icons of the possible books in the bookcase in a square
final int BOOK_ICON_SIZE = 32;
Book[] books = possible.stream()
.filter(Objects::nonNull)
.limit(9)
.toArray(Book[]::new);
if (books.length > 1 && books.length <= 9)
{
int cols = (int) Math.ceil(Math.sqrt(books.length));
int rows = (int) Math.ceil((double) books.length / cols);
height = rows * BOOK_ICON_SIZE;
int xbase = screenBookcase.getX() - ((cols * BOOK_ICON_SIZE) / 2);
int ybase = screenBookcase.getY() - rows * BOOK_ICON_SIZE / 2;
for (int i = 0; i < books.length; i++)
{
int col = i % cols;
int row = i / cols;
int x = col * BOOK_ICON_SIZE;
int y = row * BOOK_ICON_SIZE;
if (row == rows - 1)
{
x += (BOOK_ICON_SIZE * (books.length % rows)) / 2;
}
g.drawImage(books[i].getIcon(), xbase + x, ybase + y, null);
}
}
}
// Draw the bookcase's ID on top
if (KourendLibraryPlugin.debug)
{
FontMetrics fm = g.getFontMetrics();
String str = bookcase.getIndex().stream().map(Object::toString).collect(Collectors.joining(", "));
Rectangle2D bounds = fm.getStringBounds(str, g);
Point textLoc = new Point((int) (screenBookcase.getX() - (bounds.getWidth() / 2)), screenBookcase.getY() - (height / 2));
OverlayUtil.renderTextLocation(g, textLoc, str, Color.WHITE);
}
}
}
// Render the customer's wanted book on their head and a poly under their feet
LibraryCustomer customer = library.getCustomer();
if (customer != null)
{
client.getNpcs().stream()
.filter(n -> n.getId() == customer.getId())
.forEach(n ->
{
Book b = library.getCustomerBook();
Point local = n.getLocalLocation();
Polygon poly = getCanvasTilePoly(client, local);
OverlayUtil.renderPolygon(g, poly, Color.WHITE);
Point screen = Perspective.worldToCanvas(client, local.getX(), local.getY(), client.getPlane(), n.getLogicalHeight());
if (screen != null)
{
g.drawImage(b.getIcon(), screen.getX() - (b.getIcon().getWidth() / 2), screen.getY() - b.getIcon().getHeight(), null);
}
});
}
return null;
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import com.google.inject.Inject;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Singleton;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import net.runelite.client.ui.PluginPanel;
@Singleton
public class KourendLibraryPanel extends PluginPanel
{
@Inject
private Library library;
private final HashMap<Book, BookPanel> bookPanels = new HashMap<>();
void init()
{
GroupLayout layout = new GroupLayout(this);
setLayout(layout);
JPanel books = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.gridx = 0;
c.gridy = 0;
Stream.of(Book.values())
.filter(b -> !b.isDarkManuscript())
.sorted(Comparator.comparing(Book::getShortName))
.forEach(b ->
{
BookPanel p = new BookPanel(b);
bookPanels.put(b, p);
books.add(p, c);
c.gridy++;
});
JButton reset = new JButton("Reset");
reset.addActionListener(e ->
{
library.reset();
update();
});
layout.setHorizontalGroup(layout.createParallelGroup()
.addComponent(books)
.addComponent(reset)
);
layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(books)
.addGap(4)
.addComponent(reset)
);
update();
}
void update()
{
SwingUtilities.invokeLater(() ->
{
Book customerBook = library.getCustomerBook();
for (Map.Entry<Book, BookPanel> b : bookPanels.entrySet())
{
b.getValue().setIsTarget(customerBook == b.getKey());
}
HashMap<Book, HashSet<String>> bookLocations = new HashMap<>();
for (Bookcase bookcase : library.getBookcases())
{
if (bookcase.getBook() != null)
{
bookLocations.computeIfAbsent(bookcase.getBook(), a -> new HashSet<>()).add(bookcase.getLocationString());
}
else
{
for (Book book : bookcase.getPossibleBooks())
{
if (book != null)
{
bookLocations.computeIfAbsent(book, a -> new HashSet<>()).add(bookcase.getLocationString());
}
}
}
}
for (Map.Entry<Book, BookPanel> e : bookPanels.entrySet())
{
HashSet<String> locs = bookLocations.get(e.getKey());
if (locs == null || locs.size() > 3)
{
e.getValue().setLocation("Unknown");
}
else
{
e.getValue().setLocation("<html>" + locs.stream().collect(Collectors.joining("<br>")) + "</html>");
}
}
});
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import com.google.common.eventbus.Subscribe;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.AnimationID;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.MenuAction;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.Point3D;
import net.runelite.api.events.AnimationChanged;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.ui.overlay.Overlay;
@PluginDescriptor(
name = "Kourend Library"
)
@Slf4j
public class KourendLibraryPlugin extends Plugin
{
final static boolean debug = false;
@Inject
private ClientUI ui;
@Inject
private Client client;
@Inject
private Library library;
@Inject
private KourendLibraryOverlay overlay;
private KourendLibraryPanel panel;
private NavigationButton navButton;
private Point3D lastBookcaseClick = null;
private Point3D lastBookcaseAnimatedOn = null;
@Override
protected void startUp() throws Exception
{
panel = injector.getInstance(KourendLibraryPanel.class);
panel.init();
navButton = new NavigationButton(
"Kourend Library",
ImageIO.read(Book.class.getResourceAsStream("panel_icon.png")),
() -> panel
);
ui.getPluginToolbar().addNavigation(navButton);
}
@Override
protected void shutDown()
{
ui.getPluginToolbar().removeNavigation(navButton);
}
@Override
public Overlay getOverlay()
{
return overlay;
}
@Subscribe
private void onMenuOptionClicked(MenuOptionClicked menuOpt)
{
if (MenuAction.GAME_OBJECT_FIRST_OPTION == menuOpt.getMenuAction() && menuOpt.getMenuTarget().contains("Bookshelf"))
{
Point worldPoint = Perspective.regionToWorld(client, new Point(menuOpt.getId() & 127, menuOpt.getId() >> 7 & 127));
lastBookcaseClick = new Point3D(worldPoint.getX(), worldPoint.getY(), client.getPlane());
}
}
@Subscribe
private void onAnimationChanged(AnimationChanged anim)
{
if (anim.getActor() == client.getLocalPlayer() && anim.getActor().getAnimation() == AnimationID.LOOKING_INTO)
{
lastBookcaseAnimatedOn = lastBookcaseClick;
}
}
@Subscribe
public void onChatMessage(ChatMessage event)
{
if (lastBookcaseAnimatedOn != null && event.getType() == ChatMessageType.SERVER)
{
if (event.getMessage().equals("You don't find anything useful here."))
{
library.mark(lastBookcaseAnimatedOn, null);
panel.update();
lastBookcaseAnimatedOn = null;
}
}
}
private static final Pattern BOOK_EXTRACTOR = Pattern.compile("'<col=0000ff>(.*)</col>'");
private static final Pattern TAG_MATCHER = Pattern.compile("(<[^>]*>)");
@Subscribe
void onTick(GameTick tick)
{
if (lastBookcaseAnimatedOn != null)
{
Widget find = client.getWidget(WidgetInfo.DIALOG_LIBRARY_FIND);
if (find != null)
{
Book book = Book.byId(find.getItemId());
if (book != null)
{
library.mark(lastBookcaseAnimatedOn, book);
panel.update();
lastBookcaseAnimatedOn = null;
}
}
}
Widget npcHead = client.getWidget(WidgetInfo.DIALOG_NPC_HEAD_MODEL);
if (npcHead != null)
{
LibraryCustomer cust = LibraryCustomer.getById(npcHead.getModelId());
if (cust != null)
{
Widget textw = client.getWidget(WidgetInfo.DIALOG_NPC_TEXT);
String text = textw.getText();
Matcher m = BOOK_EXTRACTOR.matcher(text);
if (m.find())
{
String bookName = TAG_MATCHER.matcher(m.group(1).replace("<br>", " ")).replaceAll("");
Book book = Book.byName(bookName);
if (book == null)
{
log.warn("Book '{}' is not recognised", bookName);
return;
}
library.setCustomer(cust, book);
panel.update();
}
else if (text.contains("You can have this other book") || text.contains("please accept a token of my thanks.") || text.contains("Thanks, I'll get on with reading it."))
{
library.setCustomer(null, null);
panel.update();
}
}
}
}
}

View File

@@ -0,0 +1,800 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Point3D;
import static net.runelite.client.plugins.kourendlibrary.Book.*;
/**
* Library represents a instance of the Kourend/Arceuus House library.
* <p>
* The library changes the locations of it's books every 60-90 minutes.
* Of the 554 bookcases in the library, only 346 of them can ever have books.
* 6 of the bookcases in the south-west corner of the top floor are duplicated.
* These 6 bookcases are not handled 100% correctly due to their low chance
* of being used to train the predictor.
* <p>
* Each of the 352 bookcase slots "Bookcase"s has an index which is used to
* place the book inside of them. The game chooses one of the 5 sequences and a
* bookcase starting index, then places a book from the sequence into every 13th
* bookcase, by index. Each sequence contains 26 Books, consisting of 16 books
* and 10 dark manuscripts. You can only get one dark manuscript at a time, though
* they are all placed into shelves.
*/
@Singleton
@Slf4j
public class Library
{
private final Map<Point3D, Bookcase> byPoint = new HashMap<>();
private final Map<Integer, ArrayList<Bookcase>> byLevel = new HashMap<>();
private final List<Bookcase> byIndex = new ArrayList<>();
private final List<List<Book>> sequences = populateSequences();
private final int step;
@Getter
private SolvedState state;
@Getter
private Book customerBook;
@Getter
private LibraryCustomer customer;
Library()
{
populateBooks();
step = byIndex.size() / Book.values().length;
reset();
}
public synchronized List<Bookcase> getBookcasesOnLevel(int z)
{
return Collections.unmodifiableList(byLevel.get(z));
}
public synchronized List<Bookcase> getBookcases()
{
return Collections.unmodifiableList(byIndex);
}
public void setCustomer(LibraryCustomer customer, Book book)
{
this.customer = customer;
this.customerBook = book;
}
public synchronized void reset()
{
state = SolvedState.NO_DATA;
for (Bookcase b : byIndex)
{
b.clearBook();
b.getPossibleBooks().clear();
}
log.info("Library is now reset");
}
public synchronized void mark(Point3D loc, Book book)
{
Bookcase bookcase = byPoint.get(loc);
if (bookcase == null)
{
log.debug("Requested non-existent bookcase at {}", loc);
return;
}
if (bookcase.isBookSet())
{
// Bookcase is set from a previous mark
// Check for a mismatch, unless it is now null and had a dark manuscript
if (book != bookcase.getBook() && !(book == null && bookcase.getBook().isDarkManuscript()))
{
reset();
}
}
else if (state != SolvedState.NO_DATA)
{
// We know all of the possible things in this shelf.
if (book != null)
{
// Check to see if our guess is wrong
if (!bookcase.getPossibleBooks().contains(book))
{
reset();
}
}
}
// Everything is known, nothing to do
if (state == SolvedState.COMPLETE)
{
return;
}
log.info("Setting bookcase {} to {}", bookcase.getIndex(), book);
for (; ; )
{
bookcase.setBook(book);
// Basing the sequences on null is not supported, though possible
if (book == null)
{
return;
}
// This is one of the 6 bookcases with 2 ids. Not fully supported.
if (bookcase.getIndex().size() != 1)
{
return;
}
int bookcaseIndex = bookcase.getIndex().get(0);
state = SolvedState.INCOMPLETE;
// Map each sequence to the number of bookcases that match the sequence
// return 0 if it is a mismatch.
// Keep in mind that Bookcases with dark manuscripts may be set to null.
int[] certainty = sequences.stream().mapToInt(sequence ->
{
int zero = getBookcaseZeroIndexForSequenceWithBook(sequence, bookcaseIndex, book);
int found = 0;
for (int i = 0; i < byIndex.size(); i++)
{
int ai = (i + zero) % byIndex.size();
Bookcase iBookcase = byIndex.get(ai);
if (i % step == 0)
{
int seqI = i / step;
if (iBookcase.isBookSet() && seqI < sequence.size())
{
Book seqBook = sequence.get(seqI);
boolean isSeqManuscript = seqBook == null || seqBook.isDarkManuscript();
if (!((isSeqManuscript && iBookcase.getBook() == null) || (iBookcase.getBook() == seqBook)))
{
log.debug("Bailing @ i={} ai={} {}; {} != {}", i, ai, iBookcase.getIndex(), iBookcase.getBook(), seqBook);
found = 0;
break;
}
found++;
}
}
else
{
// Only bail if this isn't a double bookcase
if (iBookcase.isBookSet() && iBookcase.getBook() != null && iBookcase.getIndex().size() == 1)
{
log.debug("Bailing @ i={} ai={} {}; {} is set", i, ai, iBookcase.getIndex(), iBookcase.getBook());
found = 0;
break;
}
}
}
return found;
}).toArray();
log.info("Certainty is now {}", certainty);
for (Bookcase b : byIndex)
{
b.getPossibleBooks().clear();
}
// Write the most likely sequences onto the bookcases
int max = IntStream.of(certainty).max().getAsInt();
// We have books set, but 0 sequences match, Something is wrong, reset.
if (max == 0)
{
reset();
continue;
}
IntStream.range(0, sequences.size())
.filter(i -> certainty[i] == max)
.forEach(isequence ->
{
List<Book> sequence = sequences.get(isequence);
int zero = getBookcaseZeroIndexForSequenceWithBook(sequence, bookcaseIndex, book);
for (int i = 0; i < byIndex.size(); i++)
{
int ai = (i + zero) % byIndex.size();
Bookcase iBookcase = byIndex.get(ai);
if (iBookcase.getBook() == null)
{
int iseq = i / step;
if (i % step == 0 && iseq < sequence.size())
{
Book seqBook = sequence.get(iseq);
iBookcase.getPossibleBooks().add(seqBook);
}
}
}
});
if (IntStream.range(0, certainty.length).filter(i -> certainty[i] == max).count() == 1)
{
state = SolvedState.COMPLETE;
}
return;
}
}
/**
* Find the bookcase index that is index zero in the sequence, identifying by the book in bookcase
*/
private int getBookcaseZeroIndexForSequenceWithBook(List<Book> sequences, int bookcaseIndex, Book book)
{
int bookSequence = sequences.indexOf(book);
assert bookSequence >= 0;
bookcaseIndex -= step * bookSequence;
for (; bookcaseIndex < 0; )
{
bookcaseIndex += byIndex.size();
}
return bookcaseIndex;
}
private List<List<Book>> populateSequences()
{
List<List<Book>> books = Arrays.asList(
Arrays.asList(
DARK_MANUSCRIPT_13516,
KILLING_OF_A_KING,
DARK_MANUSCRIPT_13520,
IDEOLOGY_OF_DARKNESS,
RADAS_JOURNEY,
TRANSVERGENCE_THEORY,
TRISTESSAS_TRAGEDY,
DARK_MANUSCRIPT_13523,
DARK_MANUSCRIPT_13521,
RADAS_CENSUS,
TREACHERY_OF_ROYALTY,
HOSIDIUS_LETTER,
DARK_MANUSCRIPT_13519,
RICKTORS_DIARY_7,
DARK_MANUSCRIPT_13514,
EATHRAM_RADA_EXTRACT,
DARK_MANUSCRIPT_13522,
VARLAMORE_ENVOY,
WINTERTODT_PARABLE,
TWILL_ACCORD,
DARK_MANUSCRIPT_13515,
BYRNES_CORONATION_SPEECH,
DARK_MANUSCRIPT_13517,
SOUL_JORUNEY,
DARK_MANUSCRIPT_13518,
TRANSPORTATION_INCANTATIONS
),
Arrays.asList(
DARK_MANUSCRIPT_13516,
KILLING_OF_A_KING,
DARK_MANUSCRIPT_13520,
IDEOLOGY_OF_DARKNESS,
RADAS_JOURNEY,
TRANSVERGENCE_THEORY,
TRISTESSAS_TRAGEDY,
DARK_MANUSCRIPT_13523,
DARK_MANUSCRIPT_13521,
RADAS_CENSUS,
TREACHERY_OF_ROYALTY,
HOSIDIUS_LETTER,
VARLAMORE_ENVOY,
DARK_MANUSCRIPT_13519,
RICKTORS_DIARY_7,
DARK_MANUSCRIPT_13514,
EATHRAM_RADA_EXTRACT,
DARK_MANUSCRIPT_13522,
SOUL_JORUNEY,
WINTERTODT_PARABLE,
TWILL_ACCORD,
DARK_MANUSCRIPT_13515,
BYRNES_CORONATION_SPEECH,
DARK_MANUSCRIPT_13517,
DARK_MANUSCRIPT_13518,
TRANSPORTATION_INCANTATIONS
),
Arrays.asList(
RICKTORS_DIARY_7,
VARLAMORE_ENVOY,
DARK_MANUSCRIPT_13514,
EATHRAM_RADA_EXTRACT,
IDEOLOGY_OF_DARKNESS,
DARK_MANUSCRIPT_13516,
DARK_MANUSCRIPT_13521,
RADAS_CENSUS,
DARK_MANUSCRIPT_13515,
KILLING_OF_A_KING,
DARK_MANUSCRIPT_13520,
TREACHERY_OF_ROYALTY,
HOSIDIUS_LETTER,
DARK_MANUSCRIPT_13519,
BYRNES_CORONATION_SPEECH,
DARK_MANUSCRIPT_13517,
SOUL_JORUNEY,
DARK_MANUSCRIPT_13522,
WINTERTODT_PARABLE,
TWILL_ACCORD,
RADAS_JOURNEY,
TRANSVERGENCE_THEORY,
TRISTESSAS_TRAGEDY,
DARK_MANUSCRIPT_13523,
DARK_MANUSCRIPT_13518,
TRANSPORTATION_INCANTATIONS
),
Arrays.asList(
RADAS_CENSUS,
DARK_MANUSCRIPT_13522,
RICKTORS_DIARY_7,
DARK_MANUSCRIPT_13514,
EATHRAM_RADA_EXTRACT,
DARK_MANUSCRIPT_13516,
KILLING_OF_A_KING,
DARK_MANUSCRIPT_13520,
HOSIDIUS_LETTER,
DARK_MANUSCRIPT_13519,
DARK_MANUSCRIPT_13521,
WINTERTODT_PARABLE,
TWILL_ACCORD,
DARK_MANUSCRIPT_13515,
BYRNES_CORONATION_SPEECH,
DARK_MANUSCRIPT_13517,
IDEOLOGY_OF_DARKNESS,
RADAS_JOURNEY,
TRANSVERGENCE_THEORY,
TRISTESSAS_TRAGEDY,
DARK_MANUSCRIPT_13523,
TREACHERY_OF_ROYALTY,
DARK_MANUSCRIPT_13518,
TRANSPORTATION_INCANTATIONS,
SOUL_JORUNEY,
VARLAMORE_ENVOY
),
Arrays.asList(
RADAS_CENSUS,
TRANSVERGENCE_THEORY,
TREACHERY_OF_ROYALTY,
RADAS_JOURNEY,
KILLING_OF_A_KING,
DARK_MANUSCRIPT_13520,
VARLAMORE_ENVOY,
DARK_MANUSCRIPT_13522,
BYRNES_CORONATION_SPEECH,
DARK_MANUSCRIPT_13517,
HOSIDIUS_LETTER,
DARK_MANUSCRIPT_13516,
DARK_MANUSCRIPT_13519,
TRISTESSAS_TRAGEDY,
DARK_MANUSCRIPT_13523,
DARK_MANUSCRIPT_13521,
RICKTORS_DIARY_7,
DARK_MANUSCRIPT_13514,
IDEOLOGY_OF_DARKNESS,
WINTERTODT_PARABLE,
TWILL_ACCORD,
SOUL_JORUNEY,
DARK_MANUSCRIPT_13515,
EATHRAM_RADA_EXTRACT,
DARK_MANUSCRIPT_13518,
TRANSPORTATION_INCANTATIONS
)
);
for (int i = 0; i < books.size(); i++)
{
assert new HashSet<>(books.get(i)).size() == books.get(i).size();
books.set(i, Collections.unmodifiableList(books.get(i)));
}
return Collections.unmodifiableList(books);
}
private void add(int x, int y, int z, int i)
{
// 'i' is added as a parameter for readability
Point3D p = new Point3D(x, y, z);
Bookcase b = byPoint.get(p);
if (b == null)
{
b = new Bookcase(p);
byPoint.put(p, b);
byLevel.computeIfAbsent(z, a -> new ArrayList<>()).add(b);
}
b.getIndex().add(i);
assert i == byIndex.size();
byIndex.add(b);
}
private void populateBooks()
{
add(1626, 3795, 0, 0);
add(1625, 3793, 0, 1);
add(1623, 3793, 0, 2);
add(1620, 3792, 0, 3);
add(1624, 3792, 0, 4);
add(1626, 3788, 0, 5);
add(1626, 3787, 0, 6);
add(1624, 3784, 0, 7);
add(1623, 3784, 0, 8);
add(1621, 3784, 0, 9);
add(1615, 3785, 0, 10);
add(1615, 3788, 0, 11);
add(1615, 3790, 0, 12);
add(1614, 3790, 0, 13);
add(1614, 3788, 0, 14);
add(1614, 3786, 0, 15);
add(1612, 3784, 0, 16);
add(1610, 3784, 0, 17);
add(1609, 3784, 0, 18);
add(1607, 3786, 0, 19);
add(1607, 3789, 0, 20);
add(1607, 3795, 0, 21);
add(1607, 3796, 0, 22);
add(1607, 3799, 0, 23);
add(1610, 3801, 0, 24);
add(1612, 3801, 0, 25);
add(1618, 3801, 0, 26);
add(1620, 3801, 0, 27);
add(1620, 3814, 0, 28);
add(1618, 3814, 0, 29);
add(1617, 3814, 0, 30);
add(1615, 3816, 0, 31);
add(1615, 3817, 0, 32);
add(1615, 3820, 0, 33);
add(1614, 3820, 0, 34);
add(1614, 3817, 0, 35);
add(1614, 3816, 0, 36);
add(1612, 3814, 0, 37);
add(1610, 3814, 0, 38);
add(1607, 3816, 0, 39);
add(1607, 3817, 0, 40);
add(1607, 3820, 0, 41);
add(1607, 3826, 0, 42);
add(1607, 3828, 0, 43);
add(1609, 3831, 0, 44);
add(1612, 3831, 0, 45);
add(1614, 3831, 0, 46);
add(1619, 3831, 0, 47);
add(1621, 3831, 0, 48);
add(1624, 3831, 0, 49);
add(1626, 3829, 0, 50);
add(1626, 3827, 0, 51);
add(1624, 3823, 0, 52);
add(1622, 3823, 0, 53);
add(1620, 3823, 0, 54);
add(1621, 3822, 0, 55);
add(1624, 3822, 0, 56);
add(1626, 3820, 0, 57);
add(1639, 3821, 0, 58);
add(1639, 3822, 0, 59);
add(1639, 3827, 0, 60);
add(1639, 3829, 0, 61);
add(1642, 3831, 0, 62);
add(1645, 3831, 0, 63);
add(1646, 3829, 0, 64);
add(1646, 3827, 0, 65);
add(1646, 3826, 0, 66);
add(1647, 3827, 0, 67);
add(1647, 3829, 0, 68);
add(1647, 3830, 0, 69);
add(1652, 3831, 0, 70);
add(1653, 3831, 0, 71);
add(1656, 3831, 0, 72);
add(1658, 3829, 0, 73);
add(1658, 3826, 0, 74);
add(1658, 3825, 0, 75);
add(1658, 3820, 0, 76);
add(1658, 3819, 0, 77);
add(1658, 3816, 0, 78);
add(1655, 3814, 0, 79);
add(1654, 3814, 0, 80);
add(1651, 3817, 0, 81);
add(1651, 3819, 0, 82);
add(1651, 3820, 0, 83);
add(1650, 3821, 0, 84);
add(1650, 3819, 0, 85);
add(1650, 3816, 0, 86);
add(1648, 3814, 0, 87);
add(1646, 3814, 0, 88);
add(1645, 3814, 0, 89);
add(1607, 3820, 1, 90);
add(1607, 3821, 1, 91);
add(1609, 3822, 1, 92);
add(1612, 3823, 1, 93);
add(1611, 3823, 1, 94);
add(1607, 3824, 1, 95);
add(1607, 3825, 1, 96);
add(1607, 3827, 1, 97);
add(1611, 3831, 1, 98);
add(1612, 3831, 1, 99);
add(1613, 3831, 1, 100);
add(1617, 3831, 1, 101);
add(1618, 3831, 1, 102);
add(1620, 3831, 1, 103);
add(1624, 3831, 1, 104);
add(1624, 3829, 1, 105);
add(1624, 3825, 1, 106);
add(1624, 3824, 1, 107);
add(1624, 3819, 1, 108);
add(1624, 3817, 1, 109);
add(1623, 3816, 1, 110);
add(1621, 3816, 1, 111);
add(1617, 3816, 1, 112);
add(1616, 3816, 1, 113);
add(1611, 3816, 1, 114);
add(1609, 3816, 1, 115);
add(1620, 3820, 1, 116);
add(1620, 3822, 1, 117);
add(1620, 3824, 1, 118);
add(1620, 3825, 1, 119);
add(1620, 3827, 1, 120);
add(1621, 3826, 1, 121);
add(1621, 3822, 1, 122);
add(1621, 3820, 1, 123);
add(1607, 3788, 1, 124);
add(1607, 3789, 1, 125);
add(1609, 3790, 1, 126);
add(1611, 3790, 1, 127);
add(1613, 3790, 1, 128);
add(1614, 3789, 1, 129);
add(1615, 3788, 1, 130);
add(1615, 3790, 1, 131);
add(1614, 3791, 1, 132);
add(1613, 3791, 1, 133);
add(1610, 3791, 1, 134);
add(1609, 3791, 1, 135);
add(1608, 3791, 1, 136);
add(1607, 3793, 1, 137);
add(1607, 3794, 1, 138);
add(1608, 3799, 1, 139);
add(1610, 3799, 1, 140);
add(1615, 3799, 1, 141);
add(1616, 3799, 1, 142);
add(1621, 3799, 1, 143);
add(1623, 3799, 1, 144);
add(1624, 3798, 1, 145);
add(1624, 3796, 1, 146);
add(1624, 3792, 1, 147);
add(1624, 3791, 1, 148);
add(1623, 3789, 1, 149);
add(1621, 3789, 1, 150);
add(1620, 3788, 1, 151);
add(1621, 3788, 1, 152);
add(1624, 3787, 1, 153);
add(1624, 3786, 1, 154);
add(1619, 3784, 1, 155);
add(1618, 3784, 1, 156);
add(1616, 3784, 1, 157);
add(1612, 3784, 1, 158);
add(1611, 3784, 1, 159);
add(1625, 3801, 1, 160);
add(1625, 3802, 1, 161);
add(1625, 3803, 1, 162);
add(1625, 3804, 1, 163);
add(1625, 3806, 1, 164);
add(1625, 3807, 1, 165);
add(1625, 3808, 1, 166);
add(1625, 3809, 1, 167);
add(1625, 3811, 1, 168);
add(1625, 3812, 1, 169);
add(1625, 3813, 1, 170);
add(1625, 3814, 1, 171);
add(1626, 3815, 1, 172);
add(1627, 3815, 1, 173);
add(1631, 3815, 1, 174);
add(1632, 3815, 1, 175);
add(1633, 3815, 1, 176);
add(1634, 3815, 1, 177);
add(1638, 3815, 1, 178);
add(1639, 3815, 1, 179);
add(1640, 3814, 1, 180);
add(1640, 3813, 1, 181);
add(1640, 3803, 1, 182);
add(1640, 3802, 1, 183);
add(1640, 3801, 1, 184);
add(1639, 3800, 1, 185);
add(1638, 3800, 1, 186);
add(1634, 3800, 1, 187);
add(1633, 3800, 1, 188);
add(1632, 3800, 1, 189);
add(1631, 3800, 1, 190);
add(1627, 3800, 1, 191);
add(1626, 3800, 1, 192);
add(1641, 3817, 1, 193);
add(1641, 3818, 1, 194);
add(1641, 3819, 1, 195);
add(1641, 3824, 1, 196);
add(1641, 3825, 1, 197);
add(1641, 3829, 1, 198);
add(1645, 3831, 1, 199);
add(1646, 3831, 1, 200);
add(1647, 3831, 1, 201);
add(1648, 3831, 1, 202);
add(1649, 3830, 1, 203);
add(1649, 3828, 1, 204);
add(1650, 3829, 1, 205);
add(1652, 3831, 1, 206);
add(1653, 3831, 1, 207);
add(1658, 3827, 1, 208);
add(1658, 3826, 1, 209);
add(1658, 3823, 1, 210);
add(1658, 3822, 1, 211);
add(1658, 3821, 1, 212);
add(1658, 3820, 1, 213);
add(1656, 3816, 1, 214);
add(1655, 3816, 1, 215);
add(1651, 3816, 1, 216);
add(1649, 3816, 1, 217);
add(1648, 3816, 1, 218);
add(1644, 3816, 1, 219);
add(1643, 3816, 1, 220);
add(1607, 3785, 2, 221);
add(1607, 3786, 2, 222);
add(1607, 3796, 2, 223);
add(1607, 3797, 2, 224);
add(1608, 3799, 2, 225);
add(1610, 3799, 2, 226);
add(1611, 3799, 2, 227);
add(1618, 3799, 2, 228);
add(1621, 3799, 2, 229);
add(1624, 3797, 2, 230);
add(1624, 3795, 2, 231);
add(1624, 3794, 2, 232);
add(1624, 3792, 2, 233);
add(1623, 3791, 2, 234);
add(1622, 3791, 2, 235);
add(1618, 3792, 2, 236);
add(1618, 3793, 2, 237);
add(1618, 3794, 2, 238);
add(1617, 3793, 2, 239);
add(1617, 3792, 2, 240);
add(1618, 3790, 2, 241);
add(1620, 3790, 2, 242);
add(1622, 3790, 2, 243);
add(1624, 3789, 2, 244);
add(1624, 3788, 2, 245);
add(1624, 3786, 2, 246);
add(1624, 3785, 2, 247);
add(1623, 3784, 2, 248);
add(1621, 3784, 2, 249);
add(1611, 3784, 2, 250);
add(1609, 3784, 2, 251);
add(1612, 3789, 2, 252);
add(1612, 3791, 2, 253);
add(1612, 3794, 2, 254);
add(1613, 3793, 2, 255);
add(1613, 3792, 2, 256);
add(1613, 3791, 2, 257);
add(1617, 3791, 2, 258);
add(1617, 3793, 2, 259);
add(1618, 3794, 2, 260);
add(1618, 3792, 2, 261);
add(1619, 3791, 2, 262);
add(1623, 3791, 2, 263);
add(1623, 3790, 2, 264);
add(1622, 3790, 2, 265);
add(1619, 3790, 2, 266);
add(1611, 3816, 2, 267);
add(1610, 3816, 2, 268);
add(1609, 3816, 2, 269);
add(1607, 3817, 2, 270);
add(1607, 3819, 2, 271);
add(1607, 3829, 2, 272);
add(1608, 3831, 2, 273);
add(1610, 3831, 2, 274);
add(1611, 3831, 2, 275);
add(1622, 3831, 2, 276);
add(1623, 3831, 2, 277);
add(1624, 3829, 2, 278);
add(1624, 3828, 2, 279);
add(1624, 3821, 2, 280);
add(1624, 3819, 2, 281);
add(1622, 3816, 2, 282);
add(1620, 3816, 2, 283);
add(1618, 3816, 2, 284);
add(1615, 3821, 2, 285);
add(1617, 3821, 2, 286);
add(1619, 3822, 2, 287);
add(1619, 3824, 2, 288);
add(1618, 3826, 2, 289);
add(1617, 3826, 2, 290);
add(1615, 3827, 2, 291);
add(1616, 3827, 2, 292);
add(1618, 3827, 2, 293);
add(1620, 3826, 2, 294);
add(1620, 3824, 2, 295);
add(1620, 3822, 2, 296);
add(1620, 3821, 2, 297);
add(1619, 3820, 2, 298);
add(1617, 3820, 2, 299);
add(1615, 3820, 2, 300);
add(1641, 3818, 2, 301);
add(1641, 3820, 2, 302);
add(1641, 3821, 2, 303);
add(1641, 3829, 2, 304);
add(1643, 3831, 2, 305);
add(1644, 3831, 2, 306);
add(1654, 3831, 2, 307);
add(1656, 3831, 2, 308);
add(1658, 3830, 2, 309);
add(1658, 3828, 2, 310);
add(1658, 3818, 2, 311);
add(1658, 3817, 2, 312);
add(1656, 3816, 2, 313);
add(1655, 3816, 2, 314);
add(1652, 3816, 2, 315);
add(1648, 3817, 2, 316);
add(1648, 3819, 2, 317);
add(1648, 3821, 2, 318);
add(1649, 3823, 2, 319);
add(1650, 3823, 2, 320);
add(1652, 3823, 2, 321);
add(1654, 3822, 2, 322);
add(1654, 3820, 2, 323);
add(1655, 3820, 2, 324);
add(1655, 3821, 2, 325);
add(1655, 3823, 2, 326);
add(1653, 3824, 2, 327);
add(1652, 3824, 2, 328);
add(1649, 3824, 2, 329);
add(1648, 3824, 2, 330);
add(1647, 3822, 2, 331);
add(1647, 3820, 2, 332);
add(1647, 3818, 2, 333);
add(1645, 3816, 2, 334);
add(1644, 3816, 2, 335);
add(1625, 3802, 2, 336);
add(1625, 3804, 2, 337);
add(1625, 3811, 2, 338);
add(1625, 3812, 2, 339);
add(1627, 3815, 2, 340);
add(1628, 3815, 2, 341);
add(1635, 3815, 2, 342);
add(1637, 3815, 2, 343);
add(1638, 3815, 2, 344);
add(1640, 3813, 2, 345);
add(1640, 3811, 2, 346);
add(1640, 3810, 2, 347);
add(1638, 3800, 2, 348);
add(1632, 3800, 2, 349);
add(1630, 3800, 2, 350);
add(1629, 3800, 2, 351);
add(1627, 3800, 2, 352);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.NpcID;
public enum LibraryCustomer
{
VILLIA(NpcID.VILLIA, "Villia"),
PROFESSOR_GRACKLEBONE(NpcID.PROFESSOR_GRACKLEBONE, "Prof. Gracklebone"),
Sam(NpcID.SAM_7049, "Sam");
@Getter
private final int id;
@Getter
private final String name;
private static final Map<Integer, LibraryCustomer> byId = buildIdMap();
LibraryCustomer(int id, String name)
{
this.id = id;
this.name = name;
}
public static LibraryCustomer getById(int id)
{
return byId.get(id);
}
private static Map<Integer, LibraryCustomer> buildIdMap()
{
Map<Integer, LibraryCustomer> byId = new HashMap<>();
for (LibraryCustomer c : values())
{
byId.put(c.id, c);
}
return byId;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.kourendlibrary;
public enum SolvedState
{
NO_DATA,
INCOMPLETE,
COMPLETE;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB