Hiscore feature expansion (#152)

* Add remaining Hiscore parameters to HiscoreSkill

* Add remaining Hiscore parameters to HiscoreResult

* Add remaining Hiscore parameters to HiscoreResultBuilder

* Add new Hiscore panel icons (from offical Hiscore website, so they don't match very well) and subpanel for Clue Scrolls, Bounty Hunter - Hunter, Bounty Hunter - Rogue, and Last Man Standing

* Add logic to catch unranked hiscores and display them properly. Not currently checking for combat level calculations, but other cases should be covered.

* Make HiscoreService and HiscoreClient aware of different hiscore endpoints

* Add Spring Editor to convert path variable String to enum, add pretty versions of HiscoreEndpoint names, add new icons for endpoint selection

* Fix HiscoreEndpoint.valueof failing silently and preventing lookup, update HiscoreService tests, add Hiscore endpoint selection buttons to HiscorePanel

* Replace HiscorePanel skill icons with smaller versions from the official hiscore website

* Fix details listing rank instead of experience

* Fix details listing rank instead of experience, fix skill panels not being cleared when selecting a different hiscore category, make HiscoreService respond 404  when a Hiscore entry is not found instead of 500.

* Fix skill panels not being cleared when selecting a different hiscore category, make HiscoreService respond 404  when a Hiscore entry is not found instead of 500.

* Revert changing RuneliteAPI base URL, those changes should not have been committed (local testing only)

* Add ClueScrollAll and ClueScrollMaster to HiscoreService tests.

* Style cleanup and relocate NotFoundException to http-service package

* Use relative path for small skill icons

* Move Jagex Hiscore urls from HiscoreService to HiscoreEndpoint

* Create new util package in http-service for common exceptions and Spring converters, clean up HiscoreService by streamlining error handling and removing methods for old unit test

* Change HiscoreService unit test to use new HiscoreTestService subclass which handles setting the test URL

* Change HiscoreEndpoint hiscoreUrls to HttpUrl instead of String

* Cleanup formatting, remove unused http-service exception

* http-api: cleanup HiscoreEndpoint
This commit is contained in:
Julian Nowaczek
2017-09-16 15:28:42 -05:00
committed by Adam
parent 10afba7017
commit b12bd04764
59 changed files with 692 additions and 207 deletions

View File

@@ -40,10 +40,11 @@ public class HiscoreClient
{
private static final Logger logger = LoggerFactory.getLogger(HiscoreClient.class);
public HiscoreResult lookup(String username) throws IOException
public HiscoreResult lookup(String username, HiscoreEndpoint endpoint) throws IOException
{
HttpUrl.Builder builder = RuneliteAPI.getApiBase().newBuilder()
.addPathSegment("hiscore")
.addPathSegment(endpoint.name().toLowerCase())
.addQueryParameter("username", username);
HttpUrl url = builder.build();
@@ -67,10 +68,16 @@ public class HiscoreClient
}
}
public SingleHiscoreSkillResult lookup(String username, HiscoreSkill skill) throws IOException
public HiscoreResult lookup(String username) throws IOException
{
return lookup(username, HiscoreEndpoint.NORMAL);
}
public SingleHiscoreSkillResult lookup(String username, HiscoreSkill skill, HiscoreEndpoint endpoint) throws IOException
{
HttpUrl.Builder builder = RuneliteAPI.getApiBase().newBuilder()
.addPathSegment("hiscore")
.addPathSegment(endpoint.name())
.addPathSegment(skill.toString().toLowerCase())
.addQueryParameter("username", username);
@@ -94,4 +101,9 @@ public class HiscoreClient
throw new IOException(ex);
}
}
public SingleHiscoreSkillResult lookup(String username, HiscoreSkill skill) throws IOException
{
return lookup(username, skill, HiscoreEndpoint.NORMAL);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 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 HOLDER 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.http.api.hiscore;
import okhttp3.HttpUrl;
public enum HiscoreEndpoint
{
NORMAL("Normal", "http://services.runescape.com/m=hiscore_oldschool/index_lite.ws"),
IRONMAN("Ironman", "http://services.runescape.com/m=hiscore_oldschool_ironman/index_lite.ws"),
HARDCORE_IRONMAN("Hardcore Ironman", "http://services.runescape.com/m=hiscore_oldschool_hardcore_ironman/index_lite.ws"),
ULTIMATE_IRONMAN("Ultimate Ironman", "http://services.runescape.com/m=hiscore_oldschool_ultimate/index_lite.ws"),
DEADMAN("Deadman", "http://services.runescape.com/m=hiscore_oldschool_deadman/index_lite.ws"),
SEASONAL_DEADMAN("Seasonal Deadman", "http://services.runescape.com/m=hiscore_oldschool_seasonal/index_lite.ws");
private final String name;
private final HttpUrl hiscoreURL;
HiscoreEndpoint(String name, String hiscoreURL)
{
this.name = name;
this.hiscoreURL = HttpUrl.parse(hiscoreURL);
}
public String getName()
{
return name;
}
public HttpUrl getHiscoreURL()
{
return hiscoreURL;
}
}

View File

@@ -53,6 +53,15 @@ public class HiscoreResult
private Skill runecraft;
private Skill hunter;
private Skill construction;
private Skill clueScrollEasy;
private Skill clueScrollMedium;
private Skill clueScrollAll;
private Skill bountyHunterRogue;
private Skill bountyHunterHunter;
private Skill clueScrollHard;
private Skill lastManStanding;
private Skill clueScrollElite;
private Skill clueScrollMaster;
public String getPlayer()
{
@@ -304,6 +313,96 @@ public class HiscoreResult
this.construction = construction;
}
public Skill getClueScrollEasy()
{
return clueScrollEasy;
}
public void setClueScrollEasy(Skill clueScrollEasy)
{
this.clueScrollEasy = clueScrollEasy;
}
public Skill getClueScrollMedium()
{
return clueScrollMedium;
}
public void setClueScrollMedium(Skill clueScrollMedium)
{
this.clueScrollMedium = clueScrollMedium;
}
public Skill getClueScrollAll()
{
return clueScrollAll;
}
public void setClueScrollAll(Skill clueScrollAll)
{
this.clueScrollAll = clueScrollAll;
}
public Skill getBountyHunterRogue()
{
return bountyHunterRogue;
}
public void setBountyHunterRogue(Skill bountyHunterRogue)
{
this.bountyHunterRogue = bountyHunterRogue;
}
public Skill getBountyHunterHunter()
{
return bountyHunterHunter;
}
public void setBountyHunterHunter(Skill bountyHunterHunter)
{
this.bountyHunterHunter = bountyHunterHunter;
}
public Skill getClueScrollHard()
{
return clueScrollHard;
}
public void setClueScrollHard(Skill clueScrollHard)
{
this.clueScrollHard = clueScrollHard;
}
public Skill getLastManStanding()
{
return lastManStanding;
}
public void setLastManStanding(Skill lastManStanding)
{
this.lastManStanding = lastManStanding;
}
public Skill getClueScrollElite()
{
return clueScrollElite;
}
public void setClueScrollElite(Skill clueScrollElite)
{
this.clueScrollElite = clueScrollElite;
}
public Skill getClueScrollMaster()
{
return clueScrollMaster;
}
public void setClueScrollMaster(Skill clueScrollMaster)
{
this.clueScrollMaster = clueScrollMaster;
}
public Skill getSkill(HiscoreSkill skill)
{
switch (skill)
@@ -356,9 +455,27 @@ public class HiscoreResult
return getConstruction();
case OVERALL:
return getOverall();
case CLUE_SCROLL_EASY:
return getClueScrollEasy();
case CLUE_SCROLL_MEDIUM:
return getClueScrollMedium();
case CLUE_SCROLL_ALL:
return getClueScrollAll();
case BOUNTY_HUNTER_ROGUE:
return getBountyHunterRogue();
case BOUNTY_HUNTER_HUNTER:
return getBountyHunterHunter();
case CLUE_SCROLL_HARD:
return getClueScrollHard();
case LAST_MAN_STANDING:
return getLastManStanding();
case CLUE_SCROLL_ELITE:
return getClueScrollElite();
case CLUE_SCROLL_MASTER:
return getClueScrollMaster();
}
throw new IllegalArgumentException("Invalid skill");
throw new IllegalArgumentException("Invalid hiscore item");
}
@Override
@@ -390,6 +507,15 @@ public class HiscoreResult
hash = 29 * hash + Objects.hashCode(this.runecraft);
hash = 29 * hash + Objects.hashCode(this.hunter);
hash = 29 * hash + Objects.hashCode(this.construction);
hash = 29 * hash + Objects.hashCode(this.clueScrollEasy);
hash = 29 * hash + Objects.hashCode(this.clueScrollMedium);
hash = 29 * hash + Objects.hashCode(this.clueScrollAll);
hash = 29 * hash + Objects.hashCode(this.bountyHunterRogue);
hash = 29 * hash + Objects.hashCode(this.bountyHunterHunter);
hash = 29 * hash + Objects.hashCode(this.clueScrollHard);
hash = 29 * hash + Objects.hashCode(this.lastManStanding);
hash = 29 * hash + Objects.hashCode(this.clueScrollElite);
hash = 29 * hash + Objects.hashCode(this.clueScrollMaster);
return hash;
}
@@ -509,12 +635,48 @@ public class HiscoreResult
{
return false;
}
if (!Objects.equals(this.clueScrollEasy, other.clueScrollEasy))
{
return false;
}
if (!Objects.equals(this.clueScrollMedium, other.clueScrollMedium))
{
return false;
}
if (!Objects.equals(this.clueScrollAll, other.clueScrollAll))
{
return false;
}
if (!Objects.equals(this.bountyHunterRogue, other.bountyHunterRogue))
{
return false;
}
if (!Objects.equals(this.bountyHunterHunter, other.bountyHunterHunter))
{
return false;
}
if (!Objects.equals(this.clueScrollHard, other.clueScrollHard))
{
return false;
}
if (!Objects.equals(this.lastManStanding, other.lastManStanding))
{
return false;
}
if (!Objects.equals(this.clueScrollElite, other.clueScrollElite))
{
return false;
}
if (!Objects.equals(this.clueScrollMaster, other.clueScrollMaster))
{
return false;
}
return true;
}
@Override
public String toString()
{
return "HiscoreResult{" + "player=" + player + ", overall=" + overall + ", attack=" + attack + ", defence=" + defence + ", strength=" + strength + ", hitpoints=" + hitpoints + ", ranged=" + ranged + ", prayer=" + prayer + ", magic=" + magic + ", cooking=" + cooking + ", woodcutting=" + woodcutting + ", fletching=" + fletching + ", fishing=" + fishing + ", firemaking=" + firemaking + ", crafting=" + crafting + ", smithing=" + smithing + ", mining=" + mining + ", herblore=" + herblore + ", agility=" + agility + ", thieving=" + thieving + ", slayer=" + slayer + ", farming=" + farming + ", runecraft=" + runecraft + ", hunter=" + hunter + ", construction=" + construction + '}';
return "HiscoreResult{" + "player=" + player + ", overall=" + overall + ", attack=" + attack + ", defence=" + defence + ", strength=" + strength + ", hitpoints=" + hitpoints + ", ranged=" + ranged + ", prayer=" + prayer + ", magic=" + magic + ", cooking=" + cooking + ", woodcutting=" + woodcutting + ", fletching=" + fletching + ", fishing=" + fishing + ", firemaking=" + firemaking + ", crafting=" + crafting + ", smithing=" + smithing + ", mining=" + mining + ", herblore=" + herblore + ", agility=" + agility + ", thieving=" + thieving + ", slayer=" + slayer + ", farming=" + farming + ", runecraft=" + runecraft + ", hunter=" + hunter + ", construction=" + construction + ", clueScrollEasy=" + clueScrollEasy + ", clueScrollMedium=" + clueScrollMedium + ", clueScrollAll=" + clueScrollAll + ", bountyHunterRogue=" + bountyHunterRogue + ", bountyHunterHunter=" + bountyHunterHunter + ", clueScrollHard=" + clueScrollHard + ", lastManStanding=" + lastManStanding + ", clueScrollElite=" + clueScrollElite + ", clueScrollMaster=" + clueScrollMaster + '}';
}
}

View File

@@ -49,7 +49,16 @@ public enum HiscoreSkill
FARMING("Farming"),
RUNECRAFT("Runecraft"),
HUNTER("Hunter"),
CONSTRUCTION("Construction");
CONSTRUCTION("Construction"),
CLUE_SCROLL_EASY("Clue Scrolls (easy)"),
CLUE_SCROLL_MEDIUM("Clue Scrolls (medium)"),
CLUE_SCROLL_ALL("Clue Scrolls (all)"),
BOUNTY_HUNTER_ROGUE("Bounty Hunter - Rogue"),
BOUNTY_HUNTER_HUNTER("Bounty Hunter - Hunter"),
CLUE_SCROLL_HARD("Clue Scrolls (hard)"),
LAST_MAN_STANDING("Last Man Standing"),
CLUE_SCROLL_ELITE("Clue Scrolls (elite)"),
CLUE_SCROLL_MASTER("Clue Scrolls (master)");
private final String name;

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.http.service;
import java.sql.Timestamp;
import java.time.Instant;
import org.sql2o.converters.Converter;
import org.sql2o.converters.ConverterException;
public class InstantConverter implements Converter<Instant>
{
@Override
public Instant convert(Object val) throws ConverterException
{
Timestamp ts = (Timestamp) val;
return ts.toInstant();
}
@Override
public Object toDatabaseParam(Instant val)
{
return Timestamp.from(val);
}
}

View File

@@ -31,6 +31,8 @@ import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import net.runelite.http.service.util.InstantConverter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

View File

@@ -55,6 +55,7 @@ import net.runelite.cache.fs.jagex.DataFileReadResult;
import net.runelite.http.api.cache.Cache;
import net.runelite.http.api.cache.CacheArchive;
import net.runelite.http.api.cache.CacheIndex;
import net.runelite.http.service.util.exception.NotFoundException;
import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry;
import net.runelite.http.service.cache.beans.FileEntry;

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.http.service.cache;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not found")
public class NotFoundException extends RuntimeException
{
}

View File

@@ -77,6 +77,15 @@ public class HiscoreResultBuilder
hiscoreResult.setRunecraft(skills.get(21));
hiscoreResult.setHunter(skills.get(22));
hiscoreResult.setConstruction(skills.get(23));
hiscoreResult.setClueScrollEasy(skills.get(24));
hiscoreResult.setClueScrollMedium(skills.get(25));
hiscoreResult.setClueScrollAll(skills.get(26));
hiscoreResult.setBountyHunterRogue(skills.get(27));
hiscoreResult.setBountyHunterHunter(skills.get(28));
hiscoreResult.setClueScrollHard(skills.get(29));
hiscoreResult.setLastManStanding(skills.get(30));
hiscoreResult.setClueScrollElite(skills.get(31));
hiscoreResult.setClueScrollMaster(skills.get(32));
return hiscoreResult;
}
}

View File

@@ -26,10 +26,10 @@ package net.runelite.http.service.hiscore;
import java.io.IOException;
import net.runelite.http.api.RuneliteAPI;
import net.runelite.http.api.hiscore.HiscoreResult;
import net.runelite.http.api.hiscore.SingleHiscoreSkillResult;
import net.runelite.http.api.hiscore.Skill;
import net.runelite.http.api.hiscore.HiscoreSkill;
import net.runelite.http.api.hiscore.*;
import net.runelite.http.service.util.HiscoreEndpointEditor;
import net.runelite.http.service.util.exception.InternalServerErrorException;
import net.runelite.http.service.util.exception.NotFoundException;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
@@ -39,10 +39,9 @@ import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/hiscore")
@@ -50,23 +49,36 @@ public class HiscoreService
{
private static final Logger logger = LoggerFactory.getLogger(HiscoreService.class);
private static final HttpUrl RUNESCAPE_HISCORE_SERVICE = HttpUrl.parse("http://services.runescape.com/m=hiscore_oldschool/index_lite.ws");
private HttpUrl url = RUNESCAPE_HISCORE_SERVICE;
private HiscoreResultBuilder lookupUsername(String username) throws IOException
HiscoreResultBuilder lookupUsername(String username, HiscoreEndpoint endpoint) throws IOException
{
HttpUrl hiscoreUrl = url.newBuilder()
.addQueryParameter("player", username)
.build();
return lookupUsername(username, endpoint.getHiscoreURL());
}
logger.info("Built URL {}", hiscoreUrl);
HiscoreResultBuilder lookupUsername(String username, HttpUrl hiscoreUrl) throws IOException
{
HttpUrl url = hiscoreUrl.newBuilder()
.addQueryParameter("player", username)
.build();
logger.info("Built URL {}", url);
Request okrequest = new Request.Builder()
.url(hiscoreUrl)
.build();
.url(url)
.build();
Response okresponse = RuneliteAPI.CLIENT.newCall(okrequest).execute();
if (!okresponse.isSuccessful())
{
switch (HttpStatus.valueOf(okresponse.code()))
{
case NOT_FOUND:
throw new NotFoundException();
default:
throw new InternalServerErrorException("Error retrieving data from Jagex Hiscores: " + okresponse.message());
}
}
String responseStr;
try (ResponseBody body = okresponse.body())
@@ -85,13 +97,20 @@ public class HiscoreService
{
if (count++ >= HiscoreSkill.values().length)
{
logger.warn("Jagex Hiscore API returned unexpected data");
break; // rest is other things?
}
// rank, level, experience
int rank = Integer.parseInt(record.get(0));
int level = Integer.parseInt(record.get(1));
long experience = Long.parseLong(record.get(2));
// items that are not skills do not have an experience parameter
long experience = -1;
if (record.size() == 3)
{
experience = Long.parseLong(record.get(2));
}
Skill skill = new Skill(rank, level, experience);
hiscoreBuilder.setNextSkill(skill);
@@ -100,20 +119,20 @@ public class HiscoreService
return hiscoreBuilder;
}
@RequestMapping
public HiscoreResult lookup(@RequestParam String username) throws IOException
@RequestMapping("/{endpoint}")
public HiscoreResult lookup(@PathVariable HiscoreEndpoint endpoint, @RequestParam String username) throws IOException
{
HiscoreResultBuilder result = lookupUsername(username);
HiscoreResultBuilder result = lookupUsername(username, endpoint);
return result.build();
}
@RequestMapping("/{skillName}")
public SingleHiscoreSkillResult singleSkillLookup(@PathVariable String skillName, @RequestParam String username) throws IOException
@RequestMapping("/{endpoint}/{skillName}")
public SingleHiscoreSkillResult singleSkillLookup(@PathVariable HiscoreEndpoint endpoint, @PathVariable String skillName, @RequestParam String username) throws IOException
{
HiscoreSkill skill = HiscoreSkill.valueOf(skillName.toUpperCase());
// RS api only supports looking up all stats
HiscoreResultBuilder result = lookupUsername(username);
HiscoreResultBuilder result = lookupUsername(username, endpoint);
// Find the skill to return
Skill requested = result.getSkill(skill.ordinal());
@@ -126,13 +145,9 @@ public class HiscoreService
return skillResult;
}
public HttpUrl getUrl()
@InitBinder
public void initBinder(WebDataBinder binder)
{
return url;
}
public void setUrl(HttpUrl url)
{
this.url = url;
binder.registerCustomEditor(HiscoreEndpoint.class, new HiscoreEndpointEditor());
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 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 HOLDER 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.http.service.util;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import java.beans.PropertyEditorSupport;
public class HiscoreEndpointEditor extends PropertyEditorSupport
{
@Override
public void setAsText(String text) throws IllegalArgumentException
{
setValue(HiscoreEndpoint.valueOf(text.toUpperCase()));
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 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 HOLDER 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.http.service.util;
import java.sql.Timestamp;
import java.time.Instant;
import org.sql2o.converters.Converter;
import org.sql2o.converters.ConverterException;
public class InstantConverter implements Converter<Instant>
{
@Override
public Instant convert(Object val) throws ConverterException
{
Timestamp ts = (Timestamp) val;
return ts.toInstant();
}
@Override
public Object toDatabaseParam(Instant val)
{
return Timestamp.from(val);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 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 HOLDER 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.http.service.util.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerErrorException extends RuntimeException
{
public InternalServerErrorException(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 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 HOLDER 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.http.service.util.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Not found")
public class NotFoundException extends RuntimeException
{
}

View File

@@ -28,6 +28,8 @@ import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import javax.naming.NamingException;
import net.runelite.http.service.util.InstantConverter;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.SpringApplication;

View File

@@ -25,6 +25,7 @@
package net.runelite.http.service.hiscore;
import java.io.IOException;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@@ -36,38 +37,38 @@ import org.junit.Test;
public class HiscoreServiceTest
{
private static final String RESPONSE = "654683,705,1304518\n"
+ "679419,50,107181\n"
+ "550667,48,85764\n"
+ "861497,50,101366\n"
+ "891591,48,87843\n"
+ "-1,1,4\n"
+ "840255,27,10073\n"
+ "1371912,10,1310\n"
+ "432193,56,199795\n"
+ "495638,56,198304\n"
+ "514466,37,27502\n"
+ "456981,54,159727\n"
+ "459159,49,93010\n"
+ "1028855,8,823\n"
+ "862906,29,12749\n"
+ "795020,31,16097\n"
+ "673591,5,495\n"
+ "352676,51,112259\n"
+ "428419,40,37235\n"
+ "461887,43,51971\n"
+ "598582,1,10\n"
+ "638177,1,0\n"
+ "516239,9,1000\n"
+ "492790,1,0\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1";
+ "679419,50,107181\n"
+ "550667,48,85764\n"
+ "861497,50,101366\n"
+ "891591,48,87843\n"
+ "-1,1,4\n"
+ "840255,27,10073\n"
+ "1371912,10,1310\n"
+ "432193,56,199795\n"
+ "495638,56,198304\n"
+ "514466,37,27502\n"
+ "456981,54,159727\n"
+ "459159,49,93010\n"
+ "1028855,8,823\n"
+ "862906,29,12749\n"
+ "795020,31,16097\n"
+ "673591,5,495\n"
+ "352676,51,112259\n"
+ "428419,40,37235\n"
+ "461887,43,51971\n"
+ "598582,1,10\n"
+ "638177,1,0\n"
+ "516239,9,1000\n"
+ "492790,1,0\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "531,1432\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "-1,-1\n"
+ "254,92";
private final MockWebServer server = new MockWebServer();
@@ -86,16 +87,17 @@ public class HiscoreServiceTest
}
@Test
public void testLookup() throws Exception
public void testNormalLookup() throws Exception
{
HiscoreService hiscores = new HiscoreService();
hiscores.setUrl(server.url("/"));
HiscoreTestService hiscores = new HiscoreTestService(server.url("/"));
HiscoreResult result = hiscores.lookup("zezima");
HiscoreResult result = hiscores.lookup(HiscoreEndpoint.NORMAL, "zezima");
Assert.assertEquals(50, result.getAttack().getLevel());
Assert.assertEquals(159727L, result.getFishing().getExperience());
Assert.assertEquals(492790, result.getConstruction().getRank());
Assert.assertEquals(1432, result.getClueScrollAll().getLevel());
Assert.assertEquals(254, result.getClueScrollMaster().getRank());
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 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 HOLDER 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.http.service.hiscore;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import okhttp3.HttpUrl;
import java.io.IOException;
class HiscoreTestService extends HiscoreService
{
private HttpUrl testUrl;
HiscoreTestService(HttpUrl testUrl)
{
this.testUrl = testUrl;
}
@Override
HiscoreResultBuilder lookupUsername(String username, HiscoreEndpoint endpoint) throws IOException
{
return super.lookupUsername(username, testUrl);
}
}

View File

@@ -25,38 +25,27 @@
package net.runelite.client.plugins.hiscore;
import com.google.common.base.Strings;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.MouseInputAdapter;
import net.runelite.api.Experience;
import net.runelite.client.RuneLite;
import net.runelite.client.ui.IconTextField;
import net.runelite.client.ui.PluginPanel;
import net.runelite.http.api.hiscore.HiscoreClient;
import net.runelite.http.api.hiscore.HiscoreResult;
import net.runelite.http.api.hiscore.HiscoreSkill;
import net.runelite.http.api.hiscore.*;
import static net.runelite.http.api.hiscore.HiscoreSkill.*;
import net.runelite.http.api.hiscore.Skill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,9 +72,10 @@ public class HiscorePanel extends PluginPanel
private final IconTextField input;
private final List<JLabel> skillLabels = new LinkedList<>();
private final List<JLabel> skillLabels = new ArrayList<>();
private final JPanel statsPanel = new JPanel();
private final ButtonGroup endpointButtonGroup = new ButtonGroup();
private final JTextArea details = new JTextArea();
private final HiscoreClient client = new HiscoreClient();
@@ -134,6 +124,7 @@ public class HiscorePanel extends PluginPanel
input = new IconTextField();
input.setIcon(search);
input.setFont(labelFont.deriveFont(Font.BOLD));
input.addActionListener(e ->
{
ScheduledExecutorService executor = runelite.getExecutor();
@@ -178,6 +169,22 @@ public class HiscorePanel extends PluginPanel
gridBag.setConstraints(totalPanel, c);
add(totalPanel);
JPanel minigamePanel = new JPanel();
minigamePanel.setBorder(subPanelBorder);
// These aren't all on one row because when there's a label with four or more digits it causes the details
// panel to change its size for some reason...
minigamePanel.setLayout(new GridLayout(2, 3));
minigamePanel.add(makeSkillPanel(CLUE_SCROLL_ALL.getName(), CLUE_SCROLL_ALL));
minigamePanel.add(makeSkillPanel(LAST_MAN_STANDING.getName(), LAST_MAN_STANDING));
minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_ROGUE.getName(), BOUNTY_HUNTER_ROGUE));
minigamePanel.add(makeSkillPanel(BOUNTY_HUNTER_HUNTER.getName(), BOUNTY_HUNTER_HUNTER));
c.gridx = 0;
c.gridy = 3;
gridBag.setConstraints(minigamePanel, c);
add(minigamePanel);
JPanel detailsPanel = new JPanel();
detailsPanel.setBorder(subPanelBorder);
detailsPanel.setLayout(new BorderLayout());
@@ -197,16 +204,57 @@ public class HiscorePanel extends PluginPanel
detailsPanel.add(details, BorderLayout.CENTER);
c.gridx = 0;
c.gridy = 3;
// Last item has a nonzero weighty so it will expand to fill vertical space
c.weighty = 1;
c.gridy = 4;
gridBag.setConstraints(detailsPanel, c);
add(detailsPanel);
JPanel endpointPanel = new JPanel();
endpointPanel.setBorder(subPanelBorder);
List<JToggleButton> endpointButtons = new ArrayList<>();
for (HiscoreEndpoint endpoint : HiscoreEndpoint.values())
{
try
{
Icon icon = new ImageIcon(ImageIO.read(HiscorePanel.class.getResourceAsStream(endpoint.name() + ".png")));
Icon selected = new ImageIcon(ImageIO.read(HiscorePanel.class.getResourceAsStream(endpoint.name() + "_selected.png")));
JToggleButton button = new JToggleButton();
button.setIcon(icon);
button.setSelectedIcon(selected);
button.setPreferredSize(new Dimension(24,24));
button.setBackground(Color.WHITE);
button.setFocusPainted(false);
button.setActionCommand(endpoint.name());
button.setToolTipText(endpoint.getName() + " Hiscores");
button.addActionListener((e ->
{
ScheduledExecutorService executor = runelite.getExecutor();
executor.execute(this::lookup);
}));
endpointButtons.add(button);
endpointButtonGroup.add(button);
endpointPanel.add(button);
}
catch (IOException ex)
{
logger.warn(null, ex);
}
}
endpointButtons.get(0).setSelected(true);
c.gridx = 0;
c.gridy = 5;
// Last item has a nonzero weighty so it will expand to fill vertical space
c.weighty = 1;
gridBag.setConstraints(endpointPanel, c);
add(endpointPanel);
}
private void changeDetail(String skillName, HiscoreSkill skill)
{
if (result == null)
if (result == null || result.getPlayer() == null)
{
return;
}
@@ -216,14 +264,6 @@ public class HiscorePanel extends PluginPanel
String text;
switch (skillName)
{
case "Overall":
{
Skill requestedSkill = result.getOverall();
text = "Total Level" + System.lineSeparator()
+ "Rank: " + formatter.format(requestedSkill.getRank()) + System.lineSeparator()
+ "Total Experience: " + formatter.format(requestedSkill.getExperience());
break;
}
case "Combat":
{
double combatLevel = Experience.getCombatLevelPrecise(
@@ -235,19 +275,50 @@ public class HiscorePanel extends PluginPanel
result.getRanged().getLevel(),
result.getPrayer().getLevel()
);
text = "Exact Combat Level: " + formatter.format(combatLevel) + System.lineSeparator()
text = "Skill: Combat" + System.lineSeparator()
+ "Exact Combat Level: " + formatter.format(combatLevel) + System.lineSeparator()
+ "Experience: " + formatter.format(result.getAttack().getExperience()
+ result.getStrength().getExperience() + result.getDefence().getExperience()
+ result.getHitpoints().getExperience() + result.getMagic().getExperience()
+ result.getRanged().getExperience() + result.getPrayer().getExperience());
+ result.getStrength().getExperience() + result.getDefence().getExperience()
+ result.getHitpoints().getExperience() + result.getMagic().getExperience()
+ result.getRanged().getExperience() + result.getPrayer().getExperience());
break;
}
case "Clue Scrolls (all)":
{
String rank = (result.getClueScrollAll().getRank() == -1) ? "Unranked" : formatter.format(result.getClueScrollAll().getRank());
text = "Total Clue Scrolls Completed" + System.lineSeparator()
+ "Rank: " + rank;
break;
}
case "Bounty Hunter - Rogue":
{
String rank = (result.getBountyHunterRogue().getRank() == -1) ? "Unranked" : formatter.format(result.getBountyHunterRogue().getRank());
text = "Bounty Hunter - Rogue Kills" + System.lineSeparator()
+ "Rank: " + rank;
break;
}
case "Bounty Hunter - Hunter":
{
String rank = (result.getBountyHunterHunter().getRank() == -1) ? "Unranked" : formatter.format(result.getBountyHunterHunter().getRank());
text = "Bounty Hunter - Hunter Kills" + System.lineSeparator()
+ "Rank: " + rank;
break;
}
case "Last Man Standing":
{
String rank = (result.getLastManStanding().getRank() == -1) ? "Unranked" : formatter.format(result.getLastManStanding().getRank());
text = "Last Man Standing" + System.lineSeparator()
+ "Rank: " + rank;
break;
}
default:
{
Skill requestedSkill = result.getSkill(skill);
String rank = (requestedSkill.getRank() == -1) ? "Unranked" : formatter.format(requestedSkill.getRank());
String exp = (requestedSkill.getRank() == -1) ? "Unranked" : formatter.format(requestedSkill.getExperience());
text = "Skill: " + skillName + System.lineSeparator()
+ "Rank: " + formatter.format(requestedSkill.getRank()) + System.lineSeparator()
+ "Experience: " + formatter.format(requestedSkill.getExperience());
+ "Rank: " + rank + System.lineSeparator()
+ "Experience: " + exp;
break;
}
}
@@ -265,7 +336,7 @@ public class HiscorePanel extends PluginPanel
label.putClientProperty(SKILL_NAME, skillName);
label.putClientProperty(SKILL, skill);
String skillIcon = "/skill_icons/" + skillName.toLowerCase() + ".png";
String skillIcon = "skill_icons_small/" + skillName.toLowerCase() + ".png";
logger.debug("Loading skill icon from {}", skillIcon);
try
@@ -316,11 +387,14 @@ public class HiscorePanel extends PluginPanel
try
{
result = client.lookup(lookup);
HiscoreEndpoint endpoint = HiscoreEndpoint.valueOf(endpointButtonGroup.getSelection().getActionCommand());
logger.debug("Hiscore endpoint " + endpoint.name() + " selected");
result = client.lookup(lookup, endpoint);
}
catch (IOException ex)
{
logger.warn(null, ex);
logger.warn("Error fetching Hiscore data " + ex.getMessage());
return;
}
@@ -331,18 +405,29 @@ public class HiscorePanel extends PluginPanel
if (skillName.equals("Combat"))
{
int combatLevel = Experience.getCombatLevel(
result.getAttack().getLevel(),
result.getStrength().getLevel(),
result.getDefence().getLevel(),
result.getHitpoints().getLevel(),
result.getMagic().getLevel(),
result.getRanged().getLevel(),
result.getPrayer().getLevel()
);
label.setText(Integer.toString(combatLevel));
if (result.getPlayer() != null)
{
int combatLevel = Experience.getCombatLevel(
result.getAttack().getLevel(),
result.getStrength().getLevel(),
result.getDefence().getLevel(),
result.getHitpoints().getLevel(),
result.getMagic().getLevel(),
result.getRanged().getLevel(),
result.getPrayer().getLevel()
);
label.setText(Integer.toString(combatLevel));
}
else
{
label.setText("--");
}
}
else if (skill != null)
else if (result.getSkill(skill) == null)
{
label.setText("--");
}
else if (result.getSkill(skill) != null && result.getSkill(skill).getRank() != -1)
{
label.setText(Integer.toString(result.getSkill(skill).getLevel()));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B