Skip to content

Contribution guide

Zoinkwiz edited this page Feb 4, 2023 · 8 revisions

Contribution guide

Getting started

This project recommends IntelliJ IDEA for development; however you can use whatever IDE you feel comfortable with.

  1. Fork this repository.

  2. Clone the repository to your local machine.

    • For IntelliJ, click here to learn how to do that.
  3. This project uses Runelite's code conventions. Click here to see how to install the required code style.

  4. 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.

Creating a 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.

  1. Any 1.8+ JDK can be used. (Tested with OpenJDK 11 as of 21 FEB 2021).
  2. Set the classpath to towards the quest-helper test module.
    • i.e. -cp quest-helper.test
  3. Add -ea to your VM Options.
    • If the VM Options text field is not visible in IntelliJ's Run Configurations menu. Click on Modify Options and make sure Add VM Options is checked.
  4. Make sure the fully qualified class name is com.questhelper.QuestHelperPluginTest.
  5. 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.

Adding a new 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.

Creating a Quest

Quest Steps

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).


Handling Quest Dialog

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

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 to appendToTooltip 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.


Conditions

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.

Functions

loadSteps

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.

getCombatRequirements, getItemRequirements, getItemRecommended

These are used to provide the relevant details for the sidebar's requirements sections.

getPanels

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.

Making a Pull Request

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.

Tips for developing

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.

Useful websites/links

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.