-
-
Notifications
You must be signed in to change notification settings - Fork 414
Contribution guide
This project recommends IntelliJ IDEA for development; however you can use whatever IDE you feel comfortable with.
-
Make sure you have Lombok installed.
- For IntelliJ users, you can go to
File > Settings > Plugins
to see if you have Lombok installed. If not, you can install it from the Marketplace for free.
- For IntelliJ users, you can go to
-
Clone the repository to your local machine.
- For IntelliJ, click here to learn how to do that.
-
This project uses Runelite's code conventions. Click here to see how to install the required code style.
-
To run the project (and Runelite by extension), run the
QuestHelperPluginTest
file.
If you are using IntelliJ, you can find the provided run configuration in the .\run
folder.
- If you are using Intellij 2019.3 or older, the run configuration can be found in
.idea\runConfigurations
.
Sometimes IntelliJ cannot see those files when you first clone the repository.
Go to the respective directory and double-click on the file and IntelliJ should ask if you want to import that run configuration.
If you want to create a Run Configuration yourself, or if you are not using IntelliJ and need to know what options the vm needs then this step is for you.
- Any 1.8+ JDK can be used. (Tested with OpenJDK 11 as of 21 FEB 2021).
- Set the classpath to towards the quest-helper test module.
- i.e.
-cp quest-helper.test
- i.e.
- Add
-ea
to your VM Options.- If the VM Options text field is not visible in IntelliJ's
Run Configurations
menu. Click onModify Options
and make sureAdd VM Options
is checked.
- If the VM Options text field is not visible in IntelliJ's
- Make sure the fully qualified class name is
com.questhelper.QuestHelperPluginTest
. - The CLI arguments should be
--debug --developer-mode
.
If you want a non-developer mode run configuration then you can clone the configuration you just made
and remove --developer-mode
.
--developer-mode
is what enables the dev-tools in Runelite and Quest Helper.
Running it like this will let you see if there is a difference between dev and release versions of the quest helper.
The quests
folder contains all the actual helpers themselves. If you were looking to create your own quest helper, you'd create a new folder here for it, then add the files within that. If this is your first quest helper, it'd be recommended to copy the structure of an existing quest helper and go from there.
You can see a template for Quest Helpers in https://github.com/Zoinkwiz/quest-helper/wiki/Template-Quest-Helper
.
At the top of a quest helper, you should have the identifier for the quest. For example, for Ernest the Chicken, you would have:
@QuestDescriptor(
quest = QuestHelperQuest.ERNEST_THE_CHICKEN
)
public class ErnestTheChicken
The quest's class name should be the quest's name as well in camel case.
QuestStep
is the superclass for all quest steps. There are many implementations, this section will cover the most used.
NpcStep
This step is for when interacting with an NPC is required. This can be which NPC to talk to, or even which NPC(s) to fight.
The constructor
NpcStep(QuestHelper, int, String, boolean, Requirement...)
and NpcStep(QuestHelper, int, WorldPoint, String, boolean, Requirement...)
allow you to specify if multiple highlights are allowed. This can be useful when a player is required to kill certain enemies and there are multiple spawns of them in a given area.
However, it is recommended to hide the world arrow via setHideWorldArrow
so users are not confused to if there is a preferred NPC to attack.
Additionally, you can also add safespots to NPCs that a user has to fight.
This is done via addSafeSpots(WorldPoint...)
.
This will highlight the tiles the user can stand that allows them to safespot the NPC. However, if there is setup required to position the NPC that should be conveyed to the user in the side panel text, or the overlay panel text.
You can set the maximum roam range that the quest helper should look for NPC(s) that wander, however this should, generally,
not be needed as it's default value is 48. Which means that any NPC within 48 blocks of the provided WorldPoint
are checked.
ObjectStep
This step is for when interacting with a game object is required.
If there is only one object that needs to be interacted with then
the ObjectStep(QuestHelper, int, WorldPoint, String, Requirement...)
constructor is preferred.
If, however, there are multiple objects that need to be interacted with in the same general area, the ObjectStep(QuestHelper, int, String, Requirement...)
should be used instead.
Sometimes objects can be on multiple planes, in order to re-validate objects when a player changes planes (i.e. goes up/down stairs) you can use setRevalidateObjects
to tell the step to recheck that plane's objects to see if they match the given ObjectID
.
If there are alternate objects that can be used for this step (i.e. closed chests have a different object ID than open chest), you can use setAlternateObjects
to indicate those should also be highlighted. This is recommended if that object's ID can change depending on how the user interacts with it.
DetailedQuestStep
This step is used in almost every quest, along with NpcStep
and ObjectStep
.
This step is a sort of catch-all step that provides functionality that many other steps expand upon.
However, this step can be used by itself if there are no other better alternatives.
It can be used to indicate the player has to travel to a certain location via the DetailedQuestStep(QuestHelper, WorldPoint, String, Requirement...)
constructor.
If you need to draw a path the player must take (i.e. when finding the Myreque base in the Myreque quest-line) you can define a
list of WorldPoint
that will be drawn as points on a line. This is done via setLinePoints(List<WorldPoint>)
.
If you want to draw a path on the world map (not the minimap), you would use setWorldLinePoints(List<WorldPoint>)
.
As well, you can hide the world arrows (the arrows that render above a tile) via setHideWorldArrow
.
You can hide minimap lines via setHideMinimapLines
.
Other miscellaneous steps:
DigStep
is for when a player has to dig at a specified location.
EmoteStep
is for when a quest requires performing an emote. This will highlight the emote required.
TileStep
is for when a user has to go to a certain tile (make sure it's not an object, otherwise ObjectStep
is a better choice).
ItemStep
is for when a user has to pick up an item from a certain tile (or in a given area).
Most quest dialog is fairly simple.
QuestStep
allows you to highlight dialog choices that a user must choose.
However, this can be tricky if multiple dialog choices are required in the same conversation.
addDialogStep(String)
is used to highlight a specified dialog choice. This will highlight that choice no matter which
option number it is.
addDialogStep(int, String)
is used to highlight a specified dialog option. This will only highlight if that option
matches the given id.
For example: addDialogStep(3, "Dialog Choice Example")
This would only highlight Dialog Choice Example
if it was the 3rd (third) option on that dialog menu.
This can be extremely useful if the player has to ask multiple questions but the questions change order depending
on the dialog menu.
addDialogStepWithExclusion(String, String)
can be used to determine when to exclude a certain dialog option.
If the second String option (exclusionString
) is found on that widget, it will not highlight the provided choice
if the excluded option is present.
addDialogStepWithExclusions(String, String...)
is an alternative to addDialogStepWithExclusion
if you need to exclude
multiple options in a certain dialog menu.
Both addDialogStepWithExclusion
and addDialogStepWithExclusions
should not be your first option. Instead, if possible,
you should attempt to use addDialogStep(String)
and addDialogStep(int, String)
. These options are easier to understand
and debug for any future developers. This should not be construed as a reason to NOT use the exclusion methods if required.
Sometimes, dialog can be found in non-standard widgets. Most NPC dialog has the same widget ID, therefore the addDialog
methods
use that widget ID for best results.
However, in case a non-standard widget is required you can use the following:
addWidgetChoice(String, int int)
is for specifying the groupID
and childID
of the widget respectively.
addWidgetChoice(String, int, int, int)
is used in niche circumstances where a widget contains child widgets that also have
children.
Quest requirements are checked periodically to see if the player passes them.
There are many requirement implementations and for brevity only the most used will be explained.
ItemRequirement
is used to see if a player has a given item.
You can add alternate item id's via addAlternates
.
You can have the item be highlighted in a player's inventory via setHighlightInInventory
.
You can require the item to be equipped via setEquip
.
You can change the required quantity via setQuantity
.
If you don't want an ItemRequirement
to be displayed on the overlay panel you can set the condition to hide it
via setConditionToHide
and if true
that requirement will not be displayed.
There are several convenience methods for easily copying item requirements and making certain changes.
copy
will make a new copy of that item requirement.
highlighted
will make a new copy of that item requirement and set setHighlightInInventory
to true.
equipped
will make a new copy and set setEquip
to true.
quantity
will make a new copy and set the quantity to the provided quantity.
hideConditioned
will make a new copy and set setConditionToHide
to the provided Requirement
.
You can easily indicate if an item can be obtained during a quest via canBeObtainedDuringQuest
.
You can also use simple html inside item tooltips.
To set an item's tooltip use setTooltip
.
However, to append text to an existing tooltip use appendToTooltip
.
- Appending to an existing tooltip is useful when you want to format the tooltip to be more human-readable.
By default, QuestHelper will replace all lines breaks (
\n
) with html line breaks (<br>
). As well, each call toappendToTooltip
will add a line break at the end of that string.
Finally, you can set a replacement Requirement
that will be displayed in case the current Requirement
fails it checks.
This is done via setOverlayReplacement
.
ItemRequirements
is a holder class for multiple ItemRequirement
using a logical type to check if the given requirements pass their checks.
The logic types are found in LogicType
, and they are:
AND
- checks if all the requirements pass their checks.
OR
- checks if any of the requirements pass their checks.
NAND
- output is false if all requirements pass their checks. Otherwise it returns true.
NOR
- checks if none of the requirements pass their checks.
XOR
- check two of the requirements (and only two) to see if one of those requirements pass their checks. Otherwise it returns false.
There are many requirement implementations. If you have any questions about which is the best to use for a given situation please ask in the #development
channel on Discord.
ConditionForStep
is used by a number of Conditions, which are used to describe potentially complex logic fairly easily. For example, you may need to check if the player has 25 death runes, a cat following them, and if they're in the Wizards' Tower. You could define those conditions as an ItemRequirement
, a FollowerRequiement
, and a ZoneRequirement
.
Conditions
is used for combining various Requirement
. For example, to combine the logic above, you could specify new Conditions(LogicType.AND, hasDeathRunes, hasCatFollower, inWizardsTower);
.
ConditionalStep
is a special type of step which allows you to make use of the ConditionForStep
steps in a simple manner. A typical ConditionalStep will look like:
ConditionalStep mySteps = new ConditionalStep(this, getCat);
myStep.addStep(inLumbridgeFloor1AndHasCat, talkToDuke);
myStep.addStep(inLumbridgeAndHasCat, goUpToDuke);
What this is doing is creating the object with a base step, which is getCat
. Below that, new steps with conditions are added. This will be checked every tick to see which step to use. The order of checks is from the top step added to the last step added, then finally defaulting on the step you initiated the ConditionalStep with.
This function is used to return all the quest's steps to be used for running the quest helper. It should return a Map of ints (representing the quest's varbit's current value), and the step to use for it.
Each integer represents that quests' current varbit, and the related quest step. When the varbit for that quest changes, the appropriate quest step is started. Beware though, some quests' varbit can move backwards.
For organizational purposes, be sure to create methods in your helper where you store your requirement, zone, conditional step, etc. instantiation.
As well, be sure those methods are called first in loadSteps
. They must be created before the helper starts in order to prevent
any errors.
These are used to provide the relevant details for the sidebar's requirements sections.
This is used to provide the actual sections of the quest and steps for each section for the sidebar panel.
See the PanelDetails
class for more information, or ask on Discord.
Once you've made your quest, commit your changes to a branch on your fork, then make a Pull Request to allow for a review of it. Once it's been reviewed, it will be merged in.
Make sure your code meets the Runelite code style.
If you would like, you can also let us know in Discord so your PR can start being reviewed as early as possible.
Please be aware that if any changes are requested it is not personal. Sometimes, because we've made those mistakes, we can see potential problems and/or better solutions to a problem than the one you've implemented.
It can be challenging to ensure you're catching all the various changes that occur during a quest, and are properly understanding exactly what changes represent what. There are various things to consider as you develop:
- Enable the dev tools as described in https://github.com/runelite/runelite/wiki/Using-the-client-developer-tools. These will be your bread and butter for checking out IDs, locations, and variable changes
- Always have the Var Inspector open. This will catch most changes to varplayers and varbits.
- Try to predict what will happen, and code ahead of doing actions. If you know you need to go talk to Hans next, and it'll likely progress the quest state, code that and then try doing it.
- For co-ordinates, if you're in an instance, you'll need to use the 'true' position of the instance to define it. The easiest way to do this is by shift-right clicking the tile of interest, marking it, then checking in the terminal the true co-ordinates.
- You should use the north-east corner of an object's centre to define its position. You should also use the dev tool's object inspector to view the IDs of objects vs just the examine functionality, as often objects which will change in shape/form will have a
NullObjectId
which persistents through all its variations.
https://www.osrsbox.com/tools/item-search/
- This website is a god-send for searching for item ids.
https://chisel.weirdgloop.org/varbs/index
- This website is used to find the varbit changes for a quest.
https://github.com/users/Zoinkwiz/projects/1
- This is used to track which quests are currently being worked on and by whom.
If you have any questions that are not covered here, you can ask in our discord channel #development
.