api: develop

This commit is contained in:
therealunull
2020-12-13 18:41:14 -05:00
parent 06fd8e4802
commit 77ea6c6154
60 changed files with 7706 additions and 10 deletions

View File

@@ -48,7 +48,7 @@ public class MenuEntry implements Cloneable
/** /**
* An identifier value for the target of the action. * An identifier value for the target of the action.
*/ */
private int type; private int identifier;
/** /**
* The action the entry will trigger. * The action the entry will trigger.
* {@link MenuAction} * {@link MenuAction}
@@ -74,7 +74,7 @@ public class MenuEntry implements Cloneable
{ {
this.option = option; this.option = option;
this.target = target; this.target = target;
this.type = type; this.identifier = type;
this.opcode = opcode; this.opcode = opcode;
this.actionParam0 = actionParam0; this.actionParam0 = actionParam0;
this.actionParam1 = actionParam1; this.actionParam1 = actionParam1;
@@ -104,6 +104,16 @@ public class MenuEntry implements Cloneable
this.actionParam1 = i; this.actionParam1 = i;
} }
public void setType(int i)
{
this.opcode = i;
}
public int getType()
{
return this.opcode;
}
/** /**
* Get opcode, but as it's enum counterpart * Get opcode, but as it's enum counterpart
*/ */

View File

@@ -88,10 +88,25 @@ public class MenuOptionClicked extends MenuEntry implements Event
{ {
setOption(e.getOption()); setOption(e.getOption());
setTarget(e.getTarget()); setTarget(e.getTarget());
setType(e.getType()); setIdentifier(e.getIdentifier());
setOpcode(e.getOpcode()); setOpcode(e.getOpcode());
setActionParam0(e.getActionParam0()); setActionParam0(e.getActionParam0());
setActionParam1(e.getActionParam1()); setActionParam1(e.getActionParam1());
setForceLeftClick(e.isForceLeftClick()); setForceLeftClick(e.isForceLeftClick());
} }
public int getWidgetId()
{
return getActionParam1();
}
public String getMenuTarget()
{
return getTarget();
}
public String getMenuOption()
{
return getOption();
}
} }

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2019 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.achievementdiary;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Client;
@RequiredArgsConstructor
@Getter
public class CombatLevelRequirement implements Requirement
{
private final int level;
@Override
public String toString()
{
return level + " " + "Combat";
}
@Override
public boolean satisfiesRequirement(Client client)
{
return client.getLocalPlayer() != null && client.getLocalPlayer().getCombatLevel() >= level;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary;
import com.google.common.collect.ImmutableList;
import java.util.List;
import lombok.Getter;
@Getter
class DiaryRequirement
{
private final String task;
private final List<Requirement> requirements;
DiaryRequirement(String task, Requirement[] requirements)
{
this.task = task;
this.requirements = ImmutableList.copyOf(requirements);
}
}

View File

@@ -0,0 +1,293 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.FontTypeFace;
import net.runelite.api.ScriptID;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.achievementdiary.diaries.ArdougneDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.DesertDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.FaladorDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.FremennikDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.KandarinDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.KaramjaDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.KourendDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.LumbridgeDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.MorytaniaDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.VarrockDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.WesternDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.diaries.WildernessDiaryRequirement;
import net.runelite.client.util.Text;
@Slf4j
@PluginDescriptor(
name = "Diary Requirements",
description = "Display level requirements in Achievement Diary interface",
tags = {"achievements", "tasks"}
)
public class DiaryRequirementsPlugin extends Plugin
{
private static final String AND_JOINER = ", ";
private static final Pattern AND_JOINER_PATTERN = Pattern.compile("(?<=, )");
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Subscribe
public void onWidgetLoaded(final WidgetLoaded event)
{
if (event.getGroupId() == WidgetID.DIARY_QUEST_GROUP_ID)
{
String widgetTitle = Text.removeTags(
client.getWidget(
WidgetInfo.DIARY_QUEST_WIDGET_TITLE)
.getText())
.replace(' ', '_')
.toUpperCase();
if (widgetTitle.startsWith("ACHIEVEMENT_DIARY"))
{
showDiaryRequirements();
}
}
}
private void showDiaryRequirements()
{
Widget widget = client.getWidget(WidgetInfo.DIARY_QUEST_WIDGET_TEXT);
Widget[] children = widget.getStaticChildren();
Widget titleWidget = children[0];
if (titleWidget == null)
{
return;
}
FontTypeFace font = titleWidget.getFont();
int maxWidth = titleWidget.getWidth();
List<String> originalAchievements = getOriginalAchievements(children);
// new requirements starts out as a copy of the original
List<String> newRequirements = new ArrayList<>(originalAchievements);
GenericDiaryRequirement requirements = getRequirementsForTitle(titleWidget.getText());
if (requirements == null)
{
log.debug("Unknown achievement diary {}", titleWidget.getText());
return;
}
Map<String, String> skillRequirements = buildRequirements(requirements.getRequirements());
if (skillRequirements == null)
{
return;
}
int offset = 0;
String taskBuffer = "";
for (int i = 0; i < originalAchievements.size(); i++)
{
String rowText = Text.removeTags(originalAchievements.get(i));
if (skillRequirements.get(taskBuffer + " " + rowText) != null)
{
taskBuffer = taskBuffer + " " + rowText;
}
else
{
taskBuffer = rowText;
}
if (skillRequirements.get(taskBuffer) != null)
{
String levelRequirement = skillRequirements.get(taskBuffer);
String task = originalAchievements.get(i);
int taskWidth = font.getTextWidth(task);
int ourWidth = font.getTextWidth(levelRequirement);
String strike = task.startsWith("<str>") ? "<str>" : "";
if (ourWidth + taskWidth < maxWidth)
{
// Merge onto 1 line
newRequirements.set(i + offset, task + levelRequirement);
}
else if (ourWidth < maxWidth)
{
// 2 line split
newRequirements.add(i + (++offset), strike + levelRequirement);
}
else
{
// Full text layout
StringBuilder b = new StringBuilder();
b.append(task);
int runningWidth = font.getTextWidth(b.toString());
for (String word : AND_JOINER_PATTERN.split(levelRequirement))
{
int wordWidth = font.getTextWidth(word);
if (runningWidth == 0 || wordWidth + runningWidth < maxWidth)
{
runningWidth += wordWidth;
b.append(word);
}
else
{
newRequirements.add(i + (offset++), b.toString());
b.delete(0, b.length());
runningWidth = wordWidth;
b.append(strike);
b.append(word);
}
}
newRequirements.set(i + offset, b.toString());
}
}
}
int lastLine = 0;
for (int i = 0; i < newRequirements.size() && i < children.length; i++)
{
Widget achievementWidget = children[i];
String text = newRequirements.get(i);
achievementWidget.setText(text);
if (text != null && !text.isEmpty())
{
lastLine = i;
}
}
int numLines = lastLine;
clientThread.invokeLater(() -> client.runScript(ScriptID.DIARY_QUEST_UPDATE_LINECOUNT, 1, numLines));
}
private List<String> getOriginalAchievements(Widget[] children)
{
List<String> preloadedRequirements = new ArrayList<>(children.length);
for (Widget requirementWidget : children)
{
preloadedRequirements.add(requirementWidget.getText());
}
return preloadedRequirements;
}
private GenericDiaryRequirement getRequirementsForTitle(String title)
{
String diaryName = Text.removeTags(title
.replaceAll(" ", "_")
.toUpperCase());
GenericDiaryRequirement diaryRequirementContainer;
switch (diaryName)
{
case "ARDOUGNE_AREA_TASKS":
diaryRequirementContainer = new ArdougneDiaryRequirement();
break;
case "DESERT_TASKS":
diaryRequirementContainer = new DesertDiaryRequirement();
break;
case "FALADOR_AREA_TASKS":
diaryRequirementContainer = new FaladorDiaryRequirement();
break;
case "FREMENNIK_TASKS":
diaryRequirementContainer = new FremennikDiaryRequirement();
break;
case "KANDARIN_TASKS":
diaryRequirementContainer = new KandarinDiaryRequirement();
break;
case "KARAMJA_AREA_TASKS":
diaryRequirementContainer = new KaramjaDiaryRequirement();
break;
case "KOUREND_&_KEBOS_TASKS":
diaryRequirementContainer = new KourendDiaryRequirement();
break;
case "LUMBRIDGE_&_DRAYNOR_TASKS":
diaryRequirementContainer = new LumbridgeDiaryRequirement();
break;
case "MORYTANIA_TASKS":
diaryRequirementContainer = new MorytaniaDiaryRequirement();
break;
case "VARROCK_TASKS":
diaryRequirementContainer = new VarrockDiaryRequirement();
break;
case "WESTERN_AREA_TASKS":
diaryRequirementContainer = new WesternDiaryRequirement();
break;
case "WILDERNESS_AREA_TASKS":
diaryRequirementContainer = new WildernessDiaryRequirement();
break;
default:
return null;
}
return diaryRequirementContainer;
}
// returns a map of task -> level requirements
private Map<String, String> buildRequirements(Collection<DiaryRequirement> requirements)
{
Map<String, String> reqs = new HashMap<>();
for (DiaryRequirement req : requirements)
{
StringBuilder b = new StringBuilder();
b.append("<col=ffffff>(");
assert !req.getRequirements().isEmpty();
for (Requirement ireq : req.getRequirements())
{
boolean satifisfied = ireq.satisfiesRequirement(client);
b.append(satifisfied ? "<col=000080><str>" : "<col=800000>");
b.append(ireq.toString());
b.append(satifisfied ? "</str>" : "<col=000080>");
b.append(AND_JOINER);
}
b.delete(b.length() - AND_JOINER.length(), b.length());
b.append("<col=ffffff>)");
reqs.put(req.getTask(), b.toString());
}
return reqs;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2019 William <https://github.com/monsterxsync>
* 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.achievementdiary;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Client;
import net.runelite.api.Favour;
@RequiredArgsConstructor
@Getter
public class FavourRequirement implements Requirement
{
private final Favour house;
private final int percent;
@Override
public String toString()
{
return percent + "% " + house.getName() + " favour";
}
@Override
public boolean satisfiesRequirement(Client client)
{
int realFavour = client.getVar(house.getVarbit());
return (realFavour / 10) >= percent;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
public abstract class GenericDiaryRequirement
{
@Getter
private Set<DiaryRequirement> requirements = new HashSet<>();
protected void add(String task, Requirement... requirements)
{
DiaryRequirement diaryRequirement = new DiaryRequirement(task, requirements);
this.requirements.add(diaryRequirement);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2019 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.achievementdiary;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import java.util.List;
import lombok.Getter;
import net.runelite.api.Client;
public class OrRequirement implements Requirement
{
@Getter
private final List<Requirement> requirements;
public OrRequirement(Requirement... reqs)
{
this.requirements = ImmutableList.copyOf(reqs);
}
@Override
public String toString()
{
return Joiner.on(" or ").join(requirements);
}
@Override
public boolean satisfiesRequirement(Client client)
{
for (Requirement r : getRequirements())
{
if (r.satisfiesRequirement(client))
{
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2019 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.achievementdiary;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Client;
import net.runelite.api.VarPlayer;
@RequiredArgsConstructor
@Getter
public class QuestPointRequirement implements Requirement
{
private final int qp;
@Override
public String toString()
{
return qp + " " + "Quest points";
}
@Override
public boolean satisfiesRequirement(Client client)
{
return client.getVar(VarPlayer.QUEST_POINTS) >= qp;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2019 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.achievementdiary;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Client;
import net.runelite.api.Quest;
import net.runelite.api.QuestState;
@Getter
@RequiredArgsConstructor
public class QuestRequirement implements Requirement
{
private final Quest quest;
private final boolean started;
public QuestRequirement(Quest quest)
{
this(quest, false);
}
@Override
public String toString()
{
if (started)
{
return "Started " + quest.getName();
}
return quest.getName();
}
@Override
public boolean satisfiesRequirement(Client client)
{
QuestState questState = quest.getState(client);
if (started)
{
return questState != QuestState.NOT_STARTED;
}
return questState == QuestState.FINISHED;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2019 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.achievementdiary;
import net.runelite.api.Client;
public interface Requirement
{
boolean satisfiesRequirement(Client client);
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2019 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.achievementdiary;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Client;
import net.runelite.api.Skill;
@RequiredArgsConstructor
@Getter
public class SkillRequirement implements Requirement
{
private final Skill skill;
private final int level;
@Override
public String toString()
{
return level + " " + skill.getName();
}
@Override
public boolean satisfiesRequirement(Client client)
{
return client.getRealSkillLevel(skill) >= level;
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class ArdougneDiaryRequirement extends GenericDiaryRequirement
{
public ArdougneDiaryRequirement()
{
// EASY
add("Have Wizard Cromperty teleport you to the Rune Essence mine.",
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Steal a cake from the Ardougne market stalls.",
new SkillRequirement(Skill.THIEVING, 5));
add("Enter the Combat Training Camp north of W. Ardougne.",
new QuestRequirement(Quest.BIOHAZARD));
add("Go out fishing on the Fishing Trawler.",
new SkillRequirement(Skill.FISHING, 15));
// MEDIUM
add("Enter the Unicorn pen in Ardougne zoo using Fairy rings.",
new QuestRequirement(Quest.FAIRYTALE_II__CURE_A_QUEEN, true));
add("Grapple over Yanille's south wall.",
new SkillRequirement(Skill.AGILITY, 39),
new SkillRequirement(Skill.STRENGTH, 38),
new SkillRequirement(Skill.RANGED, 21));
add("Harvest some strawberries from the Ardougne farming patch.",
new SkillRequirement(Skill.FARMING, 31));
add("Cast the Ardougne Teleport spell.",
new SkillRequirement(Skill.MAGIC, 51),
new QuestRequirement(Quest.PLAGUE_CITY));
add("Travel to Castlewars by Hot Air Balloon.",
new SkillRequirement(Skill.FIREMAKING, 50),
new QuestRequirement(Quest.ENLIGHTENED_JOURNEY));
add("Claim buckets of sand from Bert in Yanille.",
new SkillRequirement(Skill.CRAFTING, 49),
new QuestRequirement(Quest.THE_HAND_IN_THE_SAND));
add("Catch any fish on the Fishing Platform.",
new QuestRequirement(Quest.SEA_SLUG, true));
add("Pickpocket the master farmer north of Ardougne.",
new SkillRequirement(Skill.THIEVING, 38));
add("Collect some Nightshade from the Skavid Caves.",
new QuestRequirement(Quest.WATCHTOWER, true));
add("Kill a swordchick in the Tower of Life.",
new QuestRequirement(Quest.TOWER_OF_LIFE));
add("Equip Iban's upgraded staff or upgrade an Iban staff.",
new SkillRequirement(Skill.MAGIC, 50),
new SkillRequirement(Skill.ATTACK, 50),
new QuestRequirement(Quest.UNDERGROUND_PASS));
add("Visit the Island East of the Necromancer's tower.",
new QuestRequirement(Quest.FAIRYTALE_II__CURE_A_QUEEN, true));
// HARD
// When the task is completed "the Totem" changes to "Totem" - so we add
// both variations.
add("Recharge some Jewellery at the Totem in the Legends Guild.",
new QuestRequirement(Quest.LEGENDS_QUEST));
add("Recharge some Jewellery at Totem in the Legends Guild.",
new QuestRequirement(Quest.LEGENDS_QUEST));
add("Enter the Magic Guild.",
new SkillRequirement(Skill.MAGIC, 66));
add("Attempt to steal from a chest in Ardougne Castle.",
new SkillRequirement(Skill.THIEVING, 72));
add("Have a zookeeper put you in Ardougne Zoo's monkey cage.",
new QuestRequirement(Quest.MONKEY_MADNESS_I, true));
add("Teleport to the Watchtower.",
new SkillRequirement(Skill.MAGIC, 58),
new QuestRequirement(Quest.WATCHTOWER));
add("Catch a Red Salamander.",
new SkillRequirement(Skill.HUNTER, 59));
add("Check the health of a Palm tree near tree gnome village.",
new SkillRequirement(Skill.FARMING, 68));
add("Pick some Poison Ivy berries from the patch south of Ardougne.",
new SkillRequirement(Skill.FARMING, 70));
add("Smith a Mithril platebody near Ardougne.",
new SkillRequirement(Skill.SMITHING, 68));
add("Enter your POH from Yanille.",
new SkillRequirement(Skill.CONSTRUCTION, 50));
add("Smith a Dragon sq shield in West Ardougne.",
new SkillRequirement(Skill.SMITHING, 60),
new QuestRequirement(Quest.LEGENDS_QUEST));
add("Craft some Death runes.",
new SkillRequirement(Skill.RUNECRAFT, 65),
new QuestRequirement(Quest.MOURNINGS_END_PART_II));
// ELITE
add("Catch a Manta ray in the Fishing Trawler and cook it in Port Khazard.",
new SkillRequirement(Skill.FISHING, 81),
new SkillRequirement(Skill.COOKING, 91)
);
add("Attempt to picklock the door to the basement of Yanille Agility Dungeon.",
new SkillRequirement(Skill.THIEVING, 82));
add("Pickpocket a Hero.",
new SkillRequirement(Skill.THIEVING, 80));
add("Make a rune crossbow yourself from scratch within Witchaven or Yanille.",
new SkillRequirement(Skill.CRAFTING, 10),
new SkillRequirement(Skill.SMITHING, 91),
new SkillRequirement(Skill.FLETCHING, 69));
add("Imbue a salve amulet at Nightmare Zone or equip an imbued salve amulet.",
new QuestRequirement(Quest.HAUNTED_MINE));
add("Pick some Torstol from the patch north of Ardougne.",
new SkillRequirement(Skill.FARMING, 85));
add("Complete a lap of Ardougne's rooftop agility course.",
new SkillRequirement(Skill.AGILITY, 90));
add("Cast Ice Barrage on another player within Castlewars.",
new SkillRequirement(Skill.MAGIC, 94),
new QuestRequirement(Quest.DESERT_TREASURE));
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class DesertDiaryRequirement extends GenericDiaryRequirement
{
public DesertDiaryRequirement()
{
// EASY
add("Catch a Golden Warbler.",
new SkillRequirement(Skill.HUNTER, 5));
add("Mine 5 clay in the north-eastern desert.",
new SkillRequirement(Skill.MINING, 5));
add("Open the Sarcophagus in the first room of Pyramid Plunder.",
new SkillRequirement(Skill.THIEVING, 21),
new QuestRequirement(Quest.ICTHLARINS_LITTLE_HELPER, true));
// MEDIUM
add("Climb to the summit of the Agility Pyramid.",
new SkillRequirement(Skill.AGILITY, 30));
add("Slay a desert lizard.",
new SkillRequirement(Skill.SLAYER, 22));
add("Catch an Orange Salamander.",
new SkillRequirement(Skill.HUNTER, 47));
add("Steal a feather from the Desert Phoenix.",
new SkillRequirement(Skill.THIEVING, 25));
add("Travel to Uzer via Magic Carpet.",
new QuestRequirement(Quest.THE_GOLEM));
add("Travel to the Desert via Eagle.",
new QuestRequirement(Quest.EAGLES_PEAK));
add("Pray at the Elidinis statuette in Nardah.",
new QuestRequirement(Quest.SPIRITS_OF_THE_ELID));
add("Create a combat potion in the desert.",
new SkillRequirement(Skill.HERBLORE, 36));
add("Teleport to Enakhra's Temple with the Camulet.",
new QuestRequirement(Quest.ENAKHRAS_LAMENT));
add("Visit the Genie.",
new QuestRequirement(Quest.SPIRITS_OF_THE_ELID));
add("Teleport to Pollnivneach with a redirected teleport to house tablet.",
new SkillRequirement(Skill.CONSTRUCTION, 20));
add("Chop some Teak logs near Uzer.",
new SkillRequirement(Skill.WOODCUTTING, 35));
// HARD
add("Knock out and pickpocket a Menaphite Thug.",
new SkillRequirement(Skill.THIEVING, 65),
new QuestRequirement(Quest.THE_FEUD));
add("Mine some Granite.",
new SkillRequirement(Skill.MINING, 45));
add("Refill your waterskins in the Desert using Lunar magic.",
new SkillRequirement(Skill.MAGIC, 68),
new QuestRequirement(Quest.DREAM_MENTOR));
add("Complete a lap of the Pollnivneach agility course.",
new SkillRequirement(Skill.AGILITY, 70));
add("Slay a Dust Devil with a Slayer helmet equipped.",
new SkillRequirement(Skill.SLAYER, 65),
new SkillRequirement(Skill.DEFENCE, 10),
new SkillRequirement(Skill.CRAFTING, 55),
new QuestRequirement(Quest.DESERT_TREASURE, true));
add("Activate Ancient Magicks at the altar in the Jaldraocht Pyramid.",
new QuestRequirement(Quest.DESERT_TREASURE));
add("Defeat a Locust Rider with Keris.",
new SkillRequirement(Skill.ATTACK, 50),
new QuestRequirement(Quest.CONTACT));
add("Burn some yew logs on the Nardah Mayor's balcony.",
new SkillRequirement(Skill.FIREMAKING, 60));
add("Create a Mithril Platebody in Nardah.",
new SkillRequirement(Skill.SMITHING, 68));
// ELITE
add("Bake a wild pie at the Nardah Clay Oven.",
new SkillRequirement(Skill.COOKING, 85));
add("Cast Ice Barrage against a foe in the Desert.",
new SkillRequirement(Skill.MAGIC, 94),
new QuestRequirement(Quest.DESERT_TREASURE));
add("Fletch some Dragon darts at the Bedabin Camp.",
new SkillRequirement(Skill.FLETCHING, 95),
new QuestRequirement(Quest.THE_TOURIST_TRAP));
add("Speak to the KQ head in your POH.",
new SkillRequirement(Skill.CONSTRUCTION, 78),
new QuestRequirement(Quest.PRIEST_IN_PERIL));
add("Steal from the Grand Gold Chest in the final room of Pyramid Plunder.",
new SkillRequirement(Skill.THIEVING, 91),
new QuestRequirement(Quest.ICTHLARINS_LITTLE_HELPER, true));
add("Restore at least 85 Prayer points when praying at the Altar in Sophanem.",
new SkillRequirement(Skill.PRAYER, 85),
new QuestRequirement(Quest.ICTHLARINS_LITTLE_HELPER, true));
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class FaladorDiaryRequirement extends GenericDiaryRequirement
{
public FaladorDiaryRequirement()
{
// EASY
add("Find out what your family crest is from Sir Renitee.",
new SkillRequirement(Skill.CONSTRUCTION, 16));
add("Climb over the western Falador wall.",
new SkillRequirement(Skill.AGILITY, 5));
add("Make a mind tiara.",
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Smith some Blurite Limbs on Doric's Anvil.",
new SkillRequirement(Skill.MINING, 10),
new SkillRequirement(Skill.SMITHING, 13),
new QuestRequirement(Quest.THE_KNIGHTS_SWORD),
new QuestRequirement(Quest.DORICS_QUEST));
// MEDIUM
add("Light a Bullseye lantern at the Chemist's in Rimmington.",
new SkillRequirement(Skill.FIREMAKING, 49));
add("Telegrab some Wine of Zamorak at the Chaos Temple by the Wilderness.",
new SkillRequirement(Skill.MAGIC, 33));
add("Place a Scarecrow in the Falador farming patch.",
new SkillRequirement(Skill.FARMING, 23));
add("Kill a Mogre at Mudskipper Point.",
new SkillRequirement(Skill.SLAYER, 32),
new QuestRequirement(Quest.SKIPPY_AND_THE_MOGRES));
add("Visit the Port Sarim Rat Pits.",
new QuestRequirement(Quest.RATCATCHERS, true));
add("Grapple up and then jump off the north Falador wall.",
new SkillRequirement(Skill.AGILITY, 11),
new SkillRequirement(Skill.STRENGTH, 37),
new SkillRequirement(Skill.RANGED, 19));
add("Pickpocket a Falador guard.",
new SkillRequirement(Skill.THIEVING, 40));
add("Pray at the Altar of Guthix in Taverley whilst wearing full Initiate.",
new SkillRequirement(Skill.PRAYER, 10),
new SkillRequirement(Skill.DEFENCE, 20),
new QuestRequirement(Quest.RECRUITMENT_DRIVE));
add("Mine some Gold ore at the Crafting Guild.",
new SkillRequirement(Skill.CRAFTING, 40),
new SkillRequirement(Skill.MINING, 40));
add("Squeeze through the crevice in the Dwarven mines.",
new SkillRequirement(Skill.AGILITY, 42));
add("Chop and burn some Willow logs in Taverley",
new SkillRequirement(Skill.WOODCUTTING, 30),
new SkillRequirement(Skill.FIREMAKING, 30));
add("Craft a fruit basket on the Falador Farm loom.",
new SkillRequirement(Skill.CRAFTING, 36));
add("Teleport to Falador.",
new SkillRequirement(Skill.MAGIC, 37));
// HARD
add("Craft 140 Mind runes simultaneously.",
new SkillRequirement(Skill.RUNECRAFT, 56));
add("Change your family crest to the Saradomin symbol.",
new SkillRequirement(Skill.PRAYER, 70));
add("Kill a Skeletal Wyvern in the Asgarnia Ice Dungeon.",
new SkillRequirement(Skill.SLAYER, 72));
add("Complete a lap of the Falador rooftop agility course.",
new SkillRequirement(Skill.AGILITY, 50));
add("Enter the mining guild wearing full prospector.",
new SkillRequirement(Skill.MINING, 60));
add("Kill the Blue Dragon under the Heroes' Guild.",
new QuestRequirement(Quest.HEROES_QUEST));
add("Crack a wall safe within Rogues Den.",
new SkillRequirement(Skill.THIEVING, 50));
add("Recharge your prayer in the Port Sarim church while wearing full Proselyte.",
new SkillRequirement(Skill.DEFENCE, 30),
new QuestRequirement(Quest.THE_SLUG_MENACE));
add("Equip a dwarven helmet within the dwarven mines.",
new SkillRequirement(Skill.DEFENCE, 50),
new QuestRequirement(Quest.GRIM_TALES));
// ELITE
add("Craft 252 Air Runes simultaneously.",
new SkillRequirement(Skill.RUNECRAFT, 88));
add("Purchase a White 2h Sword from Sir Vyvin.",
new QuestRequirement(Quest.WANTED));
add("Find at least 3 magic roots at once when digging up your magic tree in Falador.",
new SkillRequirement(Skill.FARMING, 91),
new SkillRequirement(Skill.WOODCUTTING, 75));
add("Jump over the strange floor in Taverley dungeon.",
new SkillRequirement(Skill.AGILITY, 80));
add("Mix a Saradomin brew in Falador east bank.",
new SkillRequirement(Skill.HERBLORE, 81));
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class FremennikDiaryRequirement extends GenericDiaryRequirement
{
public FremennikDiaryRequirement()
{
// EASY
add("Catch a Cerulean twitch.",
new SkillRequirement(Skill.HUNTER, 11));
add("Change your boots at Yrsa's Shoe Store.",
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS));
add("Craft a tiara from scratch in Rellekka.",
new SkillRequirement(Skill.CRAFTING, 23),
new SkillRequirement(Skill.MINING, 20),
new SkillRequirement(Skill.SMITHING, 20),
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS));
add("Browse the Stonemasons shop.",
new QuestRequirement(Quest.THE_GIANT_DWARF, true));
add("Steal from the Keldagrim crafting or baker's stall.",
new SkillRequirement(Skill.THIEVING, 5),
new QuestRequirement(Quest.THE_GIANT_DWARF, true));
add("Enter the Troll Stronghold.",
new QuestRequirement(Quest.DEATH_PLATEAU),
new QuestRequirement(Quest.TROLL_STRONGHOLD, true));
add("Chop and burn some oak logs in the Fremennik Province.",
new SkillRequirement(Skill.WOODCUTTING, 15),
new SkillRequirement(Skill.FIREMAKING, 15));
// MEDIUM
add("Slay a Brine rat.",
new SkillRequirement(Skill.SLAYER, 47),
new QuestRequirement(Quest.OLAFS_QUEST, true));
add("Travel to the Snowy Hunter Area via Eagle.",
new QuestRequirement(Quest.EAGLES_PEAK));
add("Mine some coal in Rellekka.",
new SkillRequirement(Skill.MINING, 30),
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS));
add("Steal from the Rellekka Fish stalls.",
new SkillRequirement(Skill.THIEVING, 42),
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS));
add("Travel to Miscellania by Fairy ring.",
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS),
new QuestRequirement(Quest.FAIRYTALE_II__CURE_A_QUEEN, true));
add("Catch a Snowy knight.",
new SkillRequirement(Skill.HUNTER, 35));
add("Pick up your Pet Rock from your POH Menagerie.",
new SkillRequirement(Skill.CONSTRUCTION, 37),
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS));
add("Visit the Lighthouse from Waterbirth island.",
new QuestRequirement(Quest.HORROR_FROM_THE_DEEP),
new QuestRequirement(Quest.THE_FREMENNIK_TRIALS, true));
add("Mine some gold at the Arzinian mine.",
new SkillRequirement(Skill.MINING, 40),
new QuestRequirement(Quest.BETWEEN_A_ROCK, true));
// HARD
add("Teleport to Trollheim.",
new SkillRequirement(Skill.MAGIC, 61),
new QuestRequirement(Quest.EADGARS_RUSE));
add("Catch a Sabre-toothed Kyatt.",
new SkillRequirement(Skill.HUNTER, 55));
add("Mix a super defence potion in the Fremennik province.",
new SkillRequirement(Skill.HERBLORE, 66));
add("Steal from the Keldagrim Gem Stall.",
new SkillRequirement(Skill.THIEVING, 75),
new QuestRequirement(Quest.THE_GIANT_DWARF, true));
add("Craft a Fremennik shield on Neitiznot.",
new SkillRequirement(Skill.WOODCUTTING, 56),
new QuestRequirement(Quest.THE_FREMENNIK_ISLES));
add("Mine 5 Adamantite ores on Jatizso.",
new SkillRequirement(Skill.MINING, 70),
new QuestRequirement(Quest.THE_FREMENNIK_ISLES));
add("Obtain 100% support from your kingdom subjects.",
new QuestRequirement(Quest.THRONE_OF_MISCELLANIA));
add("Teleport to Waterbirth Island.",
new SkillRequirement(Skill.MAGIC, 72),
new QuestRequirement(Quest.LUNAR_DIPLOMACY));
add("Obtain the Blast Furnace Foreman's permission to use the Blast Furnace for free.",
new SkillRequirement(Skill.SMITHING, 60),
new QuestRequirement(Quest.THE_GIANT_DWARF, true));
// ELITE
add("Craft 56 astral runes at once.",
new SkillRequirement(Skill.RUNECRAFT, 82),
new QuestRequirement(Quest.LUNAR_DIPLOMACY));
add("Create a dragonstone amulet in the Neitiznot furnace.",
new SkillRequirement(Skill.CRAFTING, 80),
new QuestRequirement(Quest.THE_FREMENNIK_ISLES, true));
add("Complete a lap of the Rellekka agility course.",
new SkillRequirement(Skill.AGILITY, 80));
add("Kill each of the Godwars generals.",
new SkillRequirement(Skill.AGILITY, 70),
new SkillRequirement(Skill.STRENGTH, 70),
new SkillRequirement(Skill.HITPOINTS, 70),
new SkillRequirement(Skill.RANGED, 70),
new QuestRequirement(Quest.TROLL_STRONGHOLD));
add("Slay a Spiritual mage within the Godwars Dungeon.",
new SkillRequirement(Skill.SLAYER, 83),
new QuestRequirement(Quest.TROLL_STRONGHOLD));
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class KandarinDiaryRequirement extends GenericDiaryRequirement
{
public KandarinDiaryRequirement()
{
// EASY
add("Catch a Mackerel at Catherby.",
new SkillRequirement(Skill.FISHING, 16));
add("Plant some Jute seeds in the patch north of McGrubor's Wood.",
new SkillRequirement(Skill.FARMING, 13));
add("Defeat one of each elemental in the workshop.",
new QuestRequirement(Quest.ELEMENTAL_WORKSHOP_I, true));
add("Cross the Coal truck log shortcut.",
new SkillRequirement(Skill.AGILITY, 20));
// MEDIUM
add("Complete a lap of the Barbarian agility course.",
new SkillRequirement(Skill.AGILITY, 35),
new QuestRequirement(Quest.ALFRED_GRIMHANDS_BARCRAWL));
add("Create a Super Antipoison potion from scratch in the Seers/Catherby Area.",
new SkillRequirement(Skill.HERBLORE, 48));
add("Enter the Ranging guild.",
new SkillRequirement(Skill.RANGED, 40));
add("Use the grapple shortcut to get from the water obelisk to Catherby shore.",
new SkillRequirement(Skill.AGILITY, 36),
new SkillRequirement(Skill.STRENGTH, 22),
new SkillRequirement(Skill.RANGED, 39));
add("Catch and cook a Bass in Catherby.",
new SkillRequirement(Skill.FISHING, 46),
new SkillRequirement(Skill.COOKING, 43));
add("Teleport to Camelot.",
new SkillRequirement(Skill.MAGIC, 45));
add("String a Maple shortbow in Seers' Village bank.",
new SkillRequirement(Skill.FLETCHING, 50));
add("Pick some Limpwurt root from the farming patch in Catherby.",
new SkillRequirement(Skill.FARMING, 26));
add("Create a Mind helmet.",
new QuestRequirement(Quest.ELEMENTAL_WORKSHOP_II));
add("Kill a Fire Giant inside Baxtorian Waterfall.",
new QuestRequirement(Quest.WATERFALL_QUEST, true));
add("Steal from the chest in Hemenster.",
new SkillRequirement(Skill.THIEVING, 47));
add("Travel to McGrubor's Wood by Fairy Ring.",
new QuestRequirement(Quest.FAIRYTALE_II__CURE_A_QUEEN, true));
add("Mine some coal near the coal trucks.",
new SkillRequirement(Skill.MINING, 30));
// HARD
add("Catch a Leaping Sturgeon.",
new SkillRequirement(Skill.FISHING, 70),
new SkillRequirement(Skill.AGILITY, 45),
new SkillRequirement(Skill.STRENGTH, 45));
add("Complete a lap of the Seers' Village agility course.",
new SkillRequirement(Skill.AGILITY, 60));
add("Create a Yew Longbow from scratch around Seers' Village.",
new SkillRequirement(Skill.WOODCUTTING, 60),
new SkillRequirement(Skill.FLETCHING, 70),
new SkillRequirement(Skill.CRAFTING, 10));
add("Enter the Seers' Village courthouse with piety turned on.",
new SkillRequirement(Skill.PRAYER, 70),
new SkillRequirement(Skill.DEFENCE, 70),
new QuestRequirement(Quest.KINGS_RANSOM));
add("Charge a Water Orb.",
new SkillRequirement(Skill.MAGIC, 56));
add("Burn some Maple logs with a bow in Seers' Village.",
new SkillRequirement(Skill.FIREMAKING, 65));
add("Kill a Shadow Hound in the Shadow dungeon.",
new SkillRequirement(Skill.THIEVING, 53),
new QuestRequirement(Quest.DESERT_TREASURE, true));
add("Purchase and equip a granite body from Barbarian Assault.",
new SkillRequirement(Skill.STRENGTH, 50),
new SkillRequirement(Skill.DEFENCE, 50));
add("Have the Seers' estate agent decorate your house with Fancy Stone.",
new SkillRequirement(Skill.CONSTRUCTION, 50));
add("Smith an Adamant spear at Otto's Grotto.",
new SkillRequirement(Skill.SMITHING, 75),
new QuestRequirement(Quest.TAI_BWO_WANNAI_TRIO));
// ELITE
add("Pick some Dwarf weed from the herb patch at Catherby.",
new SkillRequirement(Skill.FARMING, 79));
add("Fish and Cook 5 Sharks in Catherby using the Cooking gauntlets.",
new SkillRequirement(Skill.FISHING, 76),
new SkillRequirement(Skill.COOKING, 80),
new QuestRequirement(Quest.FAMILY_CREST));
add("Mix a Stamina Mix on top of the Seers' Village bank.",
new SkillRequirement(Skill.HERBLORE, 86),
new SkillRequirement(Skill.AGILITY, 60));
add("Smith a Rune Hasta at Otto's Grotto.",
new SkillRequirement(Skill.SMITHING, 90));
add("Construct a Pyre ship from Magic Logs.(Requires Chewed Bones.)",
new SkillRequirement(Skill.FIREMAKING, 85),
new SkillRequirement(Skill.CRAFTING, 85));
add("Teleport to Catherby.",
new SkillRequirement(Skill.MAGIC, 87),
new QuestRequirement(Quest.LUNAR_DIPLOMACY));
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.CombatLevelRequirement;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.OrRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class KaramjaDiaryRequirement extends GenericDiaryRequirement
{
public KaramjaDiaryRequirement()
{
// EASY
add("Use the rope swing to travel to the small island north-west of Karamja, where the " +
"moss giants are.",
new SkillRequirement(Skill.AGILITY, 10));
add("Mine some gold from the rocks on the north-west peninsula of Karamja.",
new SkillRequirement(Skill.MINING, 40));
add("Explore Cairn Island to the west of Karamja.",
new SkillRequirement(Skill.AGILITY, 15));
// MEDIUM
add("Claim a ticket from the Agility Arena in Brimhaven.",
new SkillRequirement(Skill.AGILITY, 30));
add("Discover hidden wall in the dungeon below the volcano.",
new QuestRequirement(Quest.DRAGON_SLAYER, true));
add("Visit the Isle of Crandor via the dungeon below the volcano.",
new QuestRequirement(Quest.DRAGON_SLAYER, true));
add("Use Vigroy and Hajedy's cart service.",
new QuestRequirement(Quest.SHILO_VILLAGE));
add("Earn 100% favour in the village of Tai Bwo Wannai.",
new SkillRequirement(Skill.WOODCUTTING, 10),
new QuestRequirement(Quest.JUNGLE_POTION));
add("Cook a spider on a stick.",
new SkillRequirement(Skill.COOKING, 16));
add("Charter the Lady of the Waves from Cairn Isle to Port Khazard.",
new QuestRequirement(Quest.SHILO_VILLAGE));
add("Cut a log from a teak tree.",
new SkillRequirement(Skill.WOODCUTTING, 35),
new QuestRequirement(Quest.JUNGLE_POTION));
add("Cut a log from a mahogany tree.",
new SkillRequirement(Skill.WOODCUTTING, 50),
new QuestRequirement(Quest.JUNGLE_POTION));
add("Catch a karambwan.",
new SkillRequirement(Skill.FISHING, 65),
new QuestRequirement(Quest.TAI_BWO_WANNAI_TRIO, true));
add("Exchange gems for a machete.",
new QuestRequirement(Quest.JUNGLE_POTION));
add("Use the gnome glider to travel to Karamja.",
new QuestRequirement(Quest.THE_GRAND_TREE));
add("Grow a healthy fruit tree in the patch near Brimhaven.",
new SkillRequirement(Skill.FARMING, 27));
add("Trap a horned graahk.",
new SkillRequirement(Skill.HUNTER, 41));
add("Chop the vines to gain deeper access to Brimhaven Dungeon.",
new SkillRequirement(Skill.WOODCUTTING, 10));
add("Cross the lava using the stepping stones within Brimhaven Dungeon.",
new SkillRequirement(Skill.AGILITY, 12));
add("Climb the stairs within Brimhaven Dungeon.",
new SkillRequirement(Skill.WOODCUTTING, 10));
add("Charter a ship from the shipyard in the far east of Karamja.",
new QuestRequirement(Quest.THE_GRAND_TREE));
add("Mine a red topaz from a gem rock.",
new SkillRequirement(Skill.MINING, 40),
new OrRequirement(
new QuestRequirement(Quest.SHILO_VILLAGE),
new QuestRequirement(Quest.JUNGLE_POTION)
)
);
// HARD
add("Craft some nature runes.",
new SkillRequirement(Skill.RUNECRAFT, 44),
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Cook a karambwan thoroughly.",
new SkillRequirement(Skill.COOKING, 30),
new QuestRequirement(Quest.TAI_BWO_WANNAI_TRIO));
add("Kill a deathwing in the dungeon under the Kharazi Jungle.",
new SkillRequirement(Skill.WOODCUTTING, 15),
new SkillRequirement(Skill.STRENGTH, 50),
new SkillRequirement(Skill.AGILITY, 50),
new SkillRequirement(Skill.THIEVING, 50),
new SkillRequirement(Skill.MINING, 52),
new QuestRequirement(Quest.LEGENDS_QUEST));
add("Use the crossbow short cut south of the volcano.",
new SkillRequirement(Skill.AGILITY, 53),
new SkillRequirement(Skill.RANGED, 42),
new SkillRequirement(Skill.STRENGTH, 21));
add("Collect 5 palm leaves.",
new SkillRequirement(Skill.WOODCUTTING, 15),
new QuestRequirement(Quest.LEGENDS_QUEST));
add("Be assigned a Slayer task by Duradel north of Shilo Village.",
new CombatLevelRequirement(100),
new SkillRequirement(Skill.SLAYER, 50),
new QuestRequirement(Quest.SHILO_VILLAGE));
// ELITE
add("Craft 56 Nature runes at once.",
new SkillRequirement(Skill.RUNECRAFT, 91));
add("Check the health of a palm tree in Brimhaven.",
new SkillRequirement(Skill.FARMING, 68));
add("Create an antivenom potion whilst standing in the horse shoe mine.",
new SkillRequirement(Skill.HERBLORE, 87));
add("Check the health of your Calquat tree patch.",
new SkillRequirement(Skill.FARMING, 72));
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2019 William <https://github.com/monsterxsync>
* 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.achievementdiary.diaries;
import net.runelite.api.Favour;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.FavourRequirement;
public class KourendDiaryRequirement extends GenericDiaryRequirement
{
public KourendDiaryRequirement()
{
//EASY
add("Mine some Iron at the Mount Karuulm mine.",
new SkillRequirement(Skill.MINING, 15));
add("Steal from a Hosidius Food Stall.",
new SkillRequirement(Skill.THIEVING, 25),
new FavourRequirement(Favour.HOSIDIUS, 15));
add("Browse the Warrens General Store.",
new QuestRequirement(Quest.THE_QUEEN_OF_THIEVES, true));
add("Enter your Player Owned House from Hosidius.",
new SkillRequirement(Skill.CONSTRUCTION, 25));
add("Create a Strength potion in the Lovakengj Pub.",
new SkillRequirement(Skill.HERBLORE, 12));
add("Fish a Trout from the River Molch.",
new SkillRequirement(Skill.FISHING, 20));
//MEDIUM
add("Travel to the Fairy Ring south of Mount Karuulm.",
new QuestRequirement(Quest.FAIRYTALE_II__CURE_A_QUEEN, true));
add("Use Kharedst's memoirs to teleport to all five cities in Great Kourend.",
new QuestRequirement(Quest.THE_DEPTHS_OF_DESPAIR),
new QuestRequirement(Quest.THE_QUEEN_OF_THIEVES),
new QuestRequirement(Quest.TALE_OF_THE_RIGHTEOUS),
new QuestRequirement(Quest.THE_FORSAKEN_TOWER),
new QuestRequirement(Quest.THE_ASCENT_OF_ARCEUUS));
add("Mine some Volcanic sulphur.",
new SkillRequirement(Skill.MINING, 42));
add("Enter the Farming Guild.",
new SkillRequirement(Skill.FARMING, 45));
add("Switch to the Necromancy Spellbook at Tyss.",
new FavourRequirement(Favour.ARCEUUS, 60));
add("Repair a Piscarilius crane.",
new SkillRequirement(Skill.CRAFTING, 30));
add("Deliver some intelligence to Captain Ginea.",
new FavourRequirement(Favour.SHAYZIEN, 40));
add("Catch a Bluegill on Molch Island.",
new SkillRequirement(Skill.FISHING, 43),
new SkillRequirement(Skill.HUNTER, 35));
add("Use the boulder leap in the Arceuus essence mine.",
new SkillRequirement(Skill.AGILITY, 49));
add("Subdue the Wintertodt.",
new SkillRequirement(Skill.FIREMAKING, 50));
add("Catch a Chinchompa in the Kourend Woodland.",
new SkillRequirement(Skill.HUNTER, 53),
new QuestRequirement(Quest.EAGLES_PEAK));
add("Chop some Mahogany logs north of the Farming Guild.",
new SkillRequirement(Skill.WOODCUTTING, 50));
//HARD
add("Enter the Woodcutting Guild.",
new SkillRequirement(Skill.WOODCUTTING, 60),
new FavourRequirement(Favour.HOSIDIUS, 75));
add("Smelt an Adamantite bar in The Forsaken Tower.",
new SkillRequirement(Skill.SMITHING, 70),
new QuestRequirement(Quest.THE_FORSAKEN_TOWER, true));
add("Kill a Lizardman Shaman in Molch.",
new FavourRequirement(Favour.SHAYZIEN, 100));
add("Mine some Lovakite.",
new SkillRequirement(Skill.MINING, 65),
new FavourRequirement(Favour.LOVAKENGJ, 30));
add("Plant some Logavano seeds at the Tithe Farm.",
new SkillRequirement(Skill.FARMING, 74),
new FavourRequirement(Favour.HOSIDIUS, 100));
add("Teleport to Xeric's Heart using Xeric's Talisman.",
new QuestRequirement(Quest.ARCHITECTURAL_ALLIANCE));
add("Deliver an artefact to Captain Khaled.",
new SkillRequirement(Skill.THIEVING, 49),
new FavourRequirement(Favour.PISCARILIUS, 75));
add("Kill a Wyrm in the Karuulm Slayer Dungeon.",
new SkillRequirement(Skill.SLAYER, 62));
add("Cast Monster Examine on a Troll south of Mount Quidamortem.",
new SkillRequirement(Skill.MAGIC, 66),
new QuestRequirement(Quest.DREAM_MENTOR));
//ELITE
add("Craft one or more Blood runes.",
new SkillRequirement(Skill.RUNECRAFT, 77),
new SkillRequirement(Skill.MINING, 38),
new SkillRequirement(Skill.CRAFTING, 38),
new FavourRequirement(Favour.ARCEUUS, 100));
add("Chop some Redwood logs.",
new SkillRequirement(Skill.WOODCUTTING, 90),
new FavourRequirement(Favour.HOSIDIUS, 75));
add("Catch an Anglerfish and cook it whilst in Great Kourend.",
new SkillRequirement(Skill.FISHING, 82),
new SkillRequirement(Skill.COOKING, 84),
new FavourRequirement(Favour.PISCARILIUS, 100));
add("Kill a Hydra in the Karuulm Slayer Dungeon.",
new SkillRequirement(Skill.SLAYER, 95));
add("Create an Ape Atoll teleport tablet.",
new SkillRequirement(Skill.MAGIC, 90),
new SkillRequirement(Skill.MINING, 38),
new SkillRequirement(Skill.CRAFTING, 38),
new FavourRequirement(Favour.ARCEUUS, 100));
add("Create your own Battlestaff from scratch within the Farming Guild.",
new SkillRequirement(Skill.FARMING, 85),
new SkillRequirement(Skill.FLETCHING, 40));
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.CombatLevelRequirement;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class LumbridgeDiaryRequirement extends GenericDiaryRequirement
{
public LumbridgeDiaryRequirement()
{
// EASY
add("Complete a lap of the Draynor Village agility course.",
new SkillRequirement(Skill.AGILITY, 10));
add("Slay a Cave bug beneath Lumbridge Swamp.",
new SkillRequirement(Skill.SLAYER, 7));
add("Have Sedridor teleport you to the Essence Mine.",
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Craft some water runes.",
new SkillRequirement(Skill.RUNECRAFT, 5),
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Chop and burn some oak logs in Lumbridge.",
new SkillRequirement(Skill.WOODCUTTING, 15),
new SkillRequirement(Skill.FIREMAKING, 15));
add("Catch some Anchovies in Al Kharid.",
new SkillRequirement(Skill.FISHING, 15));
add("Bake some Bread on the Lumbridge kitchen range.",
new QuestRequirement(Quest.COOKS_ASSISTANT));
add("Mine some Iron ore at the Al Kharid mine.",
new SkillRequirement(Skill.MINING, 15));
// MEDIUM
add("Complete a lap of the Al Kharid agility course.",
new SkillRequirement(Skill.AGILITY, 20));
add("Grapple across the River Lum.",
new SkillRequirement(Skill.AGILITY, 8),
new SkillRequirement(Skill.STRENGTH, 19),
new SkillRequirement(Skill.RANGED, 37));
add("Purchase an upgraded device from Ava.",
new SkillRequirement(Skill.RANGED, 50),
new QuestRequirement(Quest.ANIMAL_MAGNETISM));
add("Travel to the Wizards' Tower by Fairy ring.",
new QuestRequirement(Quest.FAIRYTALE_II__CURE_A_QUEEN, true));
add("Cast the teleport to Lumbridge spell.",
new SkillRequirement(Skill.MAGIC, 31));
add("Catch some Salmon in Lumbridge.",
new SkillRequirement(Skill.FISHING, 30));
add("Craft a coif in the Lumbridge cow pen.",
new SkillRequirement(Skill.CRAFTING, 38));
add("Chop some willow logs in Draynor Village.",
new SkillRequirement(Skill.WOODCUTTING, 30));
add("Pickpocket Martin the Master Gardener.",
new SkillRequirement(Skill.THIEVING, 38));
add("Get a slayer task from Chaeldar.",
new CombatLevelRequirement(70),
new QuestRequirement(Quest.LOST_CITY));
add("Catch an Essence or Eclectic impling in Puro-Puro.",
new SkillRequirement(Skill.HUNTER, 42),
new QuestRequirement(Quest.LOST_CITY));
add("Craft some Lava runes at the fire altar in Al Kharid.",
new SkillRequirement(Skill.RUNECRAFT, 23),
new QuestRequirement(Quest.RUNE_MYSTERIES));
// HARD
add("Cast Bones to Peaches in Al Kharid palace.",
new SkillRequirement(Skill.MAGIC, 60));
add("Squeeze past the jutting wall on your way to the cosmic altar.",
new SkillRequirement(Skill.AGILITY, 46),
new QuestRequirement(Quest.LOST_CITY));
add("Craft 56 Cosmic runes simultaneously.",
new SkillRequirement(Skill.RUNECRAFT, 59),
new QuestRequirement(Quest.LOST_CITY));
add("Travel from Lumbridge to Edgeville on a Waka Canoe.",
new SkillRequirement(Skill.WOODCUTTING, 57));
add("Collect at least 100 Tears of Guthix in one visit.",
new QuestRequirement(Quest.TEARS_OF_GUTHIX));
add("Take the train from Dorgesh-Kaan to Keldagrim.",
new QuestRequirement(Quest.ANOTHER_SLICE_OF_HAM));
add("Purchase some Barrows gloves from the Lumbridge bank chest.",
new QuestRequirement(Quest.RECIPE_FOR_DISASTER));
add("Pick some Belladonna from the farming patch at Draynor Manor.",
new SkillRequirement(Skill.FARMING, 63));
add("Light your mining helmet in the Lumbridge castle basement.",
new SkillRequirement(Skill.FIREMAKING, 65));
add("Recharge your prayer at Clan Wars with Smite activated.",
new SkillRequirement(Skill.PRAYER, 52));
add("Craft, string and enchant an Amulet of Power in Lumbridge.",
new SkillRequirement(Skill.CRAFTING, 70),
new SkillRequirement(Skill.MAGIC, 57));
// ELITE
add("Steal from a Dorgesh-Kaan rich chest.",
new SkillRequirement(Skill.THIEVING, 78),
new QuestRequirement(Quest.DEATH_TO_THE_DORGESHUUN));
add("Pickpocket Movario on the Dorgesh-Kaan Agility course.",
new SkillRequirement(Skill.AGILITY, 70),
new SkillRequirement(Skill.RANGED, 70),
new SkillRequirement(Skill.STRENGTH, 70),
new QuestRequirement(Quest.DEATH_TO_THE_DORGESHUUN));
add("Chop some magic logs at the Mage Training Arena.",
new SkillRequirement(Skill.WOODCUTTING, 75));
add("Smith an Adamant platebody down Draynor sewer.",
new SkillRequirement(Skill.SMITHING, 88));
add("Craft 140 or more Water runes at once.",
new SkillRequirement(Skill.RUNECRAFT, 76),
new QuestRequirement(Quest.RUNE_MYSTERIES));
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.CombatLevelRequirement;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.OrRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class MorytaniaDiaryRequirement extends GenericDiaryRequirement
{
public MorytaniaDiaryRequirement()
{
// EASY
add("Craft any Snelm from scratch in Morytania.",
new SkillRequirement(Skill.CRAFTING, 15));
add("Cook a thin Snail on the Port Phasmatys range.",
new SkillRequirement(Skill.COOKING, 12));
add("Get a slayer task from Mazchna.",
new CombatLevelRequirement(20));
add("Kill a Banshee in the Slayer Tower.",
new SkillRequirement(Skill.SLAYER, 15));
add("Place a Scarecrow in the Morytania flower patch.",
new SkillRequirement(Skill.FARMING, 23));
add("Kill a werewolf in its human form using the Wolfbane Dagger.",
new QuestRequirement(Quest.PRIEST_IN_PERIL));
add("Restore your prayer points at the nature altar.",
new QuestRequirement(Quest.NATURE_SPIRIT));
// MEDIUM
add("Catch a swamp lizard.",
new SkillRequirement(Skill.HUNTER, 29));
add("Complete a lap of the Canifis agility course.",
new SkillRequirement(Skill.AGILITY, 40));
add("Obtain some Bark from a Hollow tree.",
new SkillRequirement(Skill.WOODCUTTING, 45));
add("Kill a Terror Dog.",
new SkillRequirement(Skill.SLAYER, 40),
new QuestRequirement(Quest.LAIR_OF_TARN_RAZORLOR));
add("Complete a game of trouble brewing.",
new SkillRequirement(Skill.COOKING, 40),
new QuestRequirement(Quest.CABIN_FEVER));
add("Make a batch of cannonballs at the Port Phasmatys furnace.",
new SkillRequirement(Skill.SMITHING, 35),
new QuestRequirement(Quest.DWARF_CANNON),
new QuestRequirement(Quest.GHOSTS_AHOY, true));
add("Kill a Fever Spider on Braindeath Island.",
new SkillRequirement(Skill.SLAYER, 42),
new QuestRequirement(Quest.RUM_DEAL));
add("Use an ectophial to return to Port Phasmatys.",
new QuestRequirement(Quest.GHOSTS_AHOY));
add("Mix a Guthix Balance potion while in Morytania.",
new SkillRequirement(Skill.HERBLORE, 22),
new QuestRequirement(Quest.IN_AID_OF_THE_MYREQUE, true));
// HARD
add("Enter the Kharyrll portal in your POH.",
new SkillRequirement(Skill.MAGIC, 66),
new SkillRequirement(Skill.CONSTRUCTION, 50),
new QuestRequirement(Quest.DESERT_TREASURE));
add("Climb the advanced spike chain within Slayer Tower.",
new SkillRequirement(Skill.AGILITY, 71));
add("Harvest some Watermelon from the Allotment patch on Harmony Island.",
new SkillRequirement(Skill.FARMING, 47),
new QuestRequirement(Quest.THE_GREAT_BRAIN_ROBBERY, true));
add("Chop and burn some mahogany logs on Mos Le'Harmless.",
new SkillRequirement(Skill.WOODCUTTING, 50),
new SkillRequirement(Skill.FIREMAKING, 50),
new QuestRequirement(Quest.CABIN_FEVER));
add("Complete a temple trek with a hard companion.",
new QuestRequirement(Quest.IN_AID_OF_THE_MYREQUE));
add("Kill a Cave Horror.",
new SkillRequirement(Skill.SLAYER, 58),
new QuestRequirement(Quest.CABIN_FEVER));
add("Harvest some Bittercap Mushrooms from the patch in Canifis.",
new SkillRequirement(Skill.FARMING, 53));
add("Pray at the Altar of Nature with Piety activated.",
new SkillRequirement(Skill.PRAYER, 70),
new SkillRequirement(Skill.DEFENCE, 70),
new QuestRequirement(Quest.NATURE_SPIRIT),
new QuestRequirement(Quest.KINGS_RANSOM));
add("Use the shortcut to get to the bridge over the Salve.",
new SkillRequirement(Skill.AGILITY, 65));
add("Mine some Mithril ore in the Abandoned Mine.",
new SkillRequirement(Skill.MINING, 55),
new QuestRequirement(Quest.HAUNTED_MINE));
// ELITE
add("Catch a shark in Burgh de Rott with your bare hands.",
new SkillRequirement(Skill.FISHING, 96),
new SkillRequirement(Skill.STRENGTH, 76),
new QuestRequirement(Quest.IN_AID_OF_THE_MYREQUE));
add("Cremate any Shade remains on a Magic or Redwood pyre.",
new SkillRequirement(Skill.FIREMAKING, 80),
new QuestRequirement(Quest.SHADES_OF_MORTTON));
add("Fertilize the Morytania herb patch using Lunar Magic.",
new SkillRequirement(Skill.MAGIC, 83),
new QuestRequirement(Quest.LUNAR_DIPLOMACY));
add("Craft a Black dragonhide body in Canifis bank.",
new SkillRequirement(Skill.CRAFTING, 84));
add("Kill an Abyssal demon in the Slayer Tower.",
new SkillRequirement(Skill.SLAYER, 85));
add("Loot the Barrows chest while wearing any complete barrows set.",
new SkillRequirement(Skill.DEFENCE, 70),
new OrRequirement(
new SkillRequirement(Skill.ATTACK, 70),
new SkillRequirement(Skill.STRENGTH, 70),
new SkillRequirement(Skill.RANGED, 70),
new SkillRequirement(Skill.MAGIC, 70)
)
);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.CombatLevelRequirement;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestPointRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class VarrockDiaryRequirement extends GenericDiaryRequirement
{
public VarrockDiaryRequirement()
{
// EASY
add("Have Aubury teleport you to the Essence mine.",
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Mine some Iron in the south east mining patch near Varrock.",
new SkillRequirement(Skill.MINING, 15));
add("Jump over the fence south of Varrock.",
new SkillRequirement(Skill.AGILITY, 13));
add("Spin a bowl on the pottery wheel and fire it in the oven in Barb Village.",
new SkillRequirement(Skill.CRAFTING, 8));
add("Craft some Earth runes.",
new SkillRequirement(Skill.RUNECRAFT, 9));
add("Catch some trout in the River Lum at Barbarian Village.",
new SkillRequirement(Skill.FISHING, 20));
add("Steal from the Tea stall in Varrock.",
new SkillRequirement(Skill.THIEVING, 5));
// MEDIUM
add("Enter the Champions' Guild.",
new QuestPointRequirement(32));
add("Select a colour for your kitten.",
new QuestRequirement(Quest.GARDEN_OF_TRANQUILLITY, true),
new QuestRequirement(Quest.GERTRUDES_CAT));
add("Use the spirit tree north of Varrock.",
new QuestRequirement(Quest.TREE_GNOME_VILLAGE));
add("Enter the Tolna dungeon after completing A Soul's Bane.",
new QuestRequirement(Quest.A_SOULS_BANE));
add("Teleport to the digsite using a Digsite pendant.",
new QuestRequirement(Quest.THE_DIG_SITE));
add("Cast the teleport to Varrock spell.",
new SkillRequirement(Skill.MAGIC, 25));
add("Get a Slayer task from Vannaka.",
new CombatLevelRequirement(40));
add("Pick a White tree fruit.",
new SkillRequirement(Skill.FARMING, 25),
new QuestRequirement(Quest.GARDEN_OF_TRANQUILLITY));
add("Use the balloon to travel from Varrock.",
new SkillRequirement(Skill.FIREMAKING, 40),
new QuestRequirement(Quest.ENLIGHTENED_JOURNEY));
add("Complete a lap of the Varrock Agility course.",
new SkillRequirement(Skill.AGILITY, 30));
// HARD
add("Trade furs with the Fancy Dress Seller for a spottier cape and equip it.",
new SkillRequirement(Skill.HUNTER, 66));
add("Make a Waka Canoe near Edgeville.",
new SkillRequirement(Skill.WOODCUTTING, 57));
add("Teleport to Paddewwa.",
new SkillRequirement(Skill.MAGIC, 54),
new QuestRequirement(Quest.DESERT_TREASURE));
add("Chop some yew logs in Varrock and burn them at the top of the Varrock church.",
new SkillRequirement(Skill.WOODCUTTING, 60),
new SkillRequirement(Skill.FIREMAKING, 60));
add("Have the Varrock estate agent decorate your house with Fancy Stone.",
new SkillRequirement(Skill.CONSTRUCTION, 50));
add("Collect at least 2 yew roots from the Tree patch in Varrock Palace.",
new SkillRequirement(Skill.WOODCUTTING, 60),
new SkillRequirement(Skill.FARMING, 68));
add("Pray at the altar in Varrock palace with Smite active.",
new SkillRequirement(Skill.PRAYER, 52));
add("Squeeze through the obstacle pipe in Edgeville dungeon.",
new SkillRequirement(Skill.AGILITY, 51));
// ELITE
add("Create a super combat potion in Varrock west bank.",
new SkillRequirement(Skill.HERBLORE, 90),
new QuestRequirement(Quest.DRUIDIC_RITUAL));
add("Use Lunar magic to make 20 mahogany planks at the Lumberyard.",
new SkillRequirement(Skill.MAGIC, 86),
new QuestRequirement(Quest.DREAM_MENTOR));
add("Bake a summer pie in the Cooking Guild.",
new SkillRequirement(Skill.COOKING, 95));
add("Smith and fletch ten rune darts within Varrock.",
new SkillRequirement(Skill.SMITHING, 89),
new SkillRequirement(Skill.FLETCHING, 81),
new QuestRequirement(Quest.THE_TOURIST_TRAP));
add("Craft 100 or more earth runes simultaneously.",
new SkillRequirement(Skill.RUNECRAFT, 78),
new QuestRequirement(Quest.RUNE_MYSTERIES));
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.CombatLevelRequirement;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class WesternDiaryRequirement extends GenericDiaryRequirement
{
public WesternDiaryRequirement()
{
// EASY
add("Catch a Copper Longtail.",
new SkillRequirement(Skill.HUNTER, 9));
add("Complete a novice game of Pest Control.",
new CombatLevelRequirement(40));
add("Mine some Iron Ore near Piscatoris.",
new SkillRequirement(Skill.MINING, 15));
add("Claim any Chompy bird hat from Rantz.",
new QuestRequirement(Quest.BIG_CHOMPY_BIRD_HUNTING));
add("Have Brimstail teleport you to the Essence mine.",
new QuestRequirement(Quest.RUNE_MYSTERIES));
add("Fletch an Oak shortbow from the Gnome Stronghold.",
new SkillRequirement(Skill.FLETCHING, 20));
// MEDIUM
add("Take the agility shortcut from the Grand Tree to Otto's Grotto.",
new SkillRequirement(Skill.AGILITY, 37),
new QuestRequirement(Quest.TREE_GNOME_VILLAGE),
new QuestRequirement(Quest.THE_GRAND_TREE));
add("Travel to the Gnome Stronghold by Spirit Tree.",
new QuestRequirement(Quest.TREE_GNOME_VILLAGE));
add("Trap a Spined Larupia.",
new SkillRequirement(Skill.HUNTER, 31));
add("Fish some Bass on Ape Atoll.",
new SkillRequirement(Skill.FISHING, 46),
new QuestRequirement(Quest.MONKEY_MADNESS_I, true));
add("Chop and burn some teak logs on Ape Atoll.",
new SkillRequirement(Skill.WOODCUTTING, 35),
new SkillRequirement(Skill.FIREMAKING, 35),
new QuestRequirement(Quest.MONKEY_MADNESS_I, true));
add("Complete an intermediate game of Pest Control.",
new CombatLevelRequirement(70));
add("Travel to the Feldip Hills by Gnome Glider.",
new QuestRequirement(Quest.ONE_SMALL_FAVOUR),
new QuestRequirement(Quest.THE_GRAND_TREE));
add("Claim a Chompy bird hat from Rantz after registering at least 125 kills.",
new QuestRequirement(Quest.BIG_CHOMPY_BIRD_HUNTING));
add("Travel from Eagles' Peak to the Feldip Hills by Eagle.",
new QuestRequirement(Quest.EAGLES_PEAK));
add("Make a Chocolate Bomb at the Grand Tree.",
new SkillRequirement(Skill.COOKING, 42));
add("Complete a delivery for the Gnome Restaurant.",
new SkillRequirement(Skill.COOKING, 29));
add("Turn your small crystal seed into a Crystal saw.",
new QuestRequirement(Quest.THE_EYES_OF_GLOUPHRIE));
add("Mine some Gold ore underneath the Grand Tree.",
new SkillRequirement(Skill.MINING, 40),
new QuestRequirement(Quest.THE_GRAND_TREE));
// HARD
add("Kill an Elf with a Crystal bow.",
new SkillRequirement(Skill.RANGED, 70),
new SkillRequirement(Skill.AGILITY, 56),
new QuestRequirement(Quest.ROVING_ELVES));
add("Catch and cook a Monkfish in Piscatoris.",
new SkillRequirement(Skill.FISHING, 62),
new SkillRequirement(Skill.COOKING, 62),
new QuestRequirement(Quest.SWAN_SONG));
add("Complete a Veteran game of Pest Control.",
new CombatLevelRequirement(100));
add("Catch a Dashing Kebbit.",
new SkillRequirement(Skill.HUNTER, 69));
add("Complete a lap of the Ape Atoll agility course.",
new SkillRequirement(Skill.AGILITY, 48),
new QuestRequirement(Quest.MONKEY_MADNESS_I));
add("Chop and burn some Mahogany logs on Ape Atoll.",
new SkillRequirement(Skill.WOODCUTTING, 50),
new SkillRequirement(Skill.FIREMAKING, 50),
new QuestRequirement(Quest.MONKEY_MADNESS_I));
add("Mine some Adamantite ore in Tirannwn.",
new SkillRequirement(Skill.MINING, 70),
new QuestRequirement(Quest.REGICIDE));
add("Check the health of your Palm tree in Lletya.",
new SkillRequirement(Skill.FARMING, 68),
new QuestRequirement(Quest.MOURNINGS_END_PART_I, true));
add("Claim a Chompy bird hat from Rantz after registering at least 300 kills.",
new QuestRequirement(Quest.BIG_CHOMPY_BIRD_HUNTING));
add("Build an Isafdar painting in your POH Quest hall.",
new SkillRequirement(Skill.CONSTRUCTION, 65),
new QuestRequirement(Quest.ROVING_ELVES));
add("Kill Zulrah.",
new QuestRequirement(Quest.REGICIDE, true));
add("Teleport to Ape Atoll.",
new SkillRequirement(Skill.MAGIC, 64),
new QuestRequirement(Quest.RECIPE_FOR_DISASTER, true));
add("Pickpocket a Gnome.",
new SkillRequirement(Skill.THIEVING, 75),
new QuestRequirement(Quest.TREE_GNOME_VILLAGE));
// ELITE
add("Fletch a Magic Longbow in Tirannwn.",
new SkillRequirement(Skill.FLETCHING, 85),
new QuestRequirement(Quest.MOURNINGS_END_PART_I));
add("Kill the Thermonuclear Smoke devil (Does not require task).",
new SkillRequirement(Skill.SLAYER, 93));
add("Have Prissy Scilla protect your Magic tree.",
new SkillRequirement(Skill.FARMING, 75));
add("Use the Elven overpass advanced cliffside shortcut.",
new SkillRequirement(Skill.AGILITY, 85),
new QuestRequirement(Quest.UNDERGROUND_PASS));
add("Claim a Chompy bird hat from Rantz after registering at least 1000 kills.",
new QuestRequirement(Quest.BIG_CHOMPY_BIRD_HUNTING));
add("Pickpocket an Elf.",
new SkillRequirement(Skill.THIEVING, 85),
new QuestRequirement(Quest.MOURNINGS_END_PART_I, true));
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018, Marshall <https://github.com/marshdevs>
* Copyright (c) 2018, 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.client.plugins.achievementdiary.diaries;
import net.runelite.api.Quest;
import net.runelite.api.Skill;
import net.runelite.client.plugins.achievementdiary.GenericDiaryRequirement;
import net.runelite.client.plugins.achievementdiary.OrRequirement;
import net.runelite.client.plugins.achievementdiary.QuestRequirement;
import net.runelite.client.plugins.achievementdiary.SkillRequirement;
public class WildernessDiaryRequirement extends GenericDiaryRequirement
{
public WildernessDiaryRequirement()
{
// EASY
add("Cast Low Alchemy at the Fountain of Rune.",
new SkillRequirement(Skill.MAGIC, 21));
add("Kill an Earth Warrior in the Wilderness beneath Edgeville.",
new SkillRequirement(Skill.AGILITY, 15));
add("Mine some Iron ore in the Wilderness.",
new SkillRequirement(Skill.MINING, 15));
add("Have the Mage of Zamorak teleport you to the Abyss.",
new QuestRequirement(Quest.ENTER_THE_ABYSS));
// MEDIUM
add("Mine some Mithril ore in the wilderness.",
new SkillRequirement(Skill.MINING, 55));
add("Chop some yew logs from a fallen Ent.",
new SkillRequirement(Skill.WOODCUTTING, 61));
add("Enter the Wilderness Godwars Dungeon.",
new OrRequirement(
new SkillRequirement(Skill.AGILITY, 60),
new SkillRequirement(Skill.STRENGTH, 60)
)
);
add("Complete a lap of the Wilderness Agility course.",
new SkillRequirement(Skill.AGILITY, 52));
add("Charge an Earth Orb.",
new SkillRequirement(Skill.MAGIC, 60));
add("Kill a Bloodveld in the Wilderness Godwars Dungeon.",
new SkillRequirement(Skill.SLAYER, 50));
add("Smith a Golden helmet in the Resource Area.",
new SkillRequirement(Skill.SMITHING, 50),
new QuestRequirement(Quest.BETWEEN_A_ROCK, true));
// HARD
add("Cast one of the 3 God spells against another player in the Wilderness.",
new SkillRequirement(Skill.MAGIC, 60),
new QuestRequirement(Quest.THE_MAGE_ARENA));
add("Charge an Air Orb.",
new SkillRequirement(Skill.MAGIC, 66));
add("Catch a Black Salamander in the Wilderness.",
new SkillRequirement(Skill.HUNTER, 67));
add("Smith an Adamant scimitar in the Resource Area.",
new SkillRequirement(Skill.SMITHING, 75));
add("Take the agility shortcut from Trollheim into the Wilderness.",
new SkillRequirement(Skill.AGILITY, 64),
new QuestRequirement(Quest.DEATH_PLATEAU));
add("Kill a Spiritual warrior in the Wilderness Godwars Dungeon.",
new SkillRequirement(Skill.SLAYER, 68));
add("Fish some Raw Lava Eel in the Wilderness.",
new SkillRequirement(Skill.FISHING, 53));
// ELITE
add("Teleport to Ghorrock.",
new SkillRequirement(Skill.MAGIC, 96),
new QuestRequirement(Quest.DESERT_TREASURE));
add("Fish and Cook a Dark Crab in the Resource Area.",
new SkillRequirement(Skill.FISHING, 85),
new SkillRequirement(Skill.COOKING, 90));
add("Smith a rune scimitar from scratch in the Resource Area.",
new SkillRequirement(Skill.MINING, 85),
new SkillRequirement(Skill.SMITHING, 90));
add("Steal from the Rogues' chest.",
new SkillRequirement(Skill.THIEVING, 84));
add("Slay a spiritual mage inside the wilderness Godwars Dungeon.",
new SkillRequirement(Skill.SLAYER, 83),
new OrRequirement(
new SkillRequirement(Skill.AGILITY, 60),
new SkillRequirement(Skill.STRENGTH, 60)
)
);
add("Cut and burn some magic logs in the Resource Area.",
new SkillRequirement(Skill.WOODCUTTING, 75),
new SkillRequirement(Skill.FIREMAKING, 75));
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2018, Alex Kolpa <https://github.com/AlexKolpa>
* 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.agility;
import java.awt.image.BufferedImage;
import java.time.temporal.ChronoUnit;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.infobox.Timer;
class AgilityArenaTimer extends Timer
{
AgilityArenaTimer(Plugin plugin, BufferedImage image)
{
super(1, ChronoUnit.MINUTES, image, plugin);
setTooltip("Time left until location changes");
}
}

View File

@@ -0,0 +1,279 @@
/*
* Copyright (c) 2018, Cas <https://github.com/casvandongen>
* 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.agility;
import java.awt.Color;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
import net.runelite.client.config.Units;
@ConfigGroup("agility")
public interface AgilityConfig extends Config
{
@ConfigSection(
name = "Hallowed Sepulchre",
description = "Settings for Hallowed Sepulchre highlights",
position = 17
)
String sepulchreSection = "Hallowed Sepulchre";
@ConfigItem(
keyName = "showClickboxes",
name = "Show Clickboxes",
description = "Show agility course obstacle clickboxes",
position = 0
)
default boolean showClickboxes()
{
return true;
}
@ConfigItem(
keyName = "showLapCount",
name = "Show Lap Count",
description = "Enable/disable the lap counter",
position = 1
)
default boolean showLapCount()
{
return true;
}
@ConfigItem(
keyName = "lapTimeout",
name = "Hide Lap Count",
description = "Time until the lap counter hides/resets",
position = 2
)
@Units(Units.MINUTES)
default int lapTimeout()
{
return 5;
}
@ConfigItem(
keyName = "lapsToLevel",
name = "Show Laps Until Goal",
description = "Show number of laps remaining until next goal is reached.",
position = 3
)
default boolean lapsToLevel()
{
return true;
}
@ConfigItem(
keyName = "lapsPerHour",
name = "Show Laps Per Hour",
description = "Shows how many laps you can expect to complete per hour.",
position = 4
)
default boolean lapsPerHour()
{
return true;
}
@ConfigItem(
keyName = "overlayColor",
name = "Overlay Color",
description = "Color of Agility overlay",
position = 5
)
default Color getOverlayColor()
{
return Color.GREEN;
}
@ConfigItem(
keyName = "highlightMarks",
name = "Highlight Marks of Grace",
description = "Enable/disable the highlighting of retrievable Marks of Grace",
position = 6
)
default boolean highlightMarks()
{
return true;
}
@ConfigItem(
keyName = "markHighlight",
name = "Mark Highlight Color",
description = "Color of highlighted Marks of Grace",
position = 7
)
default Color getMarkColor()
{
return Color.RED;
}
@ConfigItem(
keyName = "highlightPortals",
name = "Highlight Portals",
description = "Enable/disable the highlighting of Prifddinas portals",
position = 8
)
default boolean highlightPortals()
{
return true;
}
@ConfigItem(
keyName = "portalsHighlight",
name = "Portals Highlight Color",
description = "Color of highlighted Prifddinas portals",
position = 9
)
default Color getPortalsColor()
{
return Color.MAGENTA;
}
@ConfigItem(
keyName = "highlightShortcuts",
name = "Highlight Agility Shortcuts",
description = "Enable/disable the highlighting of Agility shortcuts",
position = 10
)
default boolean highlightShortcuts()
{
return true;
}
@ConfigItem(
keyName = "trapOverlay",
name = "Show Trap Overlay",
description = "Enable/disable the highlighting of traps on Agility courses",
position = 11
)
default boolean showTrapOverlay()
{
return true;
}
@ConfigItem(
keyName = "trapHighlight",
name = "Trap Overlay Color",
description = "Color of Agility trap overlay",
position = 12
)
default Color getTrapColor()
{
return Color.RED;
}
@ConfigItem(
keyName = "agilityArenaNotifier",
name = "Agility Arena notifier",
description = "Notify on ticket location change in Agility Arena",
position = 13
)
default boolean notifyAgilityArena()
{
return true;
}
@ConfigItem(
keyName = "agilityArenaTimer",
name = "Agility Arena timer",
description = "Configures whether Agility Arena timer is displayed",
position = 14
)
default boolean showAgilityArenaTimer()
{
return true;
}
@ConfigItem(
keyName = "highlightStick",
name = "Highlight Stick",
description = "Highlight the retrievable stick in the Werewolf Agility Course",
position = 15
)
default boolean highlightStick()
{
return true;
}
@ConfigItem(
keyName = "stickHighlightColor",
name = "Stick Highlight Color",
description = "Color of highlighted stick",
position = 16
)
default Color stickHighlightColor()
{
return Color.RED;
}
@ConfigItem(
keyName = "highlightSepulchreNpcs",
name = "Highlight Projectiles",
description = "Highlights arrows and swords in the Sepulchre",
position = 17,
section = sepulchreSection
)
default boolean highlightSepulchreNpcs()
{
return true;
}
@ConfigItem(
keyName = "sepulchreHighlightColor",
name = "Projectile Color",
description = "Overlay color for arrows and swords",
position = 18,
section = sepulchreSection
)
default Color sepulchreHighlightColor()
{
return Color.GREEN;
}
@ConfigItem(
keyName = "highlightSepulchreObstacles",
name = "Highlight Obstacles",
description = "Highlights pillars and stairs in the Sepulchre",
position = 19,
section = sepulchreSection
)
default boolean highlightSepulchreObstacles()
{
return true;
}
@ConfigItem(
keyName = "highlightSepulchreSkilling",
name = "Highlight Skill Challenges",
description = "Highlights skilling challenges in the Sepulchre",
position = 20,
section = sepulchreSection
)
default boolean highlightSepulchreSkilling()
{
return true;
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2018, Cas <https://github.com/casvandongen>
* 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.agility;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.Tile;
import net.runelite.api.coords.LocalPoint;
import net.runelite.client.game.AgilityShortcut;
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;
class AgilityOverlay extends Overlay
{
private static final int MAX_DISTANCE = 2350;
private static final Color SHORTCUT_HIGH_LEVEL_COLOR = Color.ORANGE;
private final Client client;
private final AgilityPlugin plugin;
private final AgilityConfig config;
@Inject
private AgilityOverlay(Client client, AgilityPlugin plugin, AgilityConfig config)
{
super(plugin);
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
this.client = client;
this.plugin = plugin;
this.config = config;
}
@Override
public Dimension render(Graphics2D graphics)
{
LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation();
Point mousePosition = client.getMouseCanvasPosition();
final List<Tile> marksOfGrace = plugin.getMarksOfGrace();
final Tile stickTile = plugin.getStickTile();
plugin.getObstacles().forEach((object, obstacle) ->
{
if (Obstacles.SHORTCUT_OBSTACLE_IDS.containsKey(object.getId()) && !config.highlightShortcuts() ||
Obstacles.TRAP_OBSTACLE_IDS.contains(object.getId()) && !config.showTrapOverlay() ||
Obstacles.COURSE_OBSTACLE_IDS.contains(object.getId()) && !config.showClickboxes() ||
Obstacles.SEPULCHRE_OBSTACLE_IDS.contains(object.getId()) && !config.highlightSepulchreObstacles() ||
Obstacles.SEPULCHRE_SKILL_OBSTACLE_IDS.contains(object.getId()) && !config.highlightSepulchreSkilling())
{
return;
}
Tile tile = obstacle.getTile();
if (tile.getPlane() == client.getPlane()
&& object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE)
{
// This assumes that the obstacle is not clickable.
if (Obstacles.TRAP_OBSTACLE_IDS.contains(object.getId()))
{
Polygon polygon = object.getCanvasTilePoly();
if (polygon != null)
{
OverlayUtil.renderPolygon(graphics, polygon, config.getTrapColor());
}
return;
}
Shape objectClickbox = object.getClickbox();
if (objectClickbox != null)
{
AgilityShortcut agilityShortcut = obstacle.getShortcut();
Color configColor = agilityShortcut == null || agilityShortcut.getLevel() <= plugin.getAgilityLevel() ? config.getOverlayColor() : SHORTCUT_HIGH_LEVEL_COLOR;
if (config.highlightMarks() && !marksOfGrace.isEmpty())
{
configColor = config.getMarkColor();
}
if (Obstacles.PORTAL_OBSTACLE_IDS.contains(object.getId()))
{
if (config.highlightPortals())
{
configColor = config.getPortalsColor();
}
else
{
return;
}
}
if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY()))
{
graphics.setColor(configColor.darker());
}
else
{
graphics.setColor(configColor);
}
graphics.draw(objectClickbox);
graphics.setColor(new Color(configColor.getRed(), configColor.getGreen(), configColor.getBlue(), 50));
graphics.fill(objectClickbox);
}
}
});
if (config.highlightMarks() && !marksOfGrace.isEmpty())
{
for (Tile markOfGraceTile : marksOfGrace)
{
highlightTile(graphics, playerLocation, markOfGraceTile, config.getMarkColor());
}
}
if (stickTile != null && config.highlightStick())
{
highlightTile(graphics, playerLocation, stickTile, config.stickHighlightColor());
}
Set<NPC> npcs = plugin.getNpcs();
if (!npcs.isEmpty() && config.highlightSepulchreNpcs())
{
Color color = config.sepulchreHighlightColor();
for (NPC npc : npcs)
{
NPCComposition npcComposition = npc.getComposition();
int size = npcComposition.getSize();
LocalPoint lp = npc.getLocalLocation();
Polygon tilePoly = Perspective.getCanvasTileAreaPoly(client, lp, size);
if (tilePoly != null)
{
OverlayUtil.renderPolygon(graphics, tilePoly, color);
}
}
}
return null;
}
private void highlightTile(Graphics2D graphics, LocalPoint playerLocation, Tile tile, Color color)
{
if (tile.getPlane() == client.getPlane() && tile.getItemLayer() != null
&& tile.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE)
{
final Polygon poly = tile.getItemLayer().getCanvasTilePoly();
if (poly != null)
{
OverlayUtil.renderPolygon(graphics, poly, color);
}
}
}
}

View File

@@ -0,0 +1,500 @@
/*
* Copyright (c) 2018, 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.client.plugins.agility;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.ItemID;
import static net.runelite.api.ItemID.AGILITY_ARENA_TICKET;
import net.runelite.api.MenuAction;
import net.runelite.api.NPC;
import net.runelite.api.NullNpcID;
import net.runelite.api.Player;
import static net.runelite.api.Skill.AGILITY;
import net.runelite.api.Tile;
import net.runelite.api.TileItem;
import net.runelite.api.TileObject;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.DecorativeObjectChanged;
import net.runelite.api.events.DecorativeObjectDespawned;
import net.runelite.api.events.DecorativeObjectSpawned;
import net.runelite.api.events.GameObjectChanged;
import net.runelite.api.events.GameObjectDespawned;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.GroundObjectChanged;
import net.runelite.api.events.GroundObjectDespawned;
import net.runelite.api.events.GroundObjectSpawned;
import net.runelite.api.events.ItemDespawned;
import net.runelite.api.events.ItemSpawned;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.events.StatChanged;
import net.runelite.api.events.WallObjectChanged;
import net.runelite.api.events.WallObjectDespawned;
import net.runelite.api.events.WallObjectSpawned;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.OverlayMenuClicked;
import net.runelite.client.game.AgilityShortcut;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDependency;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.xptracker.XpTrackerPlugin;
import net.runelite.client.plugins.xptracker.XpTrackerService;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
@PluginDescriptor(
name = "Agility",
description = "Show helpful information about agility courses and obstacles",
tags = {"grace", "marks", "overlay", "shortcuts", "skilling", "traps", "sepulchre"}
)
@PluginDependency(XpTrackerPlugin.class)
@Slf4j
public class AgilityPlugin extends Plugin
{
private static final int AGILITY_ARENA_REGION_ID = 11157;
private static final Set<Integer> SEPULCHRE_NPCS = ImmutableSet.of(
NullNpcID.NULL_9672, NullNpcID.NULL_9673, NullNpcID.NULL_9674, // arrows
NullNpcID.NULL_9669, NullNpcID.NULL_9670, NullNpcID.NULL_9671 // swords
);
@Getter
private final Map<TileObject, Obstacle> obstacles = new HashMap<>();
@Getter
private final List<Tile> marksOfGrace = new ArrayList<>();
@Getter
private final Set<NPC> npcs = new HashSet<>();
@Inject
private OverlayManager overlayManager;
@Inject
private AgilityOverlay agilityOverlay;
@Inject
private LapCounterOverlay lapCounterOverlay;
@Inject
private Notifier notifier;
@Inject
private Client client;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private AgilityConfig config;
@Inject
private ItemManager itemManager;
@Inject
private XpTrackerService xpTrackerService;
@Getter
private AgilitySession session;
private int lastAgilityXp;
private WorldPoint lastArenaTicketPosition;
@Getter
private int agilityLevel;
@Getter(AccessLevel.PACKAGE)
private Tile stickTile;
@Provides
AgilityConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(AgilityConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(agilityOverlay);
overlayManager.add(lapCounterOverlay);
agilityLevel = client.getBoostedSkillLevel(AGILITY);
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(agilityOverlay);
overlayManager.remove(lapCounterOverlay);
marksOfGrace.clear();
obstacles.clear();
session = null;
agilityLevel = 0;
stickTile = null;
npcs.clear();
}
@Subscribe
public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked)
{
OverlayMenuEntry overlayMenuEntry = overlayMenuClicked.getEntry();
if (overlayMenuEntry.getMenuAction() == MenuAction.RUNELITE_OVERLAY
&& overlayMenuClicked.getOverlay() == lapCounterOverlay
&& overlayMenuClicked.getEntry().getOption().equals(LapCounterOverlay.AGILITY_RESET))
{
session = null;
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
switch (event.getGameState())
{
case HOPPING:
case LOGIN_SCREEN:
session = null;
lastArenaTicketPosition = null;
removeAgilityArenaTimer();
npcs.clear();
break;
case LOADING:
marksOfGrace.clear();
obstacles.clear();
stickTile = null;
break;
case LOGGED_IN:
if (!isInAgilityArena())
{
lastArenaTicketPosition = null;
removeAgilityArenaTimer();
}
break;
}
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (!config.showAgilityArenaTimer())
{
removeAgilityArenaTimer();
}
}
@Subscribe
public void onStatChanged(StatChanged statChanged)
{
if (statChanged.getSkill() != AGILITY)
{
return;
}
agilityLevel = statChanged.getBoostedLevel();
if (!config.showLapCount())
{
return;
}
// Determine how much EXP was actually gained
int agilityXp = client.getSkillExperience(AGILITY);
int skillGained = agilityXp - lastAgilityXp;
lastAgilityXp = agilityXp;
// Get course
Courses course = Courses.getCourse(client.getLocalPlayer().getWorldLocation().getRegionID());
if (course == null
|| (course.getCourseEndWorldPoints().length == 0
? Math.abs(course.getLastObstacleXp() - skillGained) > 1
: Arrays.stream(course.getCourseEndWorldPoints()).noneMatch(wp -> wp.equals(client.getLocalPlayer().getWorldLocation()))))
{
return;
}
if (session != null && session.getCourse() == course)
{
session.incrementLapCount(client, xpTrackerService);
}
else
{
session = new AgilitySession(course);
// New course found, reset lap count and set new course
session.resetLapCount();
session.incrementLapCount(client, xpTrackerService);
}
}
@Subscribe
public void onItemSpawned(ItemSpawned itemSpawned)
{
if (obstacles.isEmpty())
{
return;
}
final TileItem item = itemSpawned.getItem();
final Tile tile = itemSpawned.getTile();
if (item.getId() == ItemID.MARK_OF_GRACE)
{
marksOfGrace.add(tile);
}
if (item.getId() == ItemID.STICK)
{
stickTile = tile;
}
}
@Subscribe
public void onItemDespawned(ItemDespawned itemDespawned)
{
final TileItem item = itemDespawned.getItem();
final Tile tile = itemDespawned.getTile();
marksOfGrace.remove(tile);
if (item.getId() == ItemID.STICK && stickTile == tile)
{
stickTile = null;
}
}
@Subscribe
public void onGameTick(GameTick tick)
{
if (isInAgilityArena())
{
// Hint arrow has no plane, and always returns the current plane
WorldPoint newTicketPosition = client.getHintArrowPoint();
WorldPoint oldTickPosition = lastArenaTicketPosition;
lastArenaTicketPosition = newTicketPosition;
if (oldTickPosition != null && newTicketPosition != null
&& (oldTickPosition.getX() != newTicketPosition.getX() || oldTickPosition.getY() != newTicketPosition.getY()))
{
log.debug("Ticked position moved from {} to {}", oldTickPosition, newTicketPosition);
if (config.notifyAgilityArena())
{
notifier.notify("Ticket location changed");
}
if (config.showAgilityArenaTimer())
{
showNewAgilityArenaTimer();
}
}
}
}
private boolean isInAgilityArena()
{
Player local = client.getLocalPlayer();
if (local == null)
{
return false;
}
WorldPoint location = local.getWorldLocation();
return location.getRegionID() == AGILITY_ARENA_REGION_ID;
}
private void removeAgilityArenaTimer()
{
infoBoxManager.removeIf(infoBox -> infoBox instanceof AgilityArenaTimer);
}
private void showNewAgilityArenaTimer()
{
removeAgilityArenaTimer();
infoBoxManager.addInfoBox(new AgilityArenaTimer(this, itemManager.getImage(AGILITY_ARENA_TICKET)));
}
@Subscribe
public void onGameObjectSpawned(GameObjectSpawned event)
{
onTileObject(event.getTile(), null, event.getGameObject());
}
@Subscribe
public void onGameObjectChanged(GameObjectChanged event)
{
onTileObject(event.getTile(), event.getPrevious(), event.getGameObject());
}
@Subscribe
public void onGameObjectDespawned(GameObjectDespawned event)
{
onTileObject(event.getTile(), event.getGameObject(), null);
}
@Subscribe
public void onGroundObjectSpawned(GroundObjectSpawned event)
{
onTileObject(event.getTile(), null, event.getGroundObject());
}
@Subscribe
public void onGroundObjectChanged(GroundObjectChanged event)
{
onTileObject(event.getTile(), event.getPrevious(), event.getGroundObject());
}
@Subscribe
public void onGroundObjectDespawned(GroundObjectDespawned event)
{
onTileObject(event.getTile(), event.getGroundObject(), null);
}
@Subscribe
public void onWallObjectSpawned(WallObjectSpawned event)
{
onTileObject(event.getTile(), null, event.getWallObject());
}
@Subscribe
public void onWallObjectChanged(WallObjectChanged event)
{
onTileObject(event.getTile(), event.getPrevious(), event.getWallObject());
}
@Subscribe
public void onWallObjectDespawned(WallObjectDespawned event)
{
onTileObject(event.getTile(), event.getWallObject(), null);
}
@Subscribe
public void onDecorativeObjectSpawned(DecorativeObjectSpawned event)
{
onTileObject(event.getTile(), null, event.getDecorativeObject());
}
@Subscribe
public void onDecorativeObjectChanged(DecorativeObjectChanged event)
{
onTileObject(event.getTile(), event.getPrevious(), event.getDecorativeObject());
}
@Subscribe
public void onDecorativeObjectDespawned(DecorativeObjectDespawned event)
{
onTileObject(event.getTile(), event.getDecorativeObject(), null);
}
private void onTileObject(Tile tile, TileObject oldObject, TileObject newObject)
{
obstacles.remove(oldObject);
if (newObject == null)
{
return;
}
if (Obstacles.COURSE_OBSTACLE_IDS.contains(newObject.getId()) ||
Obstacles.PORTAL_OBSTACLE_IDS.contains(newObject.getId()) ||
(Obstacles.TRAP_OBSTACLE_IDS.contains(newObject.getId())
&& Obstacles.TRAP_OBSTACLE_REGIONS.contains(newObject.getWorldLocation().getRegionID())) ||
Obstacles.SEPULCHRE_OBSTACLE_IDS.contains(newObject.getId()) ||
Obstacles.SEPULCHRE_SKILL_OBSTACLE_IDS.contains(newObject.getId()))
{
obstacles.put(newObject, new Obstacle(tile, null));
}
if (Obstacles.SHORTCUT_OBSTACLE_IDS.containsKey(newObject.getId()))
{
AgilityShortcut closestShortcut = null;
int distance = -1;
// Find the closest shortcut to this object
for (AgilityShortcut shortcut : Obstacles.SHORTCUT_OBSTACLE_IDS.get(newObject.getId()))
{
if (!shortcut.matches(newObject))
{
continue;
}
if (shortcut.getWorldLocation() == null)
{
closestShortcut = shortcut;
break;
}
else
{
int newDistance = shortcut.getWorldLocation().distanceTo2D(newObject.getWorldLocation());
if (closestShortcut == null || newDistance < distance)
{
closestShortcut = shortcut;
distance = newDistance;
}
}
}
if (closestShortcut != null)
{
obstacles.put(newObject, new Obstacle(tile, closestShortcut));
}
}
}
@Subscribe
public void onNpcSpawned(NpcSpawned npcSpawned)
{
NPC npc = npcSpawned.getNpc();
if (SEPULCHRE_NPCS.contains(npc.getId()))
{
npcs.add(npc);
}
}
@Subscribe
public void onNpcDespawned(NpcDespawned npcDespawned)
{
NPC npc = npcDespawned.getNpc();
npcs.remove(npc);
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2018, Seth <http://github.com/sethtroll>
* 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.agility;
import com.google.common.collect.EvictingQueue;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Client;
import net.runelite.api.Skill;
import net.runelite.client.plugins.xptracker.XpTrackerService;
import java.time.Duration;
import java.time.Instant;
@Getter
@Setter
class AgilitySession
{
private final Courses course;
private Instant lastLapCompleted;
private int totalLaps;
private int lapsTillGoal;
private final EvictingQueue<Duration> lastLapTimes = EvictingQueue.create(30);
private int lapsPerHour;
AgilitySession(Courses course)
{
this.course = course;
}
void incrementLapCount(Client client, XpTrackerService xpTrackerService)
{
calculateLapsPerHour();
++totalLaps;
final int currentExp = client.getSkillExperience(Skill.AGILITY);
final int goalXp = xpTrackerService.getEndGoalXp(Skill.AGILITY);
final int goalRemainingXp = goalXp - currentExp;
double courseTotalExp = course.getTotalXp();
if (course == Courses.PYRAMID)
{
// agility pyramid has a bonus exp drop on the last obstacle that scales with player level and caps at 1000
// the bonus is not already accounted for in the total exp number in the courses enum
courseTotalExp += Math.min(300 + 8 * client.getRealSkillLevel(Skill.AGILITY), 1000);
}
lapsTillGoal = goalRemainingXp > 0 ? (int) Math.ceil(goalRemainingXp / courseTotalExp) : 0;
}
void calculateLapsPerHour()
{
Instant now = Instant.now();
if (lastLapCompleted != null)
{
Duration timeSinceLastLap = Duration.between(lastLapCompleted, now);
if (!timeSinceLastLap.isNegative())
{
lastLapTimes.add(timeSinceLastLap);
Duration sum = Duration.ZERO;
for (Duration lapTime : lastLapTimes)
{
sum = sum.plus(lapTime);
}
Duration averageLapTime = sum.dividedBy(lastLapTimes.size());
lapsPerHour = (int) (Duration.ofHours(1).toMillis() / averageLapTime.toMillis());
}
}
lastLapCompleted = now;
}
void resetLapCount()
{
totalLaps = 0;
lapsTillGoal = 0;
lastLapTimes.clear();
lapsPerHour = 0;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2018, Seth <http://github.com/sethtroll>
* 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.agility;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.coords.WorldPoint;
enum Courses
{
GNOME(86.5, 46, 9781),
DRAYNOR(120.0, 79, 12338),
AL_KHARID(180.0, 0, 13105, new WorldPoint(3299, 3194, 0)),
PYRAMID(722.0, 0, 13356, new WorldPoint(3364, 2830, 0)),
VARROCK(238.0, 125, 12853),
PENGUIN(540.0, 65, 10559),
BARBARIAN(139.5, 60, 10039),
CANIFIS(240.0, 175, 13878),
APE_ATOLL(580.0, 300, 11050),
FALADOR(440, 180, 12084),
WILDERNESS(571.0, 499, 11837),
WEREWOLF(730.0, 380, 14234),
SEERS(570.0, 435, 10806),
POLLNIVNEACH(890.0, 540, 13358),
RELLEKA(780.0, 475, 10553),
PRIFDDINAS(1337.0, 1037, 12895),
ARDOUGNE(793.0, 529, 10547);
private final static Map<Integer, Courses> coursesByRegion;
@Getter
private final double totalXp;
@Getter
private final int lastObstacleXp;
@Getter
private final int regionId;
@Getter
private final WorldPoint[] courseEndWorldPoints;
static
{
ImmutableMap.Builder<Integer, Courses> builder = new ImmutableMap.Builder<>();
for (Courses course : values())
{
builder.put(course.regionId, course);
}
coursesByRegion = builder.build();
}
Courses(double totalXp, int lastObstacleXp, int regionId, WorldPoint... courseEndWorldPoints)
{
this.totalXp = totalXp;
this.lastObstacleXp = lastObstacleXp;
this.regionId = regionId;
this.courseEndWorldPoints = courseEndWorldPoints;
}
static Courses getCourse(int regionId)
{
return coursesByRegion.get(regionId);
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2018, Seth <http://github.com/sethtroll>
* 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.agility;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.time.Duration;
import java.time.Instant;
import javax.inject.Inject;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.components.LineComponent;
class LapCounterOverlay extends OverlayPanel
{
static final String AGILITY_RESET = "Reset";
private final AgilityPlugin plugin;
private final AgilityConfig config;
@Inject
private LapCounterOverlay(AgilityPlugin plugin, AgilityConfig config)
{
super(plugin);
setPosition(OverlayPosition.TOP_LEFT);
setPriority(OverlayPriority.LOW);
this.plugin = plugin;
this.config = config;
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Agility overlay"));
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, AGILITY_RESET, "Agility overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
AgilitySession session = plugin.getSession();
if (!config.showLapCount() ||
session == null ||
session.getLastLapCompleted() == null ||
session.getCourse() == null)
{
return null;
}
Duration lapTimeout = Duration.ofMinutes(config.lapTimeout());
Duration sinceLap = Duration.between(session.getLastLapCompleted(), Instant.now());
if (sinceLap.compareTo(lapTimeout) >= 0)
{
// timeout session
session.setLastLapCompleted(null);
return null;
}
panelComponent.getChildren().add(LineComponent.builder()
.left("Total Laps:")
.right(Integer.toString(session.getTotalLaps()))
.build());
if (config.lapsToLevel() && session.getLapsTillGoal() > 0)
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Laps until goal:")
.right(Integer.toString(session.getLapsTillGoal()))
.build());
}
if (config.lapsPerHour() && session.getLapsPerHour() > 0)
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Laps per hour:")
.right(Integer.toString(session.getLapsPerHour()))
.build());
}
return super.render(graphics);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2019, MrGroggle
* 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 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.client.plugins.agility;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Value;
import net.runelite.api.Tile;
import net.runelite.client.game.AgilityShortcut;
@Value
@AllArgsConstructor
class Obstacle
{
private final Tile tile;
@Nullable
private final AgilityShortcut shortcut;
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2018, SomeoneWithAnInternetConnection
* 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.agility;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import java.util.List;
import java.util.Set;
import static net.runelite.api.NullObjectID.*;
import static net.runelite.api.ObjectID.*;
import net.runelite.client.game.AgilityShortcut;
class Obstacles
{
static final Set<Integer> COURSE_OBSTACLE_IDS = ImmutableSet.of(
// Gnome
OBSTACLE_NET_23134, TREE_BRANCH_23559, TREE_BRANCH_23560, OBSTACLE_NET_23135, OBSTACLE_PIPE_23138,
OBSTACLE_PIPE_23139, LOG_BALANCE_23145, BALANCING_ROPE_23557,
// Brimhaven
PLANK_3572, PLANK_3571, PLANK_3570, ROPE_SWING, PILLAR_3578, LOW_WALL, LOG_BALANCE, LOG_BALANCE_3557,
BALANCING_LEDGE_3561, BALANCING_LEDGE, MONKEY_BARS_3564, BALANCING_ROPE, HAND_HOLDS_3583,
// Draynor
ROUGH_WALL, TIGHTROPE, TIGHTROPE_11406, NARROW_WALL, WALL_11630, GAP_11631, CRATE_11632, STILE_7527,
// Al-Kharid
ROUGH_WALL_11633, TIGHTROPE_14398, CABLE, ZIP_LINE_14403, TROPICAL_TREE_14404, ROOF_TOP_BEAMS,
TIGHTROPE_14409, GAP_14399,
// Pyramid
STAIRS_10857, LOW_WALL_10865, LEDGE_10860, PLANK_10868, GAP_10882, LEDGE_10886, STAIRS_10857, GAP_10884,
GAP_10859, GAP_10861, LOW_WALL_10865, GAP_10859, LEDGE_10888, PLANK_10868, CLIMBING_ROCKS_10851, DOORWAY_10855,
// Varrock
ROUGH_WALL_14412, CLOTHES_LINE, GAP_14414, WALL_14832, GAP_14833, GAP_14834, GAP_14835, LEDGE_14836, EDGE,
// Penguin
STEPPING_STONE_21120, STEPPING_STONE_21126, STEPPING_STONE_21128, STEPPING_STONE_21129,
STEPPING_STONE_21130, STEPPING_STONE_21131, STEPPING_STONE_21132, STEPPING_STONE_21133,
ICICLES, ICE, ICE_21149, ICE_21150, ICE_21151, ICE_21152, ICE_21153, ICE_21154, ICE_21155, ICE_21156, GATE_21172,
// Barbarian
ROPESWING_23131, LOG_BALANCE_23144, OBSTACLE_NET_20211, BALANCING_LEDGE_23547, LADDER_16682, CRUMBLING_WALL_1948,
// Canifis
TALL_TREE_14843, GAP_14844, GAP_14845, GAP_14848, GAP_14846, POLEVAULT, GAP_14847, GAP_14897,
// Ape atoll
STEPPING_STONE_15412, TROPICAL_TREE_15414, MONKEYBARS_15417, SKULL_SLOPE_15483, ROPE_15487, TROPICAL_TREE_16062,
// Falador
ROUGH_WALL_14898, TIGHTROPE_14899, HAND_HOLDS_14901, GAP_14903, GAP_14904, TIGHTROPE_14905,
TIGHTROPE_14911, GAP_14919, LEDGE_14920, LEDGE_14921, LEDGE_14922, LEDGE_14923, LEDGE_14924, EDGE_14925,
// Wilderness
OBSTACLE_PIPE_23137, ROPESWING_23132, STEPPING_STONE_23556, LOG_BALANCE_23542, ROCKS_23640,
// Seers
WALL_14927, GAP_14928, TIGHTROPE_14932, GAP_14929, GAP_14930, EDGE_14931,
// Dorgesh-Kaan
CABLE_22569, CABLE_22572, LADDER_22564, JUTTING_WALL_22552, TUNNEL_22557, PYLON_22664,
CONSOLE, BOILER_22635, STAIRS_22650, STAIRS_22651, STAIRS_22609, STAIRS_22608,
// Pollniveach
BASKET_14935, MARKET_STALL_14936, BANNER_14937, GAP_14938, TREE_14939, ROUGH_WALL_14940,
MONKEYBARS, TREE_14944, DRYING_LINE,
// Rellaka
ROUGH_WALL_14946, GAP_14947, TIGHTROPE_14987, GAP_14990, GAP_14991, TIGHTROPE_14992, PILE_OF_FISH,
// Ardougne
WOODEN_BEAMS, GAP_15609, PLANK_26635, GAP_15610, GAP_15611, STEEP_ROOF, GAP_15612,
// Meiyerditch
NULL_12945, ROCK_17958, ROCK_17959, ROCK_17960, BOAT_17961, NULL_18122, NULL_18124, WALL_RUBBLE,
WALL_RUBBLE_18038, FLOORBOARDS, FLOORBOARDS_18071, FLOORBOARDS_18072, FLOORBOARDS_18073, NULL_18129, NULL_18130,
WALL_18078, NULL_18132, NULL_18133, NULL_18083, TUNNEL_18085, SHELF_18086, SHELF_18087, WALL_18088,
FLOORBOARDS_18089, FLOORBOARDS_18090, DOOR_18091, FLOORBOARDS_18093, FLOORBOARDS_18094, SHELF_18095,
SHELF_18096, FLOORBOARDS_18097, FLOORBOARDS_18098, WASHING_LINE_18099, WASHING_LINE_18100,
NULL_18135, NULL_18136, SHELF_18105, SHELF_18106, SHELF_18107, SHELF_18108, FLOORBOARDS_18109,
FLOORBOARDS_18110, FLOORBOARDS_18112, FLOORBOARDS_18111, FLOORBOARDS_18114, FLOORBOARDS_18113,
NULL_18116, FLOORBOARDS_18117, FLOORBOARDS_18118, STAIRS_DOWN, WALL_17980, BARRICADE_18054, LADDER_17999,
LADDER_18000, LADDER_18001, LADDER_18002, ROCKY_SURFACE, WALL_39172, WALL_39173,
// Werewolf
STEPPING_STONE_11643, HURDLE, HURDLE_11639, HURDLE_11640, PIPE_11657, SKULL_SLOPE, ZIP_LINE,
ZIP_LINE_11645, ZIP_LINE_11646,
// Prifddinas
LADDER_36221, TIGHTROPE_36225, CHIMNEY_36227, ROOF_EDGE, DARK_HOLE_36229, LADDER_36231, LADDER_36232,
ROPE_BRIDGE_36233, TIGHTROPE_36234, ROPE_BRIDGE_36235, TIGHTROPE_36236, TIGHTROPE_36237, DARK_HOLE_36238
);
static final Set<Integer> PORTAL_OBSTACLE_IDS = ImmutableSet.of(
// Prifddinas portals
NULL_36241, NULL_36242, NULL_36243, NULL_36244, NULL_36245, NULL_36246
);
static final Multimap<Integer, AgilityShortcut> SHORTCUT_OBSTACLE_IDS;
static final Set<Integer> TRAP_OBSTACLE_IDS = ImmutableSet.of(
// Agility pyramid
NULL_3550, NULL_10872, NULL_10873
);
static final List<Integer> TRAP_OBSTACLE_REGIONS = ImmutableList.of(12105, 13356);
static
{
final ImmutableMultimap.Builder<Integer, AgilityShortcut> builder = ImmutableMultimap.builder();
for (final AgilityShortcut item : AgilityShortcut.values())
{
for (int obstacle : item.getObstacleIds())
{
builder.put(obstacle, item);
}
}
SHORTCUT_OBSTACLE_IDS = builder.build();
}
static final Set<Integer> SEPULCHRE_OBSTACLE_IDS = ImmutableSet.of(
// Stairs and Platforms (and one Gate)
GATE_38460, PLATFORM_38455, PLATFORM_38456, PLATFORM_38457, PLATFORM_38458, PLATFORM_38459,
PLATFORM_38470, PLATFORM_38477, STAIRS_38462, STAIRS_38463, STAIRS_38464, STAIRS_38465,
STAIRS_38466, STAIRS_38467, STAIRS_38468, STAIRS_38469, STAIRS_38471, STAIRS_38472,
STAIRS_38473, STAIRS_38474, STAIRS_38475, STAIRS_38476
);
static final Set<Integer> SEPULCHRE_SKILL_OBSTACLE_IDS = ImmutableSet.of(
// Grapple, Portal, and Bridge skill obstacles
// They are multilocs, thus we use the NullObjectID
NULL_39524, NULL_39525, NULL_39526, NULL_39527, NULL_39528, NULL_39533
);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2019 Hydrox6 <ikada@protonmail.ch>
* 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.ammo;
import java.awt.image.BufferedImage;
import lombok.Getter;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.infobox.Counter;
import net.runelite.client.util.QuantityFormatter;
class AmmoCounter extends Counter
{
@Getter
private final int itemID;
private final String name;
AmmoCounter(Plugin plugin, int itemID, int count, String name, BufferedImage image)
{
super(image, plugin, count);
this.itemID = itemID;
this.name = name;
}
@Override
public String getText()
{
return QuantityFormatter.quantityToRSDecimalStack(getCount());
}
@Override
public String getTooltip()
{
return name;
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2019 Hydrox6 <ikada@protonmail.ch>
* 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.ammo;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.EquipmentInventorySlot;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemComposition;
import net.runelite.api.ItemContainer;
import net.runelite.api.events.ItemContainerChanged;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
@PluginDescriptor(
name = "Ammo",
description = "Shows the current ammo the player has equipped",
tags = {"bolts", "darts", "chinchompa", "equipment"}
)
public class AmmoPlugin extends Plugin
{
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ItemManager itemManager;
private AmmoCounter counterBox;
@Override
protected void startUp() throws Exception
{
clientThread.invokeLater(() ->
{
final ItemContainer container = client.getItemContainer(InventoryID.EQUIPMENT);
if (container != null)
{
checkInventory(container.getItems());
}
});
}
@Override
protected void shutDown() throws Exception
{
infoBoxManager.removeInfoBox(counterBox);
counterBox = null;
}
@Subscribe
public void onItemContainerChanged(ItemContainerChanged event)
{
if (event.getItemContainer() != client.getItemContainer(InventoryID.EQUIPMENT))
{
return;
}
checkInventory(event.getItemContainer().getItems());
}
private void checkInventory(final Item[] items)
{
// Check for weapon slot items. This overrides the ammo slot,
// as the player will use the thrown weapon (eg. chinchompas, knives, darts)
if (items.length > EquipmentInventorySlot.WEAPON.getSlotIdx())
{
final Item weapon = items[EquipmentInventorySlot.WEAPON.getSlotIdx()];
final ItemComposition weaponComp = itemManager.getItemComposition(weapon.getId());
if (weaponComp.isStackable())
{
updateInfobox(weapon, weaponComp);
return;
}
}
if (items.length <= EquipmentInventorySlot.AMMO.getSlotIdx())
{
removeInfobox();
return;
}
final Item ammo = items[EquipmentInventorySlot.AMMO.getSlotIdx()];
final ItemComposition comp = itemManager.getItemComposition(ammo.getId());
if (!comp.isStackable())
{
removeInfobox();
return;
}
updateInfobox(ammo, comp);
}
private void updateInfobox(final Item item, final ItemComposition comp)
{
if (counterBox != null && counterBox.getItemID() == item.getId())
{
counterBox.setCount(item.getQuantity());
return;
}
removeInfobox();
final BufferedImage image = itemManager.getImage(item.getId(), 5, false);
counterBox = new AmmoCounter(this, item.getId(), item.getQuantity(), comp.getName(), image);
infoBoxManager.addInfoBox(counterBox);
}
private void removeInfobox()
{
infoBoxManager.removeInfoBox(counterBox);
counterBox = null;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2017, Steve <steve.rs.dev@gmail.com>
* 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.xpglobes;
import java.time.Instant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.runelite.api.Skill;
@Getter
@Setter
@AllArgsConstructor
class XpGlobe
{
private Skill skill;
private int currentXp;
private int currentLevel;
private Instant time;
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright (c) 2017, Steve <steve.rs.dev@gmail.com>
* 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.xpglobes;
import java.awt.Color;
import net.runelite.client.config.Alpha;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Units;
@ConfigGroup("xpglobes")
public interface XpGlobesConfig extends Config
{
@ConfigItem(
keyName = "enableTooltips",
name = "Enable Tooltips",
description = "Configures whether or not to show tooltips",
position = 0
)
default boolean enableTooltips()
{
return true;
}
@ConfigItem(
keyName = "showXpLeft",
name = "Show XP Left",
description = "Shows XP Left inside the globe tooltip box",
position = 1
)
default boolean showXpLeft()
{
return true;
}
@ConfigItem(
keyName = "showActionsLeft",
name = "Show actions left",
description = "Shows the number of actions left inside the globe tooltip box",
position = 2
)
default boolean showActionsLeft()
{
return true;
}
@ConfigItem(
keyName = "showXpHour",
name = "Show XP/hr",
description = "Shows XP per hour inside the globe tooltip box",
position = 3
)
default boolean showXpHour()
{
return true;
}
@ConfigItem(
keyName = "hideMaxed",
name = "Hide maxed skills",
description = "Stop globes from showing up for level 99 skills",
position = 4
)
default boolean hideMaxed()
{
return false;
}
@ConfigItem(
keyName = "enableCustomArcColor",
name = "Enable custom arc color",
description = "Enables the custom coloring of the globe's arc instead of using the skill's default color.",
position = 5
)
default boolean enableCustomArcColor()
{
return false;
}
@Alpha
@ConfigItem(
keyName = "Progress arc color",
name = "Progress arc color",
description = "Change the color of the progress arc in the xp orb",
position = 6
)
default Color progressArcColor()
{
return Color.ORANGE;
}
@Alpha
@ConfigItem(
keyName = "Progress orb outline color",
name = "Progress orb outline color",
description = "Change the color of the progress orb outline",
position = 7
)
default Color progressOrbOutLineColor()
{
return Color.BLACK;
}
@Alpha
@ConfigItem(
keyName = "Progress orb background color",
name = "Progress orb background color",
description = "Change the color of the progress orb background",
position = 8
)
default Color progressOrbBackgroundColor()
{
return new Color(128, 128, 128, 127);
}
@ConfigItem(
keyName = "Progress arc width",
name = "Progress arc width",
description = "Change the stroke width of the progress arc",
position = 9
)
@Units(Units.PIXELS)
default int progressArcStrokeWidth()
{
return 2;
}
@ConfigItem(
keyName = "Orb size",
name = "Size of orbs",
description = "Change the size of the xp orbs",
position = 10
)
@Units(Units.PIXELS)
default int xpOrbSize()
{
return 40;
}
@ConfigItem(
keyName = "Orb duration",
name = "Duration of orbs",
description = "Change the duration the xp orbs are visible",
position = 11
)
@Units(Units.SECONDS)
default int xpOrbDuration()
{
return 10;
}
@ConfigItem(
keyName = "alignOrbsVertically",
name = "Vertical Orbs",
description = "Aligns the orbs vertically instead of horizontally.",
hidden = true
)
default boolean alignOrbsVertically()
{
return false;
}
@ConfigItem(
keyName = "alignOrbsVertically",
name = "",
description = ""
)
void setAlignOrbsVertically(Boolean alignOrbsVertically);
}

View File

@@ -0,0 +1,324 @@
/*
* Copyright (c) 2017, Steve <steve.rs.dev@gmail.com>
* 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.xpglobes;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.time.Instant;
import java.util.List;
import javax.inject.Inject;
import net.runelite.api.Client;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.api.Point;
import net.runelite.client.game.SkillIconManager;
import net.runelite.client.plugins.xptracker.XpActionType;
import net.runelite.client.plugins.xptracker.XpTrackerService;
import net.runelite.client.ui.SkillColor;
import net.runelite.client.ui.overlay.Overlay;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
import net.runelite.client.ui.overlay.tooltip.Tooltip;
import net.runelite.client.ui.overlay.tooltip.TooltipManager;
public class XpGlobesOverlay extends Overlay
{
private static final int MINIMUM_STEP = 10;
private static final int PROGRESS_RADIUS_START = 90;
private static final int PROGRESS_RADIUS_REMAINDER = 0;
private static final int TOOLTIP_RECT_SIZE_X = 150;
private static final Color DARK_OVERLAY_COLOR = new Color(0, 0, 0, 180);
static final String FLIP_ACTION = "Flip";
private final Client client;
private final XpGlobesPlugin plugin;
private final XpGlobesConfig config;
private final XpTrackerService xpTrackerService;
private final TooltipManager tooltipManager;
private final SkillIconManager iconManager;
private final Tooltip xpTooltip = new Tooltip(new PanelComponent());
@Inject
private XpGlobesOverlay(
Client client,
XpGlobesPlugin plugin,
XpGlobesConfig config,
XpTrackerService xpTrackerService,
SkillIconManager iconManager,
TooltipManager tooltipManager)
{
super(plugin);
this.iconManager = iconManager;
this.client = client;
this.plugin = plugin;
this.config = config;
this.xpTrackerService = xpTrackerService;
this.tooltipManager = tooltipManager;
this.xpTooltip.getComponent().setPreferredSize(new Dimension(TOOLTIP_RECT_SIZE_X, 0));
setPosition(OverlayPosition.TOP_CENTER);
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "XP Globes overlay"));
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, FLIP_ACTION, "XP Globes overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
final List<XpGlobe> xpGlobes = plugin.getXpGlobes();
final int queueSize = xpGlobes.size();
if (queueSize == 0)
{
return null;
}
int curDrawPosition = 0;
for (final XpGlobe xpGlobe : xpGlobes)
{
int startXp = xpTrackerService.getStartGoalXp(xpGlobe.getSkill());
int goalXp = xpTrackerService.getEndGoalXp(xpGlobe.getSkill());
if (config.alignOrbsVertically())
{
renderProgressCircle(graphics, xpGlobe, startXp, goalXp, 0, curDrawPosition, getBounds());
}
else
{
renderProgressCircle(graphics, xpGlobe, startXp, goalXp, curDrawPosition, 0, getBounds());
}
curDrawPosition += MINIMUM_STEP + config.xpOrbSize();
}
// Get length of markers
final int markersLength = (queueSize * (config.xpOrbSize())) + ((MINIMUM_STEP) * (queueSize - 1));
if (config.alignOrbsVertically())
{
return new Dimension(config.xpOrbSize(), markersLength);
}
else
{
return new Dimension(markersLength, config.xpOrbSize());
}
}
private double getSkillProgress(int startXp, int currentXp, int goalXp)
{
double xpGained = currentXp - startXp;
double xpGoal = goalXp - startXp;
return ((xpGained / xpGoal) * 100);
}
private double getSkillProgressRadius(int startXp, int currentXp, int goalXp)
{
return -(3.6 * getSkillProgress(startXp, currentXp, goalXp)); //arc goes backwards
}
private void renderProgressCircle(Graphics2D graphics, XpGlobe skillToDraw, int startXp, int goalXp, int x, int y, Rectangle bounds)
{
double radiusCurrentXp = getSkillProgressRadius(startXp, skillToDraw.getCurrentXp(), goalXp);
double radiusToGoalXp = 360; //draw a circle
Ellipse2D backgroundCircle = drawEllipse(graphics, x, y);
drawSkillImage(graphics, skillToDraw, x, y);
Point mouse = client.getMouseCanvasPosition();
int mouseX = mouse.getX() - bounds.x;
int mouseY = mouse.getY() - bounds.y;
// If mouse is hovering the globe
if (backgroundCircle.contains(mouseX, mouseY))
{
// Fill a darker overlay circle
graphics.setColor(DARK_OVERLAY_COLOR);
graphics.fill(backgroundCircle);
drawProgressLabel(graphics, skillToDraw, startXp, goalXp, x, y);
if (config.enableTooltips())
{
drawTooltip(skillToDraw, goalXp);
}
}
graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
drawProgressArc(
graphics,
x, y,
config.xpOrbSize(), config.xpOrbSize(),
PROGRESS_RADIUS_REMAINDER, radiusToGoalXp,
5,
config.progressOrbOutLineColor()
);
drawProgressArc(
graphics,
x, y,
config.xpOrbSize(), config.xpOrbSize(),
PROGRESS_RADIUS_START, radiusCurrentXp,
config.progressArcStrokeWidth(),
config.enableCustomArcColor() ? config.progressArcColor() : SkillColor.find(skillToDraw.getSkill()).getColor());
}
private void drawProgressLabel(Graphics2D graphics, XpGlobe globe, int startXp, int goalXp, int x, int y)
{
if (goalXp <= globe.getCurrentXp())
{
return;
}
// Convert to int just to limit the decimal cases
String progress = (int) (getSkillProgress(startXp, globe.getCurrentXp(), goalXp)) + "%";
final FontMetrics metrics = graphics.getFontMetrics();
int drawX = x + (config.xpOrbSize() / 2) - (metrics.stringWidth(progress) / 2);
int drawY = y + (config.xpOrbSize() / 2) + (metrics.getHeight() / 2);
OverlayUtil.renderTextLocation(graphics, new Point(drawX, drawY), progress, Color.WHITE);
}
private void drawProgressArc(Graphics2D graphics, int x, int y, int w, int h, double radiusStart, double radiusEnd, int strokeWidth, Color color)
{
Stroke stroke = graphics.getStroke();
graphics.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
graphics.setColor(color);
graphics.draw(new Arc2D.Double(
x, y,
w, h,
radiusStart, radiusEnd,
Arc2D.OPEN));
graphics.setStroke(stroke);
}
private Ellipse2D drawEllipse(Graphics2D graphics, int x, int y)
{
graphics.setColor(config.progressOrbBackgroundColor());
Ellipse2D ellipse = new Ellipse2D.Double(x, y, config.xpOrbSize(), config.xpOrbSize());
graphics.fill(ellipse);
graphics.draw(ellipse);
return ellipse;
}
private void drawSkillImage(Graphics2D graphics, XpGlobe xpGlobe, int x, int y)
{
BufferedImage skillImage = iconManager.getSkillImage(xpGlobe.getSkill());
if (skillImage == null)
{
return;
}
graphics.drawImage(
skillImage,
x + (config.xpOrbSize() / 2) - (skillImage.getWidth() / 2),
y + (config.xpOrbSize() / 2) - (skillImage.getHeight() / 2),
null
);
}
private void drawTooltip(XpGlobe mouseOverSkill, int goalXp)
{
// reset the timer on XpGlobe to prevent it from disappearing while hovered over it
mouseOverSkill.setTime(Instant.now());
String skillName = mouseOverSkill.getSkill().getName();
String skillLevel = Integer.toString(mouseOverSkill.getCurrentLevel());
DecimalFormat decimalFormat = new DecimalFormat("###,###,###");
String skillCurrentXp = decimalFormat.format(mouseOverSkill.getCurrentXp());
final PanelComponent xpTooltip = (PanelComponent) this.xpTooltip.getComponent();
xpTooltip.getChildren().clear();
xpTooltip.getChildren().add(LineComponent.builder()
.left(skillName)
.right(skillLevel)
.build());
xpTooltip.getChildren().add(LineComponent.builder()
.left("Current XP:")
.leftColor(Color.ORANGE)
.right(skillCurrentXp)
.build());
if (goalXp > mouseOverSkill.getCurrentXp())
{
XpActionType xpActionType = xpTrackerService.getActionType(mouseOverSkill.getSkill());
if (config.showActionsLeft())
{
int actionsLeft = xpTrackerService.getActionsLeft(mouseOverSkill.getSkill());
if (actionsLeft != Integer.MAX_VALUE)
{
String actionsLeftString = decimalFormat.format(actionsLeft);
xpTooltip.getChildren().add(LineComponent.builder()
.left(xpActionType.getLabel() + " left:")
.leftColor(Color.ORANGE)
.right(actionsLeftString)
.build());
}
}
if (config.showXpLeft())
{
int xpLeft = goalXp - mouseOverSkill.getCurrentXp();
String skillXpToLvl = decimalFormat.format(xpLeft);
xpTooltip.getChildren().add(LineComponent.builder()
.left("XP left:")
.leftColor(Color.ORANGE)
.right(skillXpToLvl)
.build());
}
if (config.showXpHour())
{
int xpHr = xpTrackerService.getXpHr(mouseOverSkill.getSkill());
if (xpHr != 0)
{
String xpHrString = decimalFormat.format(xpHr);
xpTooltip.getChildren().add(LineComponent.builder()
.left("XP per hour:")
.leftColor(Color.ORANGE)
.right(xpHrString)
.build());
}
}
}
tooltipManager.add(this.xpTooltip);
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (c) 2017, Steve <steve.rs.dev@gmail.com>
* 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.xpglobes;
import com.google.inject.Provides;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.Experience;
import net.runelite.api.MenuAction;
import net.runelite.api.Skill;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.StatChanged;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.OverlayMenuClicked;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDependency;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.xptracker.XpTrackerPlugin;
import net.runelite.client.task.Schedule;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(
name = "XP Globes",
description = "Show XP globes for the respective skill when gaining XP",
tags = {"experience", "levels", "overlay"},
enabledByDefault = false
)
@PluginDependency(XpTrackerPlugin.class)
public class XpGlobesPlugin extends Plugin
{
private static final int MAXIMUM_SHOWN_GLOBES = 5;
private XpGlobe[] globeCache = new XpGlobe[Skill.values().length - 1]; //overall does not trigger xp change event
@Getter
private final List<XpGlobe> xpGlobes = new ArrayList<>();
@Inject
private XpGlobesConfig config;
@Inject
private OverlayManager overlayManager;
@Inject
private XpGlobesOverlay overlay;
@Provides
XpGlobesConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(XpGlobesConfig.class);
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(overlay);
}
@Override
protected void shutDown() throws Exception
{
overlayManager.remove(overlay);
}
@Subscribe
public void onStatChanged(StatChanged statChanged)
{
Skill skill = statChanged.getSkill();
int currentXp = statChanged.getXp();
int currentLevel = statChanged.getLevel();
int skillIdx = skill.ordinal();
XpGlobe cachedGlobe = globeCache[skillIdx];
// StatChanged event occurs when stats drain/boost; check we have an change to actual xp
if (cachedGlobe != null && (cachedGlobe.getCurrentXp() >= currentXp))
{
return;
}
if (config.hideMaxed() && currentLevel >= Experience.MAX_REAL_LEVEL)
{
return;
}
if (cachedGlobe != null)
{
cachedGlobe.setSkill(skill);
cachedGlobe.setCurrentXp(currentXp);
cachedGlobe.setCurrentLevel(currentLevel);
cachedGlobe.setTime(Instant.now());
addXpGlobe(cachedGlobe);
}
else
{
// dont draw non cached globes, this is triggered on login to setup all of the initial values
globeCache[skillIdx] = new XpGlobe(skill, currentXp, currentLevel, Instant.now());
}
}
private void addXpGlobe(XpGlobe xpGlobe)
{
// insert the globe, ordered by skill, if it isn't already in the list to be drawn
int idx = Collections.binarySearch(xpGlobes, xpGlobe, Comparator.comparing(XpGlobe::getSkill));
if (idx < 0)
{
xpGlobes.add(-idx - 1, xpGlobe);
// remove the oldest globe if there are too many
if (xpGlobes.size() > MAXIMUM_SHOWN_GLOBES)
{
xpGlobes.stream()
.min(Comparator.comparing(XpGlobe::getTime))
.ifPresent(xpGlobes::remove);
}
}
}
@Schedule(
period = 1,
unit = ChronoUnit.SECONDS
)
public void removeExpiredXpGlobes()
{
if (!xpGlobes.isEmpty())
{
Instant expireTime = Instant.now()
.minusSeconds(config.xpOrbDuration());
xpGlobes.removeIf(globe -> globe.getTime().isBefore(expireTime));
}
}
private void resetGlobeState()
{
xpGlobes.clear();
globeCache = new XpGlobe[Skill.values().length - 1];
}
@Subscribe
public void onOverlayMenuClicked(final OverlayMenuClicked event)
{
if (!(event.getEntry().getMenuAction() == MenuAction.RUNELITE_OVERLAY
&& event.getOverlay() == overlay))
{
return;
}
if (event.getEntry().getOption().equals(XpGlobesOverlay.FLIP_ACTION))
{
config.setAlignOrbsVertically(!config.alignOrbsVertically());
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
switch (event.getGameState())
{
case HOPPING:
case LOGGING_IN:
resetGlobeState();
break;
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.xptracker;
import lombok.Data;
@Data
class XpAction
{
private int actions = 0;
private boolean actionsHistoryInitialized = false;
private int[] actionExps = new int[10];
private int actionExpIndex = 0;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.xptracker;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum XpActionType
{
EXPERIENCE("Actions"),
ACTOR_HEALTH("Kills");
private final String label;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2020, Dasgust <dasgust@gmail.com>
* 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.xptracker;
public enum XpGoalTimeType
{
DAYS,
HOURS,
SHORT
}

View File

@@ -0,0 +1,347 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.xptracker;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.Skill;
import net.runelite.api.WorldType;
import net.runelite.client.game.SkillIconManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.DynamicGridLayout;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.SkillColor;
import net.runelite.client.ui.components.MouseDragEventForwarder;
import net.runelite.client.ui.components.ProgressBar;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.LinkBrowser;
import net.runelite.client.util.QuantityFormatter;
class XpInfoBox extends JPanel
{
static final DecimalFormat TWO_DECIMAL_FORMAT = new DecimalFormat("0.00");
static
{
TWO_DECIMAL_FORMAT.setRoundingMode(RoundingMode.DOWN);
}
// Templates
private static final String HTML_TOOL_TIP_TEMPLATE =
"<html>%s %s done<br/>"
+ "%s %s/hr<br/>"
+ "%s %s</html>";
private static final String HTML_LABEL_TEMPLATE =
"<html><body style='color:%s'>%s<span style='color:white'>%s</span></body></html>";
private static final String REMOVE_STATE = "Remove from canvas";
private static final String ADD_STATE = "Add to canvas";
// Instance members
private final JComponent panel;
@Getter(AccessLevel.PACKAGE)
private final Skill skill;
/* The tracker's wrapping container */
private final JPanel container = new JPanel();
/* Contains the skill icon and the stats panel */
private final JPanel headerPanel = new JPanel();
/* Contains all the skill information (exp gained, per hour, etc) */
private final JPanel statsPanel = new JPanel();
private final ProgressBar progressBar = new ProgressBar();
private final JLabel topLeftStat = new JLabel();
private final JLabel bottomLeftStat = new JLabel();
private final JLabel topRightStat = new JLabel();
private final JLabel bottomRightStat = new JLabel();
private final JMenuItem pauseSkill = new JMenuItem("Pause");
private final JMenuItem canvasItem = new JMenuItem(ADD_STATE);
private final XpTrackerConfig xpTrackerConfig;
private boolean paused = false;
XpInfoBox(XpTrackerPlugin xpTrackerPlugin, XpTrackerConfig xpTrackerConfig, Client client, JComponent panel, Skill skill, SkillIconManager iconManager)
{
this.xpTrackerConfig = xpTrackerConfig;
this.panel = panel;
this.skill = skill;
setLayout(new BorderLayout());
setBorder(new EmptyBorder(5, 0, 0, 0));
container.setLayout(new BorderLayout());
container.setBackground(ColorScheme.DARKER_GRAY_COLOR);
// Create open xp tracker menu
final JMenuItem openXpTracker = new JMenuItem("Open Wise Old Man");
openXpTracker.addActionListener(e -> LinkBrowser.browse(XpPanel.buildXpTrackerUrl(
client.getLocalPlayer(), skill, client.getWorldType().contains(WorldType.LEAGUE))));
// Create reset menu
final JMenuItem reset = new JMenuItem("Reset");
reset.addActionListener(e -> xpTrackerPlugin.resetSkillState(skill));
// Create reset others menu
final JMenuItem resetOthers = new JMenuItem("Reset others");
resetOthers.addActionListener(e -> xpTrackerPlugin.resetOtherSkillState(skill));
// Create reset others menu
pauseSkill.addActionListener(e -> xpTrackerPlugin.pauseSkill(skill, !paused));
// Create popup menu
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
popupMenu.add(openXpTracker);
popupMenu.add(reset);
popupMenu.add(resetOthers);
popupMenu.add(pauseSkill);
popupMenu.add(canvasItem);
popupMenu.addPopupMenuListener(new PopupMenuListener()
{
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent)
{
canvasItem.setText(xpTrackerPlugin.hasOverlay(skill) ? REMOVE_STATE : ADD_STATE);
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent)
{
}
@Override
public void popupMenuCanceled(PopupMenuEvent popupMenuEvent)
{
}
});
canvasItem.addActionListener(e ->
{
if (canvasItem.getText().equals(REMOVE_STATE))
{
xpTrackerPlugin.removeOverlay(skill);
}
else
{
xpTrackerPlugin.addOverlay(skill);
}
});
JLabel skillIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(skill)));
skillIcon.setHorizontalAlignment(SwingConstants.CENTER);
skillIcon.setVerticalAlignment(SwingConstants.CENTER);
skillIcon.setPreferredSize(new Dimension(35, 35));
headerPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
headerPanel.setLayout(new BorderLayout());
statsPanel.setLayout(new DynamicGridLayout(2, 2));
statsPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
statsPanel.setBorder(new EmptyBorder(9, 2, 9, 2));
topLeftStat.setFont(FontManager.getRunescapeSmallFont());
bottomLeftStat.setFont(FontManager.getRunescapeSmallFont());
topRightStat.setFont(FontManager.getRunescapeSmallFont());
bottomRightStat.setFont(FontManager.getRunescapeSmallFont());
statsPanel.add(topLeftStat); // top left
statsPanel.add(topRightStat); // top right
statsPanel.add(bottomLeftStat); // bottom left
statsPanel.add(bottomRightStat); // bottom right
headerPanel.add(skillIcon, BorderLayout.WEST);
headerPanel.add(statsPanel, BorderLayout.CENTER);
JPanel progressWrapper = new JPanel();
progressWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
progressWrapper.setLayout(new BorderLayout());
progressWrapper.setBorder(new EmptyBorder(0, 7, 7, 7));
progressBar.setMaximumValue(100);
progressBar.setBackground(new Color(61, 56, 49));
progressBar.setForeground(SkillColor.find(skill).getColor());
progressBar.setDimmedText("Paused");
progressWrapper.add(progressBar, BorderLayout.NORTH);
container.add(headerPanel, BorderLayout.NORTH);
container.add(progressWrapper, BorderLayout.SOUTH);
container.setComponentPopupMenu(popupMenu);
progressBar.setComponentPopupMenu(popupMenu);
// forward mouse drag events to parent panel for drag and drop reordering
MouseDragEventForwarder mouseDragEventForwarder = new MouseDragEventForwarder(panel);
container.addMouseListener(mouseDragEventForwarder);
container.addMouseMotionListener(mouseDragEventForwarder);
progressBar.addMouseListener(mouseDragEventForwarder);
progressBar.addMouseMotionListener(mouseDragEventForwarder);
add(container, BorderLayout.NORTH);
}
void reset()
{
canvasItem.setText(ADD_STATE);
panel.remove(this);
panel.revalidate();
}
void update(boolean updated, boolean paused, XpSnapshotSingle xpSnapshotSingle)
{
SwingUtilities.invokeLater(() -> rebuildAsync(updated, paused, xpSnapshotSingle));
}
private void rebuildAsync(boolean updated, boolean skillPaused, XpSnapshotSingle xpSnapshotSingle)
{
if (updated)
{
if (getParent() != panel)
{
panel.add(this);
panel.revalidate();
}
if (xpTrackerConfig.prioritizeRecentXpSkills())
{
panel.setComponentZOrder(this, 0);
}
paused = skillPaused;
// Update progress bar
progressBar.setValue((int) xpSnapshotSingle.getSkillProgressToGoal());
progressBar.setCenterLabel(xpTrackerConfig.progressBarLabel().getValueFunc().apply(xpSnapshotSingle));
progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getStartLevel());
progressBar.setRightLabel(xpSnapshotSingle.getEndGoalXp() == Experience.MAX_SKILL_XP
? "200M"
: "Lvl. " + xpSnapshotSingle.getEndLevel());
// Add intermediate level positions to progressBar
if (xpTrackerConfig.showIntermediateLevels() && xpSnapshotSingle.getEndLevel() - xpSnapshotSingle.getStartLevel() > 1)
{
final List<Integer> positions = new ArrayList<>();
for (int level = xpSnapshotSingle.getStartLevel() + 1; level < xpSnapshotSingle.getEndLevel(); level++)
{
double relativeStartExperience = Experience.getXpForLevel(level) - xpSnapshotSingle.getStartGoalXp();
double relativeEndExperience = xpSnapshotSingle.getEndGoalXp() - xpSnapshotSingle.getStartGoalXp();
positions.add((int) (relativeStartExperience / relativeEndExperience * 100));
}
progressBar.setPositions(positions);
}
else
{
progressBar.setPositions(Collections.emptyList());
}
XpProgressBarLabel tooltipLabel = xpTrackerConfig.progressBarTooltipLabel();
progressBar.setToolTipText(String.format(
HTML_TOOL_TIP_TEMPLATE,
xpSnapshotSingle.getActionsInSession(),
xpSnapshotSingle.getActionType().getLabel(),
xpSnapshotSingle.getActionsPerHour(),
xpSnapshotSingle.getActionType().getLabel(),
tooltipLabel.getValueFunc().apply(xpSnapshotSingle),
tooltipLabel == XpProgressBarLabel.PERCENTAGE ? "of goal" : "till goal lvl"));
progressBar.setDimmed(skillPaused);
progressBar.repaint();
}
else if (!paused && skillPaused)
{
// React to the skill state now being paused
progressBar.setDimmed(true);
progressBar.repaint();
paused = true;
pauseSkill.setText("Unpause");
}
else if (paused && !skillPaused)
{
// React to the skill being unpaused (without update)
progressBar.setDimmed(false);
progressBar.repaint();
paused = false;
pauseSkill.setText("Pause");
}
// Update information labels
// Update exp per hour separately, every time (not only when there's an update)
topLeftStat.setText(htmlLabel(xpTrackerConfig.xpPanelLabel1(), xpSnapshotSingle));
topRightStat.setText(htmlLabel(xpTrackerConfig.xpPanelLabel2(), xpSnapshotSingle));
bottomLeftStat.setText(htmlLabel(xpTrackerConfig.xpPanelLabel3(), xpSnapshotSingle));
bottomRightStat.setText(htmlLabel(xpTrackerConfig.xpPanelLabel4(), xpSnapshotSingle));
}
static String htmlLabel(XpPanelLabel panelLabel, XpSnapshotSingle xpSnapshotSingle)
{
String key = panelLabel.getActionKey(xpSnapshotSingle) + ": ";
String value = panelLabel.getValueFunc().apply(xpSnapshotSingle);
return htmlLabel(key, value);
}
static String htmlLabel(String key, int value)
{
String valueStr = QuantityFormatter.quantityToRSDecimalStack(value, true);
return htmlLabel(key, valueStr);
}
static String htmlLabel(String key, String valueStr)
{
return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.xptracker;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.api.Experience;
import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG;
import net.runelite.api.Skill;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.SkillColor;
import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.components.ComponentOrientation;
import net.runelite.client.ui.overlay.components.ImageComponent;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.PanelComponent;
import net.runelite.client.ui.overlay.components.ProgressBarComponent;
import net.runelite.client.ui.overlay.components.SplitComponent;
class XpInfoBoxOverlay extends OverlayPanel
{
private static final int BORDER_SIZE = 2;
private static final int XP_AND_PROGRESS_BAR_GAP = 2;
private static final int XP_AND_ICON_GAP = 4;
private static final Rectangle XP_AND_ICON_COMPONENT_BORDER = new Rectangle(2, 1, 4, 0);
private final PanelComponent iconXpSplitPanel = new PanelComponent();
private final XpTrackerPlugin plugin;
private final XpTrackerConfig config;
@Getter(AccessLevel.PACKAGE)
private final Skill skill;
private final BufferedImage icon;
XpInfoBoxOverlay(
XpTrackerPlugin plugin,
XpTrackerConfig config,
Skill skill,
BufferedImage icon)
{
super(plugin);
this.plugin = plugin;
this.config = config;
this.skill = skill;
this.icon = icon;
panelComponent.setBorder(new Rectangle(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE));
panelComponent.setGap(new Point(0, XP_AND_PROGRESS_BAR_GAP));
iconXpSplitPanel.setBorder(XP_AND_ICON_COMPONENT_BORDER);
iconXpSplitPanel.setBackgroundColor(null);
getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "XP Tracker overlay"));
}
@Override
public Dimension render(Graphics2D graphics)
{
iconXpSplitPanel.getChildren().clear();
//Setting the font to rs small font so that the overlay isn't huge
graphics.setFont(FontManager.getRunescapeSmallFont());
final XpSnapshotSingle snapshot = plugin.getSkillSnapshot(skill);
final String leftStr = config.onScreenDisplayMode().getActionKey(snapshot);
final String rightNum = config.onScreenDisplayMode().getValueFunc().apply(snapshot);
final LineComponent xpLine = LineComponent.builder()
.left(leftStr + ":")
.right(rightNum)
.build();
final String bottomLeftStr = config.onScreenDisplayModeBottom().getActionKey(snapshot);
final String bottomRightNum = config.onScreenDisplayModeBottom().getValueFunc().apply(snapshot);
final LineComponent xpLineBottom = LineComponent.builder()
.left(bottomLeftStr + ":")
.right(bottomRightNum)
.build();
final SplitComponent xpSplit = SplitComponent.builder()
.first(xpLine)
.second(xpLineBottom)
.orientation(ComponentOrientation.VERTICAL)
.build();
final ImageComponent imageComponent = new ImageComponent(icon);
final SplitComponent iconXpSplit = SplitComponent.builder()
.first(imageComponent)
.second(xpSplit)
.orientation(ComponentOrientation.HORIZONTAL)
.gap(new Point(XP_AND_ICON_GAP, 0))
.build();
iconXpSplitPanel.getChildren().add(iconXpSplit);
final ProgressBarComponent progressBarComponent = new ProgressBarComponent();
progressBarComponent.setBackgroundColor(new Color(61, 56, 49));
progressBarComponent.setForegroundColor(SkillColor.find(skill).getColor());
progressBarComponent.setLeftLabel(String.valueOf(snapshot.getStartLevel()));
progressBarComponent.setRightLabel(snapshot.getEndGoalXp() == Experience.MAX_SKILL_XP
? "200M"
: String.valueOf(snapshot.getEndLevel()));
progressBarComponent.setValue(snapshot.getSkillProgressToGoal());
panelComponent.getChildren().add(iconXpSplitPanel);
panelComponent.getChildren().add(progressBarComponent);
return super.render(graphics);
}
@Override
public String getName()
{
return super.getName() + skill.getName();
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright (c) 2017, Cameron <moberg@tuta.io>
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* 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.xptracker;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.Skill;
import net.runelite.api.WorldType;
import net.runelite.client.game.SkillIconManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.DragAndDropReorderPane;
import net.runelite.client.ui.components.PluginErrorPanel;
import net.runelite.client.util.LinkBrowser;
import okhttp3.HttpUrl;
class XpPanel extends PluginPanel
{
private final Map<Skill, XpInfoBox> infoBoxes = new HashMap<>();
private final JLabel overallExpGained = new JLabel(XpInfoBox.htmlLabel("Gained: ", 0));
private final JLabel overallExpHour = new JLabel(XpInfoBox.htmlLabel("Per hour: ", 0));
private final JPanel overallPanel = new JPanel();
/* This displays the "No exp gained" text */
private final PluginErrorPanel errorPanel = new PluginErrorPanel();
XpPanel(XpTrackerPlugin xpTrackerPlugin, XpTrackerConfig xpTrackerConfig, Client client, SkillIconManager iconManager)
{
super();
setBorder(new EmptyBorder(6, 6, 6, 6));
setBackground(ColorScheme.DARK_GRAY_COLOR);
setLayout(new BorderLayout());
final JPanel layoutPanel = new JPanel();
BoxLayout boxLayout = new BoxLayout(layoutPanel, BoxLayout.Y_AXIS);
layoutPanel.setLayout(boxLayout);
add(layoutPanel, BorderLayout.NORTH);
overallPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
overallPanel.setLayout(new BorderLayout());
overallPanel.setVisible(false); // this will only become visible when the player gets exp
// Create open xp tracker menu
final JMenuItem openXpTracker = new JMenuItem("Open Wise Old Man");
openXpTracker.addActionListener(e -> LinkBrowser.browse(XpPanel.buildXpTrackerUrl(
client.getLocalPlayer(), Skill.OVERALL, client.getWorldType().contains(WorldType.LEAGUE))));
// Create reset all menu
final JMenuItem reset = new JMenuItem("Reset All");
reset.addActionListener(e -> xpTrackerPlugin.resetAndInitState());
// Create pause all menu
final JMenuItem pauseAll = new JMenuItem("Pause All");
pauseAll.addActionListener(e -> xpTrackerPlugin.pauseAllSkills(true));
// Create unpause all menu
final JMenuItem unpauseAll = new JMenuItem("Unpause All");
unpauseAll.addActionListener(e -> xpTrackerPlugin.pauseAllSkills(false));
// Create popup menu
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
popupMenu.add(openXpTracker);
popupMenu.add(reset);
popupMenu.add(pauseAll);
popupMenu.add(unpauseAll);
overallPanel.setComponentPopupMenu(popupMenu);
final JLabel overallIcon = new JLabel(new ImageIcon(iconManager.getSkillImage(Skill.OVERALL)));
final JPanel overallInfo = new JPanel();
overallInfo.setBackground(ColorScheme.DARKER_GRAY_COLOR);
overallInfo.setLayout(new GridLayout(2, 1));
overallInfo.setBorder(new EmptyBorder(0, 10, 0, 0));
overallExpGained.setFont(FontManager.getRunescapeSmallFont());
overallExpHour.setFont(FontManager.getRunescapeSmallFont());
overallInfo.add(overallExpGained);
overallInfo.add(overallExpHour);
overallPanel.add(overallIcon, BorderLayout.WEST);
overallPanel.add(overallInfo, BorderLayout.CENTER);
final JComponent infoBoxPanel = new DragAndDropReorderPane();
layoutPanel.add(overallPanel);
layoutPanel.add(infoBoxPanel);
for (Skill skill : Skill.values())
{
if (skill == Skill.OVERALL)
{
break;
}
infoBoxes.put(skill, new XpInfoBox(xpTrackerPlugin, xpTrackerConfig, client, infoBoxPanel, skill, iconManager));
}
errorPanel.setContent("Exp trackers", "You have not gained experience yet.");
add(errorPanel);
}
static String buildXpTrackerUrl(final Actor player, final Skill skill, boolean leagueWorld)
{
if (player == null)
{
return "";
}
final String host = leagueWorld ? "trailblazer.wiseoldman.net" : "wiseoldman.net";
return new HttpUrl.Builder()
.scheme("https")
.host(host)
.addPathSegment("players")
.addPathSegment(player.getName())
.addPathSegment("gained")
.addPathSegment("skilling")
.addQueryParameter("metric", skill.getName().toLowerCase())
.addQueryParameter("period", "week")
.build()
.toString();
}
void resetAllInfoBoxes()
{
infoBoxes.forEach((skill, xpInfoBox) -> xpInfoBox.reset());
}
void resetSkill(Skill skill)
{
XpInfoBox xpInfoBox = infoBoxes.get(skill);
if (xpInfoBox != null)
{
xpInfoBox.reset();
}
}
void updateSkillExperience(boolean updated, boolean paused, Skill skill, XpSnapshotSingle xpSnapshotSingle)
{
final XpInfoBox xpInfoBox = infoBoxes.get(skill);
if (xpInfoBox != null)
{
xpInfoBox.update(updated, paused, xpSnapshotSingle);
}
}
void updateTotal(XpSnapshotSingle xpSnapshotTotal)
{
// if player has gained exp and hasn't switched displays yet, hide error panel and show overall info
if (xpSnapshotTotal.getXpGainedInSession() > 0 && !overallPanel.isVisible())
{
overallPanel.setVisible(true);
remove(errorPanel);
}
else if (xpSnapshotTotal.getXpGainedInSession() == 0 && overallPanel.isVisible())
{
overallPanel.setVisible(false);
add(errorPanel);
}
SwingUtilities.invokeLater(() -> rebuildAsync(xpSnapshotTotal));
}
private void rebuildAsync(XpSnapshotSingle xpSnapshotTotal)
{
overallExpGained.setText(XpInfoBox.htmlLabel("Gained: ", xpSnapshotTotal.getXpGainedInSession()));
overallExpHour.setText(XpInfoBox.htmlLabel("Per hour: ", xpSnapshotTotal.getXpPerHour()));
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.xptracker;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.client.util.QuantityFormatter;
@Getter
@AllArgsConstructor
public enum XpPanelLabel
{
TIME_TO_LEVEL("TTL", XpSnapshotSingle::getTimeTillGoalShort),
XP_GAINED("XP Gained", snap -> format(snap.getXpGainedInSession())),
XP_HOUR("XP/hr", snap -> format(snap.getXpPerHour())),
XP_LEFT("XP Left", snap -> format(snap.getXpRemainingToGoal())),
ACTIONS_LEFT("Actions", snap -> format(snap.getActionsRemainingToGoal())),
ACTIONS_HOUR("Actions/hr", snap -> format(snap.getActionsPerHour())),
ACTIONS_DONE("Actions Done", snap -> format(snap.getActionsInSession())),
;
private final String key;
private final Function<XpSnapshotSingle, String> valueFunc;
/**
* Get the action key label based on if the Action type is an xp drop or kill
*
* @param snapshot
* @return
*/
public String getActionKey(XpSnapshotSingle snapshot)
{
String actionKey = key;
if (snapshot.getActionType() == XpActionType.ACTOR_HEALTH)
{
return actionKey.replace("Action", "Kill");
}
return actionKey;
}
private static String format(int val)
{
return QuantityFormatter.quantityToRSDecimalStack(val, true);
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2018, Levi <me@levischuck.com>
* 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.xptracker;
import java.util.EnumMap;
import java.util.Map;
import net.runelite.api.Skill;
class XpPauseState
{
// Internal state
private final Map<Skill, XpPauseStateSingle> skillPauses = new EnumMap<>(Skill.class);
private boolean cachedIsLoggedIn = false;
boolean pauseSkill(Skill skill)
{
return findPauseState(skill).manualPause();
}
boolean unpauseSkill(Skill skill)
{
return findPauseState(skill).unpause();
}
boolean isPaused(Skill skill)
{
return findPauseState(skill).isPaused();
}
void tickXp(Skill skill, long currentXp, int pauseAfterMinutes)
{
final XpPauseStateSingle state = findPauseState(skill);
if (state.getXp() != currentXp)
{
state.xpChanged(currentXp);
}
else if (pauseAfterMinutes > 0)
{
final long now = System.currentTimeMillis();
final int pauseAfterMillis = pauseAfterMinutes * 60 * 1000;
final long lastChangeMillis = state.getLastChangeMillis();
// When config.pauseSkillAfter is 0, it is effectively disabled
if (lastChangeMillis != 0 && (now - lastChangeMillis) >= pauseAfterMillis)
{
state.timeout();
}
}
}
void tickLogout(boolean pauseOnLogout, boolean loggedIn)
{
// Deduplicated login and logout calls
if (!cachedIsLoggedIn && loggedIn)
{
cachedIsLoggedIn = true;
for (Skill skill : Skill.values())
{
findPauseState(skill).login();
}
}
else if (cachedIsLoggedIn && !loggedIn)
{
cachedIsLoggedIn = false;
// If configured, then let the pause state know to pause with reason: logout
if (pauseOnLogout)
{
for (Skill skill : Skill.values())
{
findPauseState(skill).logout();
}
}
}
}
private XpPauseStateSingle findPauseState(Skill skill)
{
return skillPauses.computeIfAbsent(skill, XpPauseStateSingle::new);
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2018, Levi <me@levischuck.com>
* 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.xptracker;
import java.util.EnumSet;
import java.util.Set;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.api.Skill;
@RequiredArgsConstructor
class XpPauseStateSingle
{
@Getter
private final Skill skill;
private final Set<XpPauseReason> pauseReasons = EnumSet.noneOf(XpPauseReason.class);
@Getter
private long lastChangeMillis;
@Getter
private long xp;
boolean isPaused()
{
return !pauseReasons.isEmpty();
}
boolean login()
{
return pauseReasons.remove(XpPauseReason.PAUSED_LOGOUT);
}
boolean logout()
{
return pauseReasons.add(XpPauseReason.PAUSED_LOGOUT);
}
boolean timeout()
{
return pauseReasons.add(XpPauseReason.PAUSED_TIMEOUT);
}
boolean manualPause()
{
return pauseReasons.add(XpPauseReason.PAUSE_MANUAL);
}
boolean xpChanged(long xp)
{
this.xp = xp;
this.lastChangeMillis = System.currentTimeMillis();
return clearAll();
}
boolean unpause()
{
this.lastChangeMillis = System.currentTimeMillis();
return clearAll();
}
private boolean clearAll()
{
if (pauseReasons.isEmpty())
{
return false;
}
pauseReasons.clear();
return true;
}
private enum XpPauseReason
{
PAUSE_MANUAL,
PAUSED_LOGOUT,
PAUSED_TIMEOUT
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.xptracker;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.function.Function;
import static net.runelite.client.plugins.xptracker.XpInfoBox.TWO_DECIMAL_FORMAT;
@Getter
@AllArgsConstructor
public enum XpProgressBarLabel
{
PERCENTAGE((snap) -> TWO_DECIMAL_FORMAT.format(snap.getSkillProgressToGoal()) + "%"),
TIME_TO_LEVEL(XpSnapshotSingle::getTimeTillGoal),
HOURS_TO_LEVEL(XpSnapshotSingle::getTimeTillGoalHours)
;
private final Function<XpSnapshotSingle, String> valueFunc;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2018, Levi <me@levischuck.com>
* 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.xptracker;
import lombok.Builder;
import lombok.Value;
@Builder
@Value
class XpSnapshotSingle
{
private XpActionType actionType;
private int startLevel;
private int endLevel;
private int startGoalXp;
private int endGoalXp;
private int xpGainedInSession;
private int xpRemainingToGoal;
private int xpPerHour;
private double skillProgressToGoal;
private int actionsInSession;
private int actionsRemainingToGoal;
private int actionsPerHour;
private String timeTillGoal;
private String timeTillGoalHours;
private String timeTillGoalShort;
}

View File

@@ -0,0 +1,233 @@
/*
* Copyright (c) 2018, Levi <me@levischuck.com>
* 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.xptracker;
import java.util.EnumMap;
import java.util.Map;
import lombok.NonNull;
import net.runelite.api.NPC;
import net.runelite.api.Skill;
/**
* Internal state for the XpTrackerPlugin
*
* Note: This class's operations are not currently synchronized.
* It is intended to be called by the XpTrackerPlugin on the client thread.
*/
class XpState
{
private static final double DEFAULT_XP_MODIFIER = 4.0;
private static final double SHARED_XP_MODIFIER = DEFAULT_XP_MODIFIER / 3.0;
private final Map<Skill, XpStateSingle> xpSkills = new EnumMap<>(Skill.class);
private NPC interactedNPC;
/**
* Destroys all internal state, however any XpSnapshotSingle or XpSnapshotTotal remain unaffected.
*/
void reset()
{
xpSkills.clear();
}
/**
* Resets a single skill
* @param skill Skill to reset
* @param currentXp Current XP to set to, if unknown set to -1
*/
void resetSkill(Skill skill, long currentXp)
{
xpSkills.remove(skill);
xpSkills.put(skill, new XpStateSingle(skill, currentXp));
}
/**
* Updates a skill with the current known XP.
* When the result of this operation is XpUpdateResult.UPDATED, the UI should be updated accordingly.
* This is to distinguish events that reload all the skill's current values (such as world hopping)
* and also first-login when the skills are not initialized (the start XP will be -1 in this case).
* @param skill Skill to update
* @param currentXp Current known XP for this skill
* @param goalStartXp Possible XP start goal
* @param goalEndXp Possible XP end goal
* @return Whether or not the skill has been initialized, there was no change, or it has been updated
*/
XpUpdateResult updateSkill(Skill skill, long currentXp, int goalStartXp, int goalEndXp)
{
XpStateSingle state = getSkill(skill);
if (state.getStartXp() == -1)
{
if (currentXp >= 0)
{
initializeSkill(skill, currentXp);
return XpUpdateResult.INITIALIZED;
}
else
{
return XpUpdateResult.NO_CHANGE;
}
}
else
{
long startXp = state.getStartXp();
int gainedXp = state.getXpGained();
if (startXp + gainedXp > currentXp)
{
// Reinitialize with lesser currentXp, this can happen with negative xp lamps
initializeSkill(skill, currentXp);
return XpUpdateResult.INITIALIZED;
}
else
{
return state.update(currentXp, goalStartXp, goalEndXp) ? XpUpdateResult.UPDATED : XpUpdateResult.NO_CHANGE;
}
}
}
private double getCombatXPModifier(Skill skill)
{
if (skill == Skill.HITPOINTS)
{
return SHARED_XP_MODIFIER;
}
return DEFAULT_XP_MODIFIER;
}
/**
* Updates skill with average actions based on currently interacted NPC.
* @param skill experience gained skill
* @param npc currently interacted NPC
* @param npcHealth health of currently interacted NPC
*/
void updateNpcExperience(Skill skill, NPC npc, Integer npcHealth, int xpModifier)
{
if (npc == null || npc.getCombatLevel() <= 0 || npcHealth == null)
{
return;
}
final XpStateSingle state = getSkill(skill);
final int actionExp = (int) (npcHealth * getCombatXPModifier(skill) * xpModifier);
final XpAction action = state.getXpAction(XpActionType.ACTOR_HEALTH);
if (action.isActionsHistoryInitialized())
{
action.getActionExps()[action.getActionExpIndex()] = actionExp;
if (interactedNPC != npc)
{
action.setActionExpIndex((action.getActionExpIndex() + 1) % action.getActionExps().length);
}
}
else
{
// So we have a decent average off the bat, lets populate all values with what we see.
for (int i = 0; i < action.getActionExps().length; i++)
{
action.getActionExps()[i] = actionExp;
}
action.setActionsHistoryInitialized(true);
}
interactedNPC = npc;
state.setActionType(XpActionType.ACTOR_HEALTH);
}
/**
* Update number of actions performed for skill (e.g amount of kills in this case) if last interacted
* NPC died
* @param skill skill to update actions for
* @param npc npc that just died
* @param npcHealth max health of npc that just died
* @return UPDATED in case new kill was successfully added
*/
XpUpdateResult updateNpcKills(Skill skill, NPC npc, Integer npcHealth)
{
XpStateSingle state = getSkill(skill);
if (state.getXpGained() <= 0 || npcHealth == null || npc != interactedNPC)
{
return XpUpdateResult.NO_CHANGE;
}
final XpAction xpAction = state.getXpAction(XpActionType.ACTOR_HEALTH);
xpAction.setActions(xpAction.getActions() + 1);
return xpAction.isActionsHistoryInitialized() ? XpUpdateResult.UPDATED : XpUpdateResult.NO_CHANGE;
}
void tick(Skill skill, long delta)
{
getSkill(skill).tick(delta);
}
/**
* Forcefully initialize a skill with a known start XP from the current XP.
* This is used in resetAndInitState by the plugin. It should not result in showing the XP in the UI.
* @param skill Skill to initialize
* @param currentXp Current known XP for the skill
*/
void initializeSkill(Skill skill, long currentXp)
{
xpSkills.put(skill, new XpStateSingle(skill, currentXp));
}
boolean isInitialized(Skill skill)
{
XpStateSingle xpStateSingle = xpSkills.get(skill);
return xpStateSingle != null && xpStateSingle.getStartXp() != -1;
}
@NonNull
XpStateSingle getSkill(Skill skill)
{
return xpSkills.computeIfAbsent(skill, (s) -> new XpStateSingle(s, -1));
}
/**
* Obtain an immutable snapshot of the provided skill
* intended for use with the UI which operates on another thread
* @param skill Skill to obtain the snapshot for
* @return An immutable snapshot of the specified skill for this session since first login or last reset
*/
@NonNull
XpSnapshotSingle getSkillSnapshot(Skill skill)
{
return getSkill(skill).snapshot();
}
/**
* Obtain an immutable snapshot of the provided skill
* intended for use with the UI which operates on another thread
* @return An immutable snapshot of total information for this session since first login or last reset
*/
@NonNull
XpSnapshotSingle getTotalSnapshot()
{
return getSkill(Skill.OVERALL).snapshot();
}
}

View File

@@ -0,0 +1,303 @@
/*
* Copyright (c) 2017, Cameron <moberg@tuta.io>
* Copyright (c) 2018, Levi <me@levischuck.com>
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.xptracker;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Experience;
import net.runelite.api.Skill;
@Slf4j
class XpStateSingle
{
private final Skill skill;
private final Map<XpActionType, XpAction> actions = new HashMap<>();
@Getter
@Setter
private long startXp;
@Getter
private int xpGained = 0;
@Setter
private XpActionType actionType = XpActionType.EXPERIENCE;
private long skillTime = 0;
private int startLevelExp = 0;
private int endLevelExp = 0;
XpStateSingle(Skill skill, long startXp)
{
this.skill = skill;
this.startXp = startXp;
}
XpAction getXpAction(final XpActionType type)
{
actions.putIfAbsent(type, new XpAction());
return actions.get(type);
}
long getCurrentXp()
{
return startXp + xpGained;
}
private int getActionsHr()
{
return toHourly(getXpAction(actionType).getActions());
}
private int toHourly(int value)
{
return (int) ((1.0 / (getTimeElapsedInSeconds() / 3600.0)) * value);
}
private long getTimeElapsedInSeconds()
{
// If the skill started just now, we can divide by near zero, this results in odd behavior.
// To prevent that, pretend the skill has been active for a minute (60 seconds)
// This will create a lower estimate for the first minute,
// but it isn't ridiculous like saying 2 billion XP per hour.
return Math.max(60, skillTime / 1000);
}
private int getXpRemaining()
{
return endLevelExp - (int) getCurrentXp();
}
private int getActionsRemaining()
{
final XpAction action = getXpAction(actionType);
if (action.isActionsHistoryInitialized())
{
long xpRemaining = getXpRemaining() * action.getActionExps().length;
long totalActionXp = 0;
for (int actionXp : action.getActionExps())
{
totalActionXp += actionXp;
}
// Let's not divide by zero (or negative)
if (totalActionXp > 0)
{
// Make sure to account for the very last action at the end
long remainder = xpRemaining % totalActionXp;
long quotient = xpRemaining / totalActionXp;
return Math.toIntExact(quotient + (remainder > 0 ? 1 : 0));
}
}
return Integer.MAX_VALUE;
}
private double getSkillProgress()
{
double xpGained = getCurrentXp() - startLevelExp;
double xpGoal = endLevelExp - startLevelExp;
return (xpGained / xpGoal) * 100;
}
private long getSecondsTillLevel()
{
// Java 8 doesn't have good duration / period objects to represent spans of time that can be formatted
// Rather than importing another dependency like joda time (which is practically built into java 10)
// below will be a custom formatter that handles spans larger than 1 day
long seconds = getTimeElapsedInSeconds();
if (seconds <= 0 || xpGained <= 0)
{
return -1;
}
// formula is xpRemaining / xpPerSecond
// xpPerSecond being xpGained / seconds
// This can be simplified so division is only done once and we can work in whole numbers!
return (getXpRemaining() * seconds) / xpGained;
}
private String getTimeTillLevel(XpGoalTimeType goalTimeType)
{
long remainingSeconds = getSecondsTillLevel();
if (remainingSeconds < 0)
{
return "\u221e";
}
long durationDays = remainingSeconds / (24 * 60 * 60);
long durationHours = (remainingSeconds % (24 * 60 * 60)) / (60 * 60);
long durationHoursTotal = remainingSeconds / (60 * 60);
long durationMinutes = (remainingSeconds % (60 * 60)) / 60;
long durationSeconds = remainingSeconds % 60;
switch (goalTimeType)
{
case DAYS:
if (durationDays > 1)
{
return String.format("%d days %02d:%02d:%02d", durationDays, durationHours, durationMinutes, durationSeconds);
}
else if (durationDays == 1)
{
return String.format("1 day %02d:%02d:%02d", durationHours, durationMinutes, durationSeconds);
}
case HOURS:
if (durationHoursTotal > 1)
{
return String.format("%d hours %02d:%02d", durationHoursTotal, durationMinutes, durationSeconds);
}
else if (durationHoursTotal == 1)
{
return String.format("1 hour %02d:%02d", durationMinutes, durationSeconds);
}
case SHORT:
default:
// durationDays = 0 or durationHoursTotal = 0 or goalTimeType = SHORT if we got here.
// return time remaining in hh:mm:ss or mm:ss format where hh can be > 24
if (durationHoursTotal > 0)
{
return String.format("%02d:%02d:%02d", durationHoursTotal, durationMinutes, durationSeconds);
}
// Minutes and seconds will always be present
return String.format("%02d:%02d", durationMinutes, durationSeconds);
}
}
int getXpHr()
{
return toHourly(xpGained);
}
boolean update(long currentXp, int goalStartXp, int goalEndXp)
{
if (startXp == -1)
{
log.warn("Attempted to update skill state " + skill + " but was not initialized with current xp");
return false;
}
long originalXp = xpGained + startXp;
int actionExp = (int) (currentXp - originalXp);
// No experience gained
if (actionExp == 0)
{
return false;
}
// Update EXPERIENCE action
final XpAction action = getXpAction(XpActionType.EXPERIENCE);
if (action.isActionsHistoryInitialized())
{
action.getActionExps()[action.getActionExpIndex()] = actionExp;
}
else
{
// populate all values in our action history array with this first value that we see
// so the average value of our action history starts out as this first value we see
for (int i = 0; i < action.getActionExps().length; i++)
{
action.getActionExps()[i] = actionExp;
}
action.setActionsHistoryInitialized(true);
}
action.setActionExpIndex((action.getActionExpIndex() + 1) % action.getActionExps().length);
action.setActions(action.getActions() + 1);
// Calculate experience gained
xpGained = (int) (currentXp - startXp);
// Determine XP goals, overall has no goals
if (skill != Skill.OVERALL)
{
if (goalStartXp < 0 || currentXp > goalEndXp)
{
startLevelExp = Experience.getXpForLevel(Experience.getLevelForXp((int) currentXp));
}
else
{
startLevelExp = goalStartXp;
}
if (goalEndXp <= 0 || currentXp > goalEndXp)
{
int currentLevel = Experience.getLevelForXp((int) currentXp);
endLevelExp = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL
? Experience.getXpForLevel(currentLevel + 1)
: Experience.MAX_SKILL_XP;
}
else
{
endLevelExp = goalEndXp;
}
}
return true;
}
public void tick(long delta)
{
// Don't tick skills that have not gained XP or have been reset.
if (xpGained <= 0)
{
return;
}
skillTime += delta;
}
XpSnapshotSingle snapshot()
{
return XpSnapshotSingle.builder()
.startLevel(Experience.getLevelForXp(startLevelExp))
.endLevel(Experience.getLevelForXp(endLevelExp))
.xpGainedInSession(xpGained)
.xpRemainingToGoal(getXpRemaining())
.xpPerHour(getXpHr())
.skillProgressToGoal(getSkillProgress())
.actionType(actionType)
.actionsInSession(getXpAction(actionType).getActions())
.actionsRemainingToGoal(getActionsRemaining())
.actionsPerHour(getActionsHr())
.timeTillGoal(getTimeTillLevel(XpGoalTimeType.DAYS))
.timeTillGoalHours(getTimeTillLevel(XpGoalTimeType.HOURS))
.timeTillGoalShort(getTimeTillLevel(XpGoalTimeType.SHORT))
.startGoalXp(startLevelExp)
.endGoalXp(endLevelExp)
.build();
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright (c) 2018, Levi <me@levischuck.com>
* Copyright (c) 2020, Anthony <https://github.com/while-loop>
* 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.xptracker;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
import net.runelite.client.config.Units;
@ConfigGroup("xpTracker")
public interface XpTrackerConfig extends Config
{
@ConfigSection(
name = "Overlay",
description = "Canvas overlay options",
position = 99
)
String overlaySection = "overlay";
@ConfigItem(
position = 0,
keyName = "hideMaxed",
name = "Hide maxed skills",
description = "Stop globes from showing up for level 99 skills "
)
default boolean hideMaxed()
{
return false;
}
@ConfigItem(
position = 1,
keyName = "logoutPausing",
name = "Pause on Logout",
description = "Configures whether skills should pause on logout"
)
default boolean pauseOnLogout()
{
return true;
}
@ConfigItem(
position = 2,
keyName = "intermediateLevelMarkers",
name = "Show intermediate level markers",
description = "Marks intermediate levels on the progressbar"
)
default boolean showIntermediateLevels()
{
return false;
}
@ConfigItem(
position = 3,
keyName = "pauseSkillAfter",
name = "Auto pause after",
description = "Configures how many minutes passes before pausing a skill while in game and there's no XP, 0 means disabled"
)
@Units(Units.MINUTES)
default int pauseSkillAfter()
{
return 0;
}
@ConfigItem(
position = 4,
keyName = "skillTabOverlayMenuOptions",
name = "Add skill tab canvas menu option",
description = "Configures whether a menu option to show/hide canvas XP trackers will be added to skills on the skill tab",
section = overlaySection
)
default boolean skillTabOverlayMenuOptions()
{
return true;
}
@ConfigItem(
position = 5,
keyName = "onScreenDisplayMode",
name = "On-screen tracker display mode (top)",
description = "Configures the information displayed in the first line of on-screen XP overlays",
section = overlaySection
)
default XpPanelLabel onScreenDisplayMode()
{
return XpPanelLabel.XP_GAINED;
}
@ConfigItem(
position = 6,
keyName = "onScreenDisplayModeBottom",
name = "On-screen tracker display mode (bottom)",
description = "Configures the information displayed in the second line of on-screen XP overlays",
section = overlaySection
)
default XpPanelLabel onScreenDisplayModeBottom()
{
return XpPanelLabel.XP_HOUR;
}
@ConfigItem(
position = 7,
keyName = "xpPanelLabel1",
name = "Top-left XP info label",
description = "Configures the information displayed in the top-left of XP info box"
)
default XpPanelLabel xpPanelLabel1()
{
return XpPanelLabel.XP_GAINED;
}
@ConfigItem(
position = 8,
keyName = "xpPanelLabel2",
name = "Top-right XP info label",
description = "Configures the information displayed in the top-right of XP info box"
)
default XpPanelLabel xpPanelLabel2()
{
return XpPanelLabel.XP_LEFT;
}
@ConfigItem(
position = 9,
keyName = "xpPanelLabel3",
name = "Bottom-left XP info label",
description = "Configures the information displayed in the bottom-left of XP info box"
)
default XpPanelLabel xpPanelLabel3()
{
return XpPanelLabel.XP_HOUR;
}
@ConfigItem(
position = 10,
keyName = "xpPanelLabel4",
name = "Bottom-right XP info label",
description = "Configures the information displayed in the bottom-right of XP info box"
)
default XpPanelLabel xpPanelLabel4()
{
return XpPanelLabel.ACTIONS_LEFT;
}
@ConfigItem(
position = 11,
keyName = "progressBarLabel",
name = "Progress bar label",
description = "Configures the info box progress bar to show Time to goal or percentage complete"
)
default XpProgressBarLabel progressBarLabel()
{
return XpProgressBarLabel.PERCENTAGE;
}
@ConfigItem(
position = 12,
keyName = "progressBarTooltipLabel",
name = "Tooltip label",
description = "Configures the info box progress bar tooltip to show Time to goal or percentage complete"
)
default XpProgressBarLabel progressBarTooltipLabel()
{
return XpProgressBarLabel.TIME_TO_LEVEL;
}
@ConfigItem(
position = 13,
keyName = "prioritizeRecentXpSkills",
name = "Move recently trained skills to top",
description = "Configures whether skills should be organized by most recently gained xp"
)
default boolean prioritizeRecentXpSkills()
{
return false;
}
}

View File

@@ -0,0 +1,749 @@
/*
* Copyright (c) 2017, Cameron <moberg@tuta.io>
* Copyright (c) 2018, Levi <me@levischuck.com>
* 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.xptracker;
import com.google.common.annotations.VisibleForTesting;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.GameState;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.Player;
import net.runelite.api.Skill;
import net.runelite.api.VarPlayer;
import net.runelite.api.WorldType;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.StatChanged;
import net.runelite.api.widgets.WidgetID;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.NPCManager;
import net.runelite.client.game.SkillIconManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import static net.runelite.client.plugins.xptracker.XpWorldType.NORMAL;
import net.runelite.client.task.Schedule;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
import net.runelite.http.api.xp.XpClient;
import okhttp3.OkHttpClient;
@PluginDescriptor(
name = "XP Tracker",
description = "Enable the XP Tracker panel",
tags = {"experience", "levels", "panel"}
)
@Slf4j
public class XpTrackerPlugin extends Plugin
{
/**
* Amount of EXP that must be gained for an update to be submitted.
*/
private static final int XP_THRESHOLD = 10_000;
private static final String MENUOP_ADD_CANVAS_TRACKER = "Add to canvas";
private static final String MENUOP_REMOVE_CANVAS_TRACKER = "Remove from canvas";
static final List<Skill> COMBAT = ImmutableList.of(
Skill.ATTACK,
Skill.STRENGTH,
Skill.DEFENCE,
Skill.RANGED,
Skill.HITPOINTS,
Skill.MAGIC);
@Inject
private ClientToolbar clientToolbar;
@Inject
private Client client;
@Inject
private SkillIconManager skillIconManager;
@Inject
private XpTrackerConfig xpTrackerConfig;
@Inject
private NPCManager npcManager;
@Inject
private OverlayManager overlayManager;
@Inject
private XpClient xpClient;
private NavigationButton navButton;
@Setter(AccessLevel.PACKAGE)
@VisibleForTesting
private XpPanel xpPanel;
private XpWorldType lastWorldType;
private String lastUsername;
private long lastTickMillis = 0;
private boolean fetchXp; // fetch lastXp for the online xp tracker
private long lastXp = 0;
private boolean initializeTracker;
private final XpState xpState = new XpState();
private final XpPauseState xpPauseState = new XpPauseState();
@Provides
XpTrackerConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(XpTrackerConfig.class);
}
@Provides
XpClient provideXpClient(OkHttpClient okHttpClient)
{
return new XpClient(okHttpClient);
}
@Override
public void configure(Binder binder)
{
binder.bind(XpTrackerService.class).to(XpTrackerServiceImpl.class);
}
@Override
protected void startUp() throws Exception
{
xpPanel = new XpPanel(this, xpTrackerConfig, client, skillIconManager);
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "/skill_icons/overall.png");
navButton = NavigationButton.builder()
.tooltip("XP Tracker")
.icon(icon)
.priority(2)
.panel(xpPanel)
.build();
clientToolbar.addNavigation(navButton);
// Initialize the tracker & last xp if already logged in
fetchXp = true;
initializeTracker = true;
}
@Override
protected void shutDown() throws Exception
{
overlayManager.removeIf(e -> e instanceof XpInfoBoxOverlay);
xpState.reset();
clientToolbar.removeNavigation(navButton);
}
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
GameState state = event.getGameState();
if (state == GameState.LOGGED_IN)
{
// LOGGED_IN is triggered between region changes too.
// Check that the username changed or the world type changed.
XpWorldType type = worldSetToType(client.getWorldType());
if (!Objects.equals(client.getUsername(), lastUsername) || lastWorldType != type)
{
// Reset
log.debug("World change: {} -> {}, {} -> {}",
lastUsername, client.getUsername(),
firstNonNull(lastWorldType, "<unknown>"),
firstNonNull(type, "<unknown>"));
lastUsername = client.getUsername();
// xp is not available until after login is finished, so fetch it on the next gametick
fetchXp = true;
lastWorldType = type;
resetState();
// Must be set from hitting the LOGGING_IN or HOPPING case below
assert initializeTracker;
}
}
else if (state == GameState.LOGGING_IN || state == GameState.HOPPING)
{
initializeTracker = true;
}
else if (state == GameState.LOGIN_SCREEN)
{
Player local = client.getLocalPlayer();
if (local == null)
{
return;
}
String username = local.getName();
if (username == null)
{
return;
}
long totalXp = client.getOverallExperience();
// Don't submit xptrack unless xp threshold is reached
if (Math.abs(totalXp - lastXp) > XP_THRESHOLD)
{
xpClient.update(username);
lastXp = totalXp;
}
}
}
private XpWorldType worldSetToType(EnumSet<WorldType> types)
{
XpWorldType xpType = NORMAL;
for (WorldType type : types)
{
XpWorldType t = XpWorldType.of(type);
if (t != NORMAL)
{
xpType = t;
}
}
return xpType;
}
/**
* Adds an overlay to the canvas for tracking a specific skill.
*
* @param skill the skill for which the overlay should be added
*/
void addOverlay(Skill skill)
{
removeOverlay(skill);
overlayManager.add(new XpInfoBoxOverlay(this, xpTrackerConfig, skill, skillIconManager.getSkillImage(skill)));
}
/**
* Removes an overlay from the overlayManager if it's present.
*
* @param skill the skill for which the overlay should be removed.
*/
void removeOverlay(Skill skill)
{
overlayManager.removeIf(e -> e instanceof XpInfoBoxOverlay && ((XpInfoBoxOverlay) e).getSkill() == skill);
}
/**
* Check if there is an overlay on the canvas for the skill.
*
* @param skill the skill which should have an overlay.
* @return true if the skill has an overlay.
*/
boolean hasOverlay(final Skill skill)
{
return overlayManager.anyMatch(o -> o instanceof XpInfoBoxOverlay && ((XpInfoBoxOverlay) o).getSkill() == skill);
}
/**
* Reset internal state and re-initialize all skills with XP currently cached by the RS client
* This is called by the user manually clicking resetSkillState in the UI.
* It reloads the current skills from the client after resetting internal state.
*/
void resetAndInitState()
{
resetState();
for (Skill skill : Skill.values())
{
long currentXp;
if (skill == Skill.OVERALL)
{
currentXp = client.getOverallExperience();
}
else
{
currentXp = client.getSkillExperience(skill);
}
xpState.initializeSkill(skill, currentXp);
removeOverlay(skill);
}
}
/**
* Throw out everything, the user has chosen a different account or world type.
* This resets both the internal state and UI elements
*/
private void resetState()
{
xpState.reset();
xpPanel.resetAllInfoBoxes();
xpPanel.updateTotal(new XpSnapshotSingle.XpSnapshotSingleBuilder().build());
overlayManager.removeIf(e -> e instanceof XpInfoBoxOverlay);
}
/**
* Reset an individual skill with the client's current known state of the skill
* Will also clear the skill from the UI.
* @param skill Skill to reset
*/
void resetSkillState(Skill skill)
{
int currentXp = client.getSkillExperience(skill);
xpState.resetSkill(skill, currentXp);
xpPanel.resetSkill(skill);
removeOverlay(skill);
}
/**
* Reset all skills except for the one provided
* @param skill Skill to ignore during reset
*/
void resetOtherSkillState(Skill skill)
{
for (Skill s : Skill.values())
{
// Overall is not reset from resetting individual skills
if (skill != s && s != Skill.OVERALL)
{
resetSkillState(s);
}
}
}
@Subscribe
public void onStatChanged(StatChanged statChanged)
{
final Skill skill = statChanged.getSkill();
final int currentXp = statChanged.getXp();
final int currentLevel = statChanged.getLevel();
final VarPlayer startGoal = startGoalVarpForSkill(skill);
final VarPlayer endGoal = endGoalVarpForSkill(skill);
final int startGoalXp = startGoal != null ? client.getVar(startGoal) : -1;
final int endGoalXp = endGoal != null ? client.getVar(endGoal) : -1;
if (initializeTracker)
{
// This is the XP sync on login, wait until after login to begin counting
return;
}
if (xpTrackerConfig.hideMaxed() && currentLevel >= Experience.MAX_REAL_LEVEL)
{
return;
}
final XpStateSingle state = xpState.getSkill(skill);
state.setActionType(XpActionType.EXPERIENCE);
final Actor interacting = client.getLocalPlayer().getInteracting();
if (interacting instanceof NPC && COMBAT.contains(skill))
{
final int xpModifier = worldSetToType(client.getWorldType()).modifier(client);;
final NPC npc = (NPC) interacting;
xpState.updateNpcExperience(skill, npc, npcManager.getHealth(npc.getId()), xpModifier);
}
final XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp, startGoalXp, endGoalXp);
xpPanel.updateSkillExperience(updateResult == XpUpdateResult.UPDATED, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
// Also update the total experience
xpState.updateSkill(Skill.OVERALL, client.getOverallExperience(), -1, -1);
xpPanel.updateTotal(xpState.getTotalSnapshot());
}
@Subscribe
public void onNpcDespawned(NpcDespawned event)
{
final NPC npc = event.getNpc();
if (!npc.isDead())
{
return;
}
for (Skill skill : COMBAT)
{
final XpUpdateResult updateResult = xpState.updateNpcKills(skill, npc, npcManager.getHealth(npc.getId()));
final boolean updated = XpUpdateResult.UPDATED.equals(updateResult);
xpPanel.updateSkillExperience(updated, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
}
xpPanel.updateTotal(xpState.getTotalSnapshot());
}
@Subscribe
public void onGameTick(GameTick event)
{
if (initializeTracker)
{
initializeTracker = false;
// Check for xp gained while logged out
for (Skill skill : Skill.values())
{
if (skill == Skill.OVERALL || !xpState.isInitialized(skill))
{
continue;
}
XpStateSingle skillState = xpState.getSkill(skill);
final int currentXp = client.getSkillExperience(skill);
if (skillState.getCurrentXp() != currentXp)
{
if (currentXp < skillState.getCurrentXp())
{
log.debug("Xp is going backwards! {} {} -> {}", skill, skillState.getCurrentXp(), currentXp);
resetState();
break;
}
log.debug("Skill xp for {} changed when offline: {} -> {}", skill, skillState.getCurrentXp(), currentXp);
// Offset start xp for offline gains
long diff = currentXp - skillState.getCurrentXp();
skillState.setStartXp(skillState.getStartXp() + diff);
}
}
// Initialize the tracker with the initial xp if not already initialized
for (Skill skill : Skill.values())
{
if (skill == Skill.OVERALL)
{
continue;
}
if (!xpState.isInitialized(skill))
{
final int currentXp = client.getSkillExperience(skill);
// goal exps are not necessary for skill initialization
XpUpdateResult xpUpdateResult = xpState.updateSkill(skill, currentXp, -1, -1);
assert xpUpdateResult == XpUpdateResult.INITIALIZED;
}
}
// Initialize the overall xp
if (!xpState.isInitialized(Skill.OVERALL))
{
long overallXp = client.getOverallExperience();
log.debug("Initializing XP tracker with {} overall exp", overallXp);
xpState.initializeSkill(Skill.OVERALL, overallXp);
}
}
if (fetchXp)
{
lastXp = client.getOverallExperience();
fetchXp = false;
}
rebuildSkills();
}
@Subscribe
public void onMenuEntryAdded(final MenuEntryAdded event)
{
int widgetID = event.getActionParam1();
if (TO_GROUP(widgetID) != WidgetID.SKILLS_GROUP_ID
|| !event.getOption().startsWith("View")
|| !xpTrackerConfig.skillTabOverlayMenuOptions())
{
return;
}
// Get skill from menu option, eg. "View <col=ff981f>Attack</col> guide"
final String skillText = event.getOption().split(" ")[1];
final Skill skill = Skill.valueOf(Text.removeTags(skillText).toUpperCase());
MenuEntry[] menuEntries = client.getMenuEntries();
menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1);
MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry();
menuEntry.setTarget(skillText);
menuEntry.setOption(hasOverlay(skill) ? MENUOP_REMOVE_CANVAS_TRACKER : MENUOP_ADD_CANVAS_TRACKER);
menuEntry.setParam0(event.getActionParam0());
menuEntry.setParam1(widgetID);
menuEntry.setType(MenuAction.RUNELITE.getId());
client.setMenuEntries(menuEntries);
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked event)
{
if (event.getMenuAction().getId() != MenuAction.RUNELITE.getId()
|| TO_GROUP(event.getWidgetId()) != WidgetID.SKILLS_GROUP_ID)
{
System.out.println("opcode " + event.getMenuAction());
System.out.println("id " + event.getMenuAction().getId());
System.out.println("group " + TO_GROUP(event.getWidgetId()));
return;
}
final Skill skill;
try
{
skill = Skill.valueOf(Text.removeTags(event.getMenuTarget()).toUpperCase());
}
catch (IllegalArgumentException ex)
{
log.debug(null, ex);
return;
}
switch (event.getMenuOption())
{
case MENUOP_ADD_CANVAS_TRACKER:
addOverlay(skill);
break;
case MENUOP_REMOVE_CANVAS_TRACKER:
removeOverlay(skill);
break;
default:
System.out.println(event.getMenuOption());
}
}
XpStateSingle getSkillState(Skill skill)
{
return xpState.getSkill(skill);
}
XpSnapshotSingle getSkillSnapshot(Skill skill)
{
return xpState.getSkillSnapshot(skill);
}
private static VarPlayer startGoalVarpForSkill(final Skill skill)
{
switch (skill)
{
case ATTACK:
return VarPlayer.ATTACK_GOAL_START;
case MINING:
return VarPlayer.MINING_GOAL_START;
case WOODCUTTING:
return VarPlayer.WOODCUTTING_GOAL_START;
case DEFENCE:
return VarPlayer.DEFENCE_GOAL_START;
case MAGIC:
return VarPlayer.MAGIC_GOAL_START;
case RANGED:
return VarPlayer.RANGED_GOAL_START;
case HITPOINTS:
return VarPlayer.HITPOINTS_GOAL_START;
case AGILITY:
return VarPlayer.AGILITY_GOAL_START;
case STRENGTH:
return VarPlayer.STRENGTH_GOAL_START;
case PRAYER:
return VarPlayer.PRAYER_GOAL_START;
case SLAYER:
return VarPlayer.SLAYER_GOAL_START;
case FISHING:
return VarPlayer.FISHING_GOAL_START;
case RUNECRAFT:
return VarPlayer.RUNECRAFT_GOAL_START;
case HERBLORE:
return VarPlayer.HERBLORE_GOAL_START;
case FIREMAKING:
return VarPlayer.FIREMAKING_GOAL_START;
case CONSTRUCTION:
return VarPlayer.CONSTRUCTION_GOAL_START;
case HUNTER:
return VarPlayer.HUNTER_GOAL_START;
case COOKING:
return VarPlayer.COOKING_GOAL_START;
case FARMING:
return VarPlayer.FARMING_GOAL_START;
case CRAFTING:
return VarPlayer.CRAFTING_GOAL_START;
case SMITHING:
return VarPlayer.SMITHING_GOAL_START;
case THIEVING:
return VarPlayer.THIEVING_GOAL_START;
case FLETCHING:
return VarPlayer.FLETCHING_GOAL_START;
default:
return null;
}
}
private static VarPlayer endGoalVarpForSkill(final Skill skill)
{
switch (skill)
{
case ATTACK:
return VarPlayer.ATTACK_GOAL_END;
case MINING:
return VarPlayer.MINING_GOAL_END;
case WOODCUTTING:
return VarPlayer.WOODCUTTING_GOAL_END;
case DEFENCE:
return VarPlayer.DEFENCE_GOAL_END;
case MAGIC:
return VarPlayer.MAGIC_GOAL_END;
case RANGED:
return VarPlayer.RANGED_GOAL_END;
case HITPOINTS:
return VarPlayer.HITPOINTS_GOAL_END;
case AGILITY:
return VarPlayer.AGILITY_GOAL_END;
case STRENGTH:
return VarPlayer.STRENGTH_GOAL_END;
case PRAYER:
return VarPlayer.PRAYER_GOAL_END;
case SLAYER:
return VarPlayer.SLAYER_GOAL_END;
case FISHING:
return VarPlayer.FISHING_GOAL_END;
case RUNECRAFT:
return VarPlayer.RUNECRAFT_GOAL_END;
case HERBLORE:
return VarPlayer.HERBLORE_GOAL_END;
case FIREMAKING:
return VarPlayer.FIREMAKING_GOAL_END;
case CONSTRUCTION:
return VarPlayer.CONSTRUCTION_GOAL_END;
case HUNTER:
return VarPlayer.HUNTER_GOAL_END;
case COOKING:
return VarPlayer.COOKING_GOAL_END;
case FARMING:
return VarPlayer.FARMING_GOAL_END;
case CRAFTING:
return VarPlayer.CRAFTING_GOAL_END;
case SMITHING:
return VarPlayer.SMITHING_GOAL_END;
case THIEVING:
return VarPlayer.THIEVING_GOAL_END;
case FLETCHING:
return VarPlayer.FLETCHING_GOAL_END;
default:
return null;
}
}
@Schedule(
period = 1,
unit = ChronoUnit.SECONDS
)
public void tickSkillTimes()
{
// Adjust unpause states
for (Skill skill : Skill.values())
{
long skillExperience;
if (skill == Skill.OVERALL)
{
skillExperience = client.getOverallExperience();
}
else
{
skillExperience = client.getSkillExperience(skill);
}
xpPauseState.tickXp(skill, skillExperience, xpTrackerConfig.pauseSkillAfter());
}
final boolean loggedIn;
switch (client.getGameState())
{
case LOGIN_SCREEN:
case LOGGING_IN:
case LOGIN_SCREEN_AUTHENTICATOR:
loggedIn = false;
break;
default:
loggedIn = true;
break;
}
xpPauseState.tickLogout(xpTrackerConfig.pauseOnLogout(), loggedIn);
if (lastTickMillis == 0)
{
lastTickMillis = System.currentTimeMillis();
return;
}
final long nowMillis = System.currentTimeMillis();
final long tickDelta = nowMillis - lastTickMillis;
lastTickMillis = nowMillis;
for (Skill skill : Skill.values())
{
if (!xpPauseState.isPaused(skill))
{
xpState.tick(skill, tickDelta);
}
}
rebuildSkills();
}
private void rebuildSkills()
{
// Rebuild calculated values like xp/hr in panel
for (Skill skill : Skill.values())
{
xpPanel.updateSkillExperience(false, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
}
xpPanel.updateTotal(xpState.getTotalSnapshot());
}
void pauseSkill(Skill skill, boolean pause)
{
if (pause ? xpPauseState.pauseSkill(skill) : xpPauseState.unpauseSkill(skill))
{
xpPanel.updateSkillExperience(false, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill));
}
}
void pauseAllSkills(boolean pause)
{
for (Skill skill : Skill.values())
{
pauseSkill(skill, pause);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018, 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.client.plugins.xptracker;
import net.runelite.api.Skill;
public interface XpTrackerService
{
/**
* Get the number of actions done
*/
int getActions(Skill skill);
/**
* Get the number of actions per hour
*/
int getActionsHr(Skill skill);
/**
* Get the number of actions remaining
*/
int getActionsLeft(Skill skill);
/**
* Get the action type
*/
XpActionType getActionType(Skill skill);
/**
* Get the amount of xp per hour
*/
int getXpHr(Skill skill);
/**
* Get the start goal XP
*/
int getStartGoalXp(Skill skill);
/**
* Get the amount of XP left until goal level
*/
int getEndGoalXp(Skill skill);
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2018, 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.client.plugins.xptracker;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Skill;
@Singleton
class XpTrackerServiceImpl implements XpTrackerService
{
private final XpTrackerPlugin plugin;
@Inject
XpTrackerServiceImpl(XpTrackerPlugin plugin)
{
this.plugin = plugin;
}
@Override
public int getActions(Skill skill)
{
return plugin.getSkillSnapshot(skill).getActionsInSession();
}
@Override
public int getActionsHr(Skill skill)
{
return plugin.getSkillSnapshot(skill).getActionsPerHour();
}
@Override
public int getActionsLeft(Skill skill)
{
return plugin.getSkillSnapshot(skill).getActionsRemainingToGoal();
}
@Override
public XpActionType getActionType(Skill skill)
{
return plugin.getSkillSnapshot(skill).getActionType();
}
@Override
public int getXpHr(Skill skill)
{
return plugin.getSkillSnapshot(skill).getXpPerHour();
}
@Override
public int getStartGoalXp(Skill skill)
{
return plugin.getSkillSnapshot(skill).getStartGoalXp();
}
@Override
public int getEndGoalXp(Skill skill)
{
return plugin.getSkillSnapshot(skill).getEndGoalXp();
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018, Levi <me@levischuck.com>
* 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.xptracker;
enum XpUpdateResult
{
NO_CHANGE,
INITIALIZED,
UPDATED,
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2018, 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.client.plugins.xptracker;
import net.runelite.api.Client;
import net.runelite.api.Varbits;
import net.runelite.api.WorldType;
enum XpWorldType
{
NORMAL,
TOURNEY,
DMM
{
@Override
int modifier(Client client)
{
return 5;
}
},
LEAGUE
{
@Override
int modifier(Client client)
{
if (client.getVar(Varbits.LEAGUE_RELIC_6) != 0)
{
return 16;
}
if (client.getVar(Varbits.LEAGUE_RELIC_4) != 0)
{
return 12;
}
if (client.getVar(Varbits.LEAGUE_RELIC_2) != 0)
{
return 8;
}
return 5;
}
};
int modifier(Client client)
{
return 1;
}
static XpWorldType of(WorldType type)
{
switch (type)
{
case TOURNAMENT:
return TOURNEY;
case DEADMAN:
return DMM;
case LEAGUE:
return LEAGUE;
default:
return NORMAL;
}
}
}

View File

@@ -186,7 +186,7 @@ public abstract class MenuMixin implements RSClient
final int i = getMenuOptionCount() - 1; final int i = getMenuOptionCount() - 1;
getMenuOptions()[i] = entry.getOption(); getMenuOptions()[i] = entry.getOption();
getMenuTargets()[i] = entry.getTarget(); getMenuTargets()[i] = entry.getTarget();
getMenuIdentifiers()[i] = entry.getType(); getMenuIdentifiers()[i] = entry.getIdentifier();
getMenuOpcodes()[i] = entry.getOpcode(); getMenuOpcodes()[i] = entry.getOpcode();
getMenuArguments1()[i] = entry.getActionParam0(); getMenuArguments1()[i] = entry.getActionParam0();
getMenuArguments2()[i] = entry.getActionParam1(); getMenuArguments2()[i] = entry.getActionParam1();
@@ -214,7 +214,7 @@ public abstract class MenuMixin implements RSClient
tempMenuAction.setOption(entry.getOption()); tempMenuAction.setOption(entry.getOption());
tempMenuAction.setOpcode(entry.getOpcode()); tempMenuAction.setOpcode(entry.getOpcode());
tempMenuAction.setIdentifier(entry.getType()); tempMenuAction.setIdentifier(entry.getIdentifier());
tempMenuAction.setParam0(entry.getActionParam0()); tempMenuAction.setParam0(entry.getActionParam0());
tempMenuAction.setParam1(entry.getActionParam1()); tempMenuAction.setParam1(entry.getActionParam1());
} }

View File

@@ -752,7 +752,7 @@ public abstract class RSClientMixin implements RSClient
MenuEntry entry = entries[i] = new MenuEntry(); MenuEntry entry = entries[i] = new MenuEntry();
entry.setOption(menuOptions[i]); entry.setOption(menuOptions[i]);
entry.setTarget(menuTargets[i]); entry.setTarget(menuTargets[i]);
entry.setType(menuIdentifiers[i]); entry.setIdentifier(menuIdentifiers[i]);
entry.setOpcode(menuTypes[i]); entry.setOpcode(menuTypes[i]);
entry.setActionParam0(params0[i]); entry.setActionParam0(params0[i]);
entry.setActionParam1(params1[i]); entry.setActionParam1(params1[i]);
@@ -783,7 +783,7 @@ public abstract class RSClientMixin implements RSClient
menuOptions[count] = entry.getOption(); menuOptions[count] = entry.getOption();
menuTargets[count] = entry.getTarget(); menuTargets[count] = entry.getTarget();
menuIdentifiers[count] = entry.getType(); menuIdentifiers[count] = entry.getIdentifier();
menuTypes[count] = entry.getOpcode(); menuTypes[count] = entry.getOpcode();
params0[count] = entry.getActionParam0(); params0[count] = entry.getActionParam0();
params1[count] = entry.getActionParam1(); params1[count] = entry.getActionParam1();
@@ -830,7 +830,7 @@ public abstract class RSClientMixin implements RSClient
{ {
options[oldCount] = event.getOption(); options[oldCount] = event.getOption();
targets[oldCount] = event.getTarget(); targets[oldCount] = event.getTarget();
identifiers[oldCount] = event.getType(); identifiers[oldCount] = event.getIdentifier();
opcodes[oldCount] = event.getOpcode(); opcodes[oldCount] = event.getOpcode();
arguments1[oldCount] = event.getActionParam0(); arguments1[oldCount] = event.getActionParam0();
arguments2[oldCount] = event.getActionParam1(); arguments2[oldCount] = event.getActionParam1();
@@ -1425,14 +1425,14 @@ public abstract class RSClientMixin implements RSClient
{ {
client.getLogger().info( client.getLogger().info(
"|MenuAction|: MenuOption={} MenuTarget={} Id={} Opcode={} Param0={} Param1={} CanvasX={} CanvasY={} Authentic={}", "|MenuAction|: MenuOption={} MenuTarget={} Id={} Opcode={} Param0={} Param1={} CanvasX={} CanvasY={} Authentic={}",
menuOptionClicked.getOption(), menuOptionClicked.getTarget(), menuOptionClicked.getType(), menuOptionClicked.getOption(), menuOptionClicked.getTarget(), menuOptionClicked.getIdentifier(),
menuOptionClicked.getOpcode(), menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(), menuOptionClicked.getOpcode(), menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(),
canvasX, canvasY, authentic canvasX, canvasY, authentic
); );
} }
copy$menuAction(menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(), menuOptionClicked.getOpcode(), copy$menuAction(menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(), menuOptionClicked.getOpcode(),
menuOptionClicked.getType(), menuOptionClicked.getOption(), menuOptionClicked.getTarget(), canvasX, canvasY); menuOptionClicked.getIdentifier(), menuOptionClicked.getOption(), menuOptionClicked.getTarget(), canvasX, canvasY);
} }
@Override @Override