diff --git a/buildSrc/src/main/kotlin/BootstrapPlugin.kt b/buildSrc/src/main/kotlin/BootstrapPlugin.kt index 4a3824dd19..445959fe45 100644 --- a/buildSrc/src/main/kotlin/BootstrapPlugin.kt +++ b/buildSrc/src/main/kotlin/BootstrapPlugin.kt @@ -17,8 +17,9 @@ class BootstrapPlugin : Plugin { bootstrapDependencies(project(":runelite-client")) } - tasks.register("bootstrap", "openosrs") tasks.register("bootstrapStaging", "staging") + tasks.register("bootstrapNightly", "nightly") + tasks.register("bootstrapStable", "stable") tasks.withType { this.group = "openosrs" diff --git a/buildSrc/src/main/kotlin/BootstrapTask.kt b/buildSrc/src/main/kotlin/BootstrapTask.kt index 3eaf7dcebb..2a022557cc 100644 --- a/buildSrc/src/main/kotlin/BootstrapTask.kt +++ b/buildSrc/src/main/kotlin/BootstrapTask.kt @@ -1,3 +1,4 @@ +import groovy.json.JsonOutput import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* @@ -71,6 +72,10 @@ open class BootstrapTask @Inject constructor(@Input val type: String) : DefaultT { path = "http://repo.runelite.net/net/runelite/jogl/jogl-all/" + version + "/" + it.file.name } + else if (it.file.name.contains("jocl")) + { + path = "http://repo.runelite.net/net/runelite/jocl/jocl/" + version + "/" + it.file.name + } else { println("ERROR: " + it.file.name + " has no download path!") @@ -117,11 +122,13 @@ open class BootstrapTask @Inject constructor(@Input val type: String) : DefaultT "artifacts" to getArtifacts() ).toString() + val prettyJson = JsonOutput.prettyPrint(json) + val bootstrapDir = File("${project.buildDir}/bootstrap") bootstrapDir.mkdirs() File(bootstrapDir, "bootstrap-${type}.json").printWriter().use { out -> - out.println(json) + out.println(prettyJson) } } } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 79dc14b728..a230b4272c 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -27,7 +27,7 @@ object ProjectVersions { const val launcherVersion = "2.2.0" const val rlVersion = "1.6.38" - const val openosrsVersion = "3.5.4" + const val openosrsVersion = "4.0.0" const val rsversion = 193 const val cacheversion = 165 diff --git a/cache/src/main/java/net/runelite/cache/fs/flat/FlatStorage.java b/cache/src/main/java/net/runelite/cache/fs/flat/FlatStorage.java index 92a82e26b7..c8d651bcf6 100644 --- a/cache/src/main/java/net/runelite/cache/fs/flat/FlatStorage.java +++ b/cache/src/main/java/net/runelite/cache/fs/flat/FlatStorage.java @@ -220,7 +220,7 @@ public class FlatStorage implements Storage br.printf("revision=%d\n", idx.getRevision()); br.printf("compression=%d\n", idx.getCompression()); br.printf("crc=%d\n", idx.getCrc()); - br.printf("named=%b\n", idx.getCompression()); + br.printf("named=%b\n", idx.isNamed()); idx.getArchives().sort(Comparator.comparing(Archive::getArchiveId)); for (Archive archive : idx.getArchives()) diff --git a/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java b/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java index 0b017518c8..dc483f35db 100644 --- a/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java +++ b/http-api/src/main/java/net/runelite/http/api/RuneLiteAPI.java @@ -25,10 +25,16 @@ package net.runelite.http.api; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.awt.Color; import java.io.IOException; import java.io.InputStream; +import java.time.Instant; import java.util.Properties; import java.util.concurrent.TimeUnit; +import net.runelite.http.api.gson.ColorTypeAdapter; +import net.runelite.http.api.gson.InstantTypeAdapter; +import net.runelite.http.api.gson.IllegalReflectionExclusion; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.MediaType; @@ -46,7 +52,7 @@ public class RuneLiteAPI public static final String RUNELITE_MACHINEID = "RUNELITE-MACHINEID"; public static final OkHttpClient CLIENT; - public static final Gson GSON = new Gson(); + public static final Gson GSON; public static final MediaType JSON = MediaType.parse("application/json"); public static String userAgent; @@ -96,6 +102,23 @@ public class RuneLiteAPI } }) .build(); + + GsonBuilder gsonBuilder = new GsonBuilder(); + + gsonBuilder + .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) + .registerTypeAdapter(Color.class, new ColorTypeAdapter()); + + boolean assertionsEnabled = false; + assert assertionsEnabled = true; + if (assertionsEnabled) + { + IllegalReflectionExclusion jbe = new IllegalReflectionExclusion(); + gsonBuilder.addSerializationExclusionStrategy(jbe); + gsonBuilder.addDeserializationExclusionStrategy(jbe); + } + + GSON = gsonBuilder.create(); } public static HttpUrl getSessionBase() diff --git a/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java b/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java new file mode 100644 index 0000000000..6f4b003df0 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/gson/ColorTypeAdapter.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020 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.http.api.gson; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.awt.Color; +import java.io.IOException; + +public class ColorTypeAdapter extends TypeAdapter +{ + @Override + public void write(JsonWriter out, Color value) throws IOException + { + if (value == null) + { + out.nullValue(); + return; + } + + int rgba = value.getRGB(); + out.beginObject() + .name("value") + .value(rgba) + .endObject(); + } + + @Override + public Color read(JsonReader in) throws IOException + { + switch (in.peek()) + { + case NULL: + in.nextNull(); + return null; + case BEGIN_OBJECT: + in.beginObject(); + double value = 0; + while (in.peek() != JsonToken.END_OBJECT) + { + switch (in.nextName()) + { + case "value": + value = in.nextDouble(); + break; + default: + in.skipValue(); + break; + } + } + in.endObject(); + return new Color((int) value, true); + } + return null; // throws + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java b/http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java new file mode 100644 index 0000000000..a25ffb79f7 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/gson/IllegalReflectionExclusion.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 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.http.api.gson; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +public class IllegalReflectionExclusion implements ExclusionStrategy +{ + private static final Set PRIVATE_CLASSLOADERS = new HashSet<>(); + + static + { + for (ClassLoader cl = ClassLoader.getSystemClassLoader(); cl != null; ) + { + cl = cl.getParent(); + PRIVATE_CLASSLOADERS.add(cl); + } + } + + @Override + public boolean shouldSkipField(FieldAttributes f) + { + if (!PRIVATE_CLASSLOADERS.contains(f.getDeclaringClass().getClassLoader())) + { + return false; + } + + assert !Modifier.isPrivate(f.getDeclaringClass().getModifiers()) : "gsoning private class " + f.getDeclaringClass().getName(); + try + { + f.getDeclaringClass().getField(f.getName()); + } + catch (NoSuchFieldException e) + { + throw new AssertionError("gsoning private field " + f.getDeclaringClass() + "." + f.getName()); + } + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) + { + return false; + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java b/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java new file mode 100644 index 0000000000..bf92af94b8 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/gson/InstantTypeAdapter.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 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.http.api.gson; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.time.Instant; + +// Just add water! +public class InstantTypeAdapter extends TypeAdapter +{ + @Override + public void write(JsonWriter out, Instant value) throws IOException + { + if (value == null) + { + out.nullValue(); + return; + } + + out.beginObject() + .name("seconds") + .value(value.getEpochSecond()) + .name("nanos") + .value(value.getNano()) + .endObject(); + } + + @Override + public Instant read(JsonReader in) throws IOException + { + if (in.peek() == JsonToken.NULL) + { + in.nextNull(); + return null; + } + + long seconds = 0; + int nanos = 0; + in.beginObject(); + while (in.peek() != JsonToken.END_OBJECT) + { + switch (in.nextName()) + { + case "nanos": + nanos = in.nextInt(); + break; + case "seconds": + seconds = in.nextLong(); + break; + } + } + in.endObject(); + + return Instant.ofEpochSecond(seconds, nanos); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java index 9d4e46e6ea..328d0aa1a7 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/WebsocketGsonFactory.java @@ -25,11 +25,11 @@ package net.runelite.http.api.ws; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.ws.messages.Handshake; import net.runelite.http.api.ws.messages.LoginResponse; import net.runelite.http.api.ws.messages.party.Join; @@ -76,7 +76,7 @@ public class WebsocketGsonFactory public static Gson build(final RuntimeTypeAdapterFactory factory) { - return new GsonBuilder() + return RuneLiteAPI.GSON.newBuilder() .registerTypeAdapterFactory(factory) .create(); } diff --git a/injector/LICENSE b/injector/LICENSE new file mode 100644 index 0000000000..e72bfddabc --- /dev/null +++ b/injector/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/injector/src/main/java/com/openosrs/injector/InjectException.java b/injector/src/main/java/com/openosrs/injector/InjectException.java index aa1a352a0d..d3297ec78e 100644 --- a/injector/src/main/java/com/openosrs/injector/InjectException.java +++ b/injector/src/main/java/com/openosrs/injector/InjectException.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector; diff --git a/injector/src/main/java/com/openosrs/injector/InjectUtil.java b/injector/src/main/java/com/openosrs/injector/InjectUtil.java index f55539fb34..79ceabb196 100644 --- a/injector/src/main/java/com/openosrs/injector/InjectUtil.java +++ b/injector/src/main/java/com/openosrs/injector/InjectUtil.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector; diff --git a/injector/src/main/java/com/openosrs/injector/Injector.java b/injector/src/main/java/com/openosrs/injector/Injector.java index 08294eb0d1..95df2a568f 100644 --- a/injector/src/main/java/com/openosrs/injector/Injector.java +++ b/injector/src/main/java/com/openosrs/injector/Injector.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector; @@ -23,6 +23,7 @@ import com.openosrs.injector.injectors.raw.RenderDraw; import com.openosrs.injector.injectors.raw.ScriptVM; import com.openosrs.injector.rsapi.RSApi; import com.openosrs.injector.transformers.InjectTransformer; +import com.openosrs.injector.transformers.Java8Ifier; import com.openosrs.injector.transformers.SourceChanger; import static net.runelite.deob.util.JarUtil.load; import static net.runelite.deob.util.JarUtil.save; @@ -57,6 +58,8 @@ public class Injector extends InjectData implements InjectTaskHandler { log.debug("[DEBUG] Starting injection"); + transform(new Java8Ifier(this)); + inject(new CreateAnnotations(this)); inject(new InterfaceInjector(this)); diff --git a/injector/src/main/java/com/openosrs/injector/InjectorValidator.java b/injector/src/main/java/com/openosrs/injector/InjectorValidator.java index 1e3bc6b739..c8664dc160 100644 --- a/injector/src/main/java/com/openosrs/injector/InjectorValidator.java +++ b/injector/src/main/java/com/openosrs/injector/InjectorValidator.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector; diff --git a/injector/src/main/java/com/openosrs/injector/Validator.java b/injector/src/main/java/com/openosrs/injector/Validator.java index ba3b7ef037..d4679c7a35 100644 --- a/injector/src/main/java/com/openosrs/injector/Validator.java +++ b/injector/src/main/java/com/openosrs/injector/Validator.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector; diff --git a/injector/src/main/java/com/openosrs/injector/injection/InjectData.java b/injector/src/main/java/com/openosrs/injector/injection/InjectData.java index b201d83c06..378bc42d38 100644 --- a/injector/src/main/java/com/openosrs/injector/injection/InjectData.java +++ b/injector/src/main/java/com/openosrs/injector/injection/InjectData.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injection; diff --git a/injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java b/injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java index a6f29b6f3e..f391d0c321 100644 --- a/injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java +++ b/injector/src/main/java/com/openosrs/injector/injection/InjectTaskHandler.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injection; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java b/injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java index b1c645c544..3554dd3b91 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/AbstractInjector.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java b/injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java index 4bdc647cf3..f29ff939f9 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/CreateAnnotations.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2016-2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java b/injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java index 0dde556645..9fcb6085b4 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/InjectConstruct.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2016-2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java b/injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java index a7e74e848d..ff8a533cd5 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/InjectHook.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2017, Adam * All rights reserved. * diff --git a/injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java b/injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java index b4ca566c3d..0023b4d3e6 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/InjectHookMethod.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/Injector.java b/injector/src/main/java/com/openosrs/injector/injectors/Injector.java index a3d9048e47..e1e497b9e9 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/Injector.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/Injector.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java b/injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java index 5e12bd4c00..19fa8d8033 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/InterfaceInjector.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java b/injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java index 8202be72a7..de042f5514 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/MixinInjector.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2017, Adam * All rights reserved. @@ -438,7 +438,7 @@ public class MixinInjector extends AbstractInjector else if (hasInject) { // Make sure the method doesn't invoke copied methods - for (Instruction i : mixinMethod.getCode().getInstructions()) + /*for (Instruction i : mixinMethod.getCode().getInstructions()) { if (i instanceof InvokeInstruction) { @@ -446,10 +446,10 @@ public class MixinInjector extends AbstractInjector if (copiedMethods.containsKey(ii.getMethod())) { - throw new InjectException("Injected methods cannot invoke copied methods"); + throw new InjectException("Injected methods cannot invoke copied methods " + ii.toString()); } } - } + }*/ Method copy = new Method(targetClass, mixinMethod.getName(), mixinMethod.getDescriptor()); moveCode(copy, mixinMethod.getCode()); diff --git a/injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java b/injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java index d904d3cb26..aaa6e21493 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/RSApiInjector.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2016-2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java index 578b30b005..672a6d10b1 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/AddPlayerToMenu.java @@ -4,7 +4,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors.raw; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java index 762e269613..6f81ecd8c8 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/ClearColorBuffer.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors.raw; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java index 5f6d8c2d40..60a2a900fb 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawAfterWidgets.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2016-2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawMenu.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawMenu.java index b54c568be1..8208405f16 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawMenu.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/DrawMenu.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors.raw; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/Occluder.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/Occluder.java index 14d44773c5..0425462438 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/Occluder.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/Occluder.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors.raw; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/RasterizerAlpha.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/RasterizerAlpha.java index bf38cd42d5..96e5bb4712 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/RasterizerAlpha.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/RasterizerAlpha.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors.raw; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/RenderDraw.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/RenderDraw.java index c31802a391..a36feabad8 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/RenderDraw.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/RenderDraw.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.injectors.raw; diff --git a/injector/src/main/java/com/openosrs/injector/injectors/raw/ScriptVM.java b/injector/src/main/java/com/openosrs/injector/injectors/raw/ScriptVM.java index 1d9c463828..e103bc50ab 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/raw/ScriptVM.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/raw/ScriptVM.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2018 Abex * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectGetter.java b/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectGetter.java index 696753b2b0..29efa77920 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectGetter.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectGetter.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2016-2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectInvoke.java b/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectInvoke.java index 61a273d673..3b98e2d856 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectInvoke.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectInvoke.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2016-2017, Adam * All rights reserved. @@ -43,6 +43,7 @@ import net.runelite.asm.attributes.code.Instructions; import net.runelite.asm.attributes.code.instructions.ALoad; import net.runelite.asm.attributes.code.instructions.BiPush; import net.runelite.asm.attributes.code.instructions.CheckCast; +import net.runelite.asm.attributes.code.instructions.InvokeInterface; import net.runelite.asm.attributes.code.instructions.InvokeStatic; import net.runelite.asm.attributes.code.instructions.InvokeVirtual; import net.runelite.asm.attributes.code.instructions.LDC; @@ -131,6 +132,10 @@ public class InjectInvoke { ins.add(new InvokeStatic(instructions, vanillaMethod.getPoolMethod())); } + else if (vanillaMethod.getClassFile().isInterface()) + { + ins.add(new InvokeInterface(instructions, vanillaMethod.getPoolMethod())); + } else { ins.add(new InvokeVirtual(instructions, vanillaMethod.getPoolMethod())); diff --git a/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectSetter.java b/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectSetter.java index 9eb137d2b7..59eda4e2e4 100644 --- a/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectSetter.java +++ b/injector/src/main/java/com/openosrs/injector/injectors/rsapi/InjectSetter.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. * * Copyright (c) 2017, Adam * All rights reserved. diff --git a/injector/src/main/java/com/openosrs/injector/rsapi/RSApi.java b/injector/src/main/java/com/openosrs/injector/rsapi/RSApi.java index 103e1619ad..def5997c75 100644 --- a/injector/src/main/java/com/openosrs/injector/rsapi/RSApi.java +++ b/injector/src/main/java/com/openosrs/injector/rsapi/RSApi.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.rsapi; diff --git a/injector/src/main/java/com/openosrs/injector/rsapi/RSApiClass.java b/injector/src/main/java/com/openosrs/injector/rsapi/RSApiClass.java index 5283095a72..84a1d55036 100644 --- a/injector/src/main/java/com/openosrs/injector/rsapi/RSApiClass.java +++ b/injector/src/main/java/com/openosrs/injector/rsapi/RSApiClass.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.rsapi; diff --git a/injector/src/main/java/com/openosrs/injector/rsapi/RSApiMethod.java b/injector/src/main/java/com/openosrs/injector/rsapi/RSApiMethod.java index 9700f25330..4d512f2105 100644 --- a/injector/src/main/java/com/openosrs/injector/rsapi/RSApiMethod.java +++ b/injector/src/main/java/com/openosrs/injector/rsapi/RSApiMethod.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.rsapi; diff --git a/injector/src/main/java/com/openosrs/injector/transformers/InjectTransformer.java b/injector/src/main/java/com/openosrs/injector/transformers/InjectTransformer.java index 270b8b7b33..798947c832 100644 --- a/injector/src/main/java/com/openosrs/injector/transformers/InjectTransformer.java +++ b/injector/src/main/java/com/openosrs/injector/transformers/InjectTransformer.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.transformers; diff --git a/injector/src/main/java/com/openosrs/injector/transformers/Java8Ifier.java b/injector/src/main/java/com/openosrs/injector/transformers/Java8Ifier.java new file mode 100644 index 0000000000..7d37c48368 --- /dev/null +++ b/injector/src/main/java/com/openosrs/injector/transformers/Java8Ifier.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020, ThatGamerBlue + * All rights reserved. + * + * This code is licensed under GPL3, see the complete license in + * the LICENSE file in the root directory of this submodule. + */ +package com.openosrs.injector.transformers; + +import com.openosrs.injector.InjectException; +import com.openosrs.injector.injection.InjectData; +import java.util.ListIterator; +import net.runelite.asm.ClassFile; +import net.runelite.asm.Method; +import net.runelite.asm.attributes.Code; +import net.runelite.asm.attributes.code.Exception; +import net.runelite.asm.attributes.code.Instruction; +import net.runelite.asm.attributes.code.Instructions; +import net.runelite.asm.attributes.code.Label; +import net.runelite.asm.attributes.code.instructions.InvokeSpecial; +import org.objectweb.asm.Opcodes; + +public class Java8Ifier extends InjectTransformer +{ + public Java8Ifier(InjectData inject) + { + super(inject); + } + + @Override + void transformImpl() + { + inject.forEachPair(this::makeJava8); + } + + private void makeJava8(ClassFile rsc, ClassFile vanilla) + { + vanilla.setVersion(Opcodes.V1_8); + for (Method method : vanilla.getMethods()) + { + if (!method.getName().equals("")) + { + continue; + } + + fixTryCatch(method); + } + } + + private void fixTryCatch(Method method) + { + Code code = method.getCode(); + + if (code.getExceptions().getExceptions().stream().noneMatch(e -> e.getCatchType() != null && e.getCatchType().getName().equals("java/lang/RuntimeException"))) + { + return; + } + + Instructions instructions = code.getInstructions(); + ListIterator insnIt = instructions.listIterator(); + Instruction insn; + Label firstLabel = null; + Label injectedLabel = null; + while (insnIt.hasNext()) + { + insn = insnIt.next(); + + if (firstLabel == null && insn instanceof Label) + { + firstLabel = (Label) insn; + } + else if (insn instanceof InvokeSpecial) + { + if (((InvokeSpecial) insn).getMethod().getName().equals("")) + { + injectedLabel = new Label(instructions); + insnIt.add(injectedLabel); + break; + } + } + } + + // this should never happen + if (firstLabel == null) + { + throw new InjectException("Label missing from ctor " + method.toString() + " even though exception exists"); + } + + // label was injected + if (injectedLabel != null) + { + for (Exception ex : code.getExceptions().getExceptions()) + { + if (ex.getStart().equals(firstLabel)) + { + ex.setStart(injectedLabel); + } + } + } + } +} \ No newline at end of file diff --git a/injector/src/main/java/com/openosrs/injector/transformers/SourceChanger.java b/injector/src/main/java/com/openosrs/injector/transformers/SourceChanger.java index 7e56e46cb3..a45a8aedde 100644 --- a/injector/src/main/java/com/openosrs/injector/transformers/SourceChanger.java +++ b/injector/src/main/java/com/openosrs/injector/transformers/SourceChanger.java @@ -3,7 +3,7 @@ * All rights reserved. * * This code is licensed under GPL3, see the complete license in - * the LICENSE file in the root directory of this source tree. + * the LICENSE file in the root directory of this submodule. */ package com.openosrs.injector.transformers; diff --git a/runelite-api/src/main/java/com/openosrs/api/AnimationID.java b/runelite-api/src/main/java/com/openosrs/api/AnimationID.java deleted file mode 100644 index 8a3de35994..0000000000 --- a/runelite-api/src/main/java/com/openosrs/api/AnimationID.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.openosrs.api; - -//This is where Animation IDs should go that aren't found in RuneLite -public class AnimationID extends net.runelite.api.AnimationID -{ - - // NPC animations - public static final int TZTOK_JAD_MAGIC_ATTACK = 2656; - public static final int TZTOK_JAD_RANGE_ATTACK = 2652; - public static final int HELLHOUND_DEFENCE = 6566; - - public static final int FISHING_TRAILBLAZER_HARPOON_2 = 8785; - - public static final int CRYSTALLINE_RAT_DEATH = 8334; - public static final int CRYSTALLINE_BAT_DEATH = 4917; - public static final int CRYSTALLINE_WOLF_DEATH = 8335; - public static final int CRYSTALLINE_SPIDER_DEATH = 8338; - public static final int CRYSTALLINE_UNICORN_DEATH = 6377; - public static final int CRYSTALLINE_DRAGON_DEATH = 92; - public static final int CRYSTALLINE_BEAR_DEATH = 4929; - public static final int CRYSTALLINE_DARK_BEAST_DEATH = 2733; - public static final int CORRUPTED_SCORPION_DEATH = 6256; - - public static final int TABLET_TELEPORT = 4069; - - public static final int THIEVING_STALL = 832; - public static final int PICKPOCKET_SUCCESS = 881; - public static final int PULL_LEVER = 2140; - public static final int STANDARD_PURPLE_TELEPORT = 714; - public static final int ECTOPHIAL_TELEPORT = 878; - public static final int FAIRY_RING_TELEPORT = 3265; - public static final int SCROLL_TELEPORT = 3864; - public static final int XERICS_TALISMAN_TELEPORT = 3865; - public static final int WILDERNESS_OBELISK_TELEPORT = 3945; - public static final int SEED_POD_TELEPORT = 4544; - - //block animations for players and perhaps npcs as well? - public static final int BLOCK_DEFENDER = 4177; - public static final int BLOCK_NO_SHIELD = 420; - public static final int BLOCK_SHIELD = 1156; - public static final int BLOCK_SWORD = 388; - public static final int BLOCK_UNARMED = 424; // Same Animation as failed pickpocked - - public static final int NIGHTMARE_DEATH = 8612; - public static final int LOW_LEVEL_MAGIC_ATTACK = 1162; - public static final int HIGH_LEVEL_MAGIC_ATTACK = 1167; - public static final int BLOWPIPE_ATTACK = 5061; - - // NPC animations - public static final int BLACKJACK_KO = 838; - - // Fight Caves - public static final int TZTOK_JAD_MELEE_ATTACK = 2655; - public static final int TOK_XIL_RANGE_ATTACK = 2633; - public static final int TOK_XIL_MELEE_ATTACK = 2628; - public static final int KET_ZEK_MELEE_ATTACK = 2644; - public static final int KET_ZEK_MAGE_ATTACK = 2647; - public static final int MEJ_KOT_MELEE_ATTACK = 2637; - public static final int MEJ_KOT_HEAL_ATTACK = 2639; - - // Vorkath - public static final int VORKATH_WAKE_UP = 7950; - public static final int VORKATH_DEATH = 7949; - public static final int VORKATH_SLASH_ATTACK = 7951; - public static final int VORKATH_ATTACK = 7952; - public static final int VORKATH_FIRE_BOMB_OR_SPAWN_ATTACK = 7960; - public static final int VORKATH_ACID_ATTACK = 7957; - - // Tekton - public static final int TEKTON_ANVIL = 7475; - public static final int TEKTON_AUTO1 = 7482; - public static final int TEKTON_AUTO2 = 7483; - public static final int TEKTON_AUTO3 = 7484; - public static final int TEKTON_FAST_AUTO1 = 7478; - public static final int TEKTON_FAST_AUTO2 = 7488; - public static final int TEKTON_ENRAGE_AUTO1 = 7492; - public static final int TEKTON_ENRAGE_AUTO2 = 7493; - public static final int TEKTON_ENRAGE_AUTO3 = 7494; - - // Hydra - public static final int HYDRA_WALKING = 8232; - public static final int HYDRA_IDLE = 8233; - public static final int HYDRA_POISON_1 = 8234; - public static final int HYDRA_RANGED_1 = 8235; - public static final int HYDRA_MAGIC_1 = 8236; - public static final int HYDRA_1_1 = 8237; - public static final int HYDRA_1_2 = 8238; - public static final int HYDRA_LIGHTNING = 8241; - public static final int HYDRA_RANGED_2 = 8242; - public static final int HYDRA_MAGIC_2 = 8243; - public static final int HYDRA_2_1 = 8244; - public static final int HYDRA_2_2 = 8245; - public static final int HYDRA_FIRE = 8248; - public static final int HYDRA_RANGED_3 = 8249; - public static final int HYDRA_MAGIC_3 = 8250; - public static final int HYDRA_3_1 = 8251; - public static final int HYDRA_3_2 = 8252; - public static final int HYDRA_MAGIC_4 = 8254; - public static final int HYDRA_POISON_4 = 8254; - public static final int HYDRA_RANGED_4 = 8255; - public static final int HYDRA_RANGED_OR_POISON_ATTACK = 8256; - public static final int HYDRA_4_1 = 8257; - public static final int HYDRA_4_2 = 8258; - - // Inferno animations - public static final int JAL_NIB = 7574; - public static final int JAL_MEJRAH = 7578; - public static final int JAL_MEJRAH_STAND = 7577; - public static final int JAL_AK_RANGE_ATTACK = 7581; - public static final int JAL_AK_MELEE_ATTACK = 7582; - public static final int JAL_AK_MAGIC_ATTACK = 7583; - public static final int JAL_IMKOT = 7597; - public static final int JAL_XIL_MELEE_ATTACK = 7604; - public static final int JAL_XIL_RANGE_ATTACK = 7605; - public static final int JAL_ZEK_MAGE_ATTACK = 7610; - public static final int JAL_ZEK_MELEE_ATTACK = 7612; - public static final int JALTOK_JAD_MELEE_ATTACK = 7590; - public static final int JALTOK_JAD_MAGE_ATTACK = 7592; - public static final int JALTOK_JAD_RANGE_ATTACK = 7593; - public static final int TZKAL_ZUK = 7566; - public static final int JAL_MEJJAK = 2858; - - // General Graardor - public static final int MINION_AUTO1 = 6154; - public static final int MINION_AUTO2 = 6156; - public static final int MINION_AUTO3 = 7071; - public static final int MINION_AUTO4 = 7073; - public static final int GENERAL_AUTO1 = 7018; - public static final int GENERAL_AUTO2 = 7020; - public static final int GENERAL_AUTO3 = 7021; - - // Kr'il Tsutsaroth - public static final int ZAMMY_GENERIC_AUTO = 64; - public static final int KRIL_AUTO = 6948; - public static final int KRIL_SPEC = 6950; - public static final int ZAKL_AUTO = 7077; - public static final int BALFRUG_AUTO = 4630; - - // Commander Zilyana - public static final int ZILYANA_MELEE_AUTO = 6964; - public static final int ZILYANA_AUTO = 6967; - public static final int ZILYANA_SPEC = 6970; - public static final int STARLIGHT_AUTO = 6376; - public static final int BREE_AUTO = 7026; - public static final int GROWLER_AUTO = 7037; - - // Kree'arra - public static final int KREE_RANGED = 6978; - public static final int SKREE_AUTO = 6955; - public static final int GEERIN_AUTO = 6956; - public static final int GEERIN_FLINCH = 6958; - public static final int KILISA_AUTO = 6957; - - // Vetion - public static final int VETION_EARTHQUAKE = 5507; - - // Zulrah - public static final int ZULRAH_DEATH = 5804; - public static final int ZULRAH_PHASE = 5072; - - //Dagannoth Kings - public static final int DAG_REX = 2853; - public static final int DAG_PRIME = 2854; - public static final int DAG_SUPREME = 2855; - - // Lizardman shaman - public static final int LIZARDMAN_SHAMAN_SPAWN = 7157; - public static final int LIZARDMAN_SHAMAN_SPAWN_EXPLOSION = 7159; - - // Cerberus - public static final int CERBERUS_MAGIC_ATTACK = 4489; - public static final int CERBERUS_RANGED_ATTACK = 4490; - public static final int CERBERUS_MELEE_ATTACK = 4491; - public static final int CERBERUS_LAVA_ATTACK = 4493; - public static final int CERBERUS_SUMMON_GHOSTS = 4494; - - // Gauntlet Hunleff - public static final int HUNLEFF_TRAMPLE = 8420; - public static final int HUNLEFF_ATTACK = 8419; - public static final int HUNLEFF_TORNADO = 8418; - public static final int HUNLLEF_SWITCH_TO_MAGIC = 8754; - public static final int HUNLLEF_SWITCH_TO_RANGED = 8755; - - //Zalcano - public static final int ZALCANO_KNOCKED_DOWN = 8437; - public static final int ZALCANO_WAKEUP = 8439; - public static final int ZALCANO_ROCK_GLOWING = 8448; - - // Theatre of Blood - Sugadinti Maiden - public static final int SUGADINTI_MAIDEN_BLOOD_SPLAT_ATTACK = 8091; - public static final int SUGADINTI_MAIDEN_MAGIC_ATTACK = 8092; - public static final int SUGADINTI_MAIDEN_DEATH = 8094; - - // Theatre of Blood - Pestilent Bloat - public static final int BLOAT_SLEEP = 8082; - - // Theatre of Blood - Sotetseg - public static final int SOTETSEG_MELEE_ATTACK = 8138; - public static final int SOTETSEG_REGULAR_PROJECTILE_ATTACK = 8139; - - // Theatre of Blood - Verzik Vitur - public static final int VERZIK_PHASE_1_MAGIC_ATTACK = 8109; - public static final int VERZIK_PHASE_1_MAGIC_ATTACK_CHANNEL = 8110; - public static final int VERZIK_CHANGE_TO_PHASE_2 = 8111; - public static final int VERZIK_PHASE_2_MAGIC_ATTACK = 8114; - public static final int VERZIK_PHASE_2_BELLY_FLOP_ATTACK_1 = 8116; - public static final int VERZIK_PHASE_2_HEALING_CHANNEL = 8117; - public static final int VERZIK_PHASE_2_BELLY_FLOP_ATTACK_2 = 8118; - public static final int VERZIK_CHANGE_TO_PHASE_3 = 8119; - public static final int VERZIK_PHASE_3_MELEE_ATTACK = 8123; - public static final int VERZIK_PHASE_3_MAGIC_ATTACK = 8124; - public static final int VERZIK_PHASE_3_RANGED_ATTACK = 8125; - public static final int VERZIK_PHASE_3_GREEN_POOL_ATTACK = 8126; - public static final int VERZIK_PHASE_3_WEB_ATTACK = 8127; - public static final int VERZIK_DEATH_1 = 8128; - public static final int VERZIK_DEATH_2 = 8129; - - // The Nightmare of Ashihama - public static final int NIGHTMARE_SPAWN_SLEEPWALKERS = 8572; - public static final int NIGHTMARE_FLOATY = 8592; - public static final int NIGHTMARE_WALKING = 8592; - public static final int NIGHTMARE_IDLE = 8593; - public static final int NIGHTMARE_MELEE_ATTACK = 8594; - public static final int NIGHTMARE_MAGIC_ATTACK = 8595; - public static final int NIGHTMARE_RANGED_ATTACK = 8596; - public static final int NIGHTMARE_SURGE_ATTACK = 8597; - public static final int NIGHTMARE_GHOST_AOE_ATTACK = 8598; - public static final int NIGHTMARE_CURSE_PRAYERS_ATTACK = 8599; - public static final int NIGHTMARE_SPAWN_INFECTIOUS_SPORES = 8600; - public static final int NIGHTMARE_SPAWN_ROOM_SECTION_FLOWERS = 8601; - public static final int NIGHTMARE_CHANNEL_DEVASTATING_ATTACK = 8604; - public static final int NIGHTMARE_SWITCH_TO_DEVIL_PHASE = 8605; - public static final int NIGHTMARE_PARASITE_ATTACK = 8606; - public static final int NIGHTMARE_JUMP_DOWN = 8607; - public static final int NIGHTMARE_SINK_DOWN = 8608; - public static final int NIGHTMARE_JUMP_UP = 8609; - public static final int NIGHTMARE_JUMP_UP_2 = 8610; - public static final int NIGHTMARE_WAKE_UP = 8611; -} diff --git a/runelite-api/src/main/java/com/openosrs/api/Varbits.java b/runelite-api/src/main/java/com/openosrs/api/Varbits.java deleted file mode 100644 index bc98952741..0000000000 --- a/runelite-api/src/main/java/com/openosrs/api/Varbits.java +++ /dev/null @@ -1,312 +0,0 @@ -package com.openosrs.api; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public enum Varbits -{ - /* - * Kharedst's Memoirs Teleport Item - */ - KHAREDSTS_MEMOIRS_CHARGES(6035), - - /** - * Grand Exchange - */ - GRAND_EXCHANGE_PRICE_PER_ITEM(4398), - - - /** - * Locked Prayers - * 0-7 = Locked - * 8 = Unlocked - */ - CHIVPIETY_UNLOCKED(3909), - - /** - * Locked Prayers - * 0 = Locked - * 1 = Unlocked - */ - - RIGOUR_UNLOCKED(5451), - AUGURY_UNLOCKED(5452), - PRESERVE_UNLOCKED(5453), - - /** - * Theatre of Blood 1=In Party, 2=Inside/Spectator, 3=Dead Spectating - */ - THEATRE_OF_BLOOD(6440), - BLOAT_DOOR(6447), - - /** - * Theatre of Blood orb varbits each number stands for the player's health on a scale of 1-27 (I think), 0 hides the orb - */ - THEATRE_OF_BLOOD_ORB_1(6442), - THEATRE_OF_BLOOD_ORB_2(6443), - THEATRE_OF_BLOOD_ORB_3(6444), - THEATRE_OF_BLOOD_ORB_4(6445), - THEATRE_OF_BLOOD_ORB_5(6446), - - /** - * Nightmare Zone - */ - NMZ_ABSORPTION(3956), - NMZ_POINTS(3949), - NMZ_OVERLOAD(3955), - - /** - * Pyramid plunder - */ - PYRAMID_PLUNDER_SARCO_OPEN(2362), - PYRAMID_PLUNDER_CHEST_OPEN(2363), - PYRAMID_PLUNDER_ROOM_LOCATION(2365), - PYRAMID_PLUNDER_TIMER(2375), - PYRAMID_PLUNDER_THIEVING_LEVEL(2376), - PYRAMID_PLUNDER_ROOM(2377), - - /** - * In the Wilderness - */ - IN_THE_WILDERNESS(5963), - - /** - * Kingdom Management - */ - KINGDOM_FAVOR(72), - KINGDOM_COFFER(74), - KINGDOM_WORKERS_WOOD(81), - KINGDOM_WORKERS_HERBS(82), - KINGDOM_WORKERS_FISHING(83), - KINGDOM_WORKERS_MINING(84), - KINGDOM_WORKERS_FISH_COOKED_BUTTON(135), // 0 - Raw, 1 - Cooked - KINGDOM_WORKERS_HARDWOOD(2131), - KINGDOM_WORKERS_FARM(2132), - KINGDOM_WORKERS_HARDWOOD_BUTTON(2133), // 0 - Mahogany, 1 - Teak, 2 - Both - KINGDOM_WORKERS_HERBS_BUTTON(2134), // 0 - Herbs, 1 - Flax - - /** - * Varbit used for Slayer reward points - */ - SLAYER_REWARD_POINTS(4068), - - /** - * 0 = standard - * 1 = ancients - * 2 = lunars - * 3 = arrceus - **/ - SPELLBOOK(4070), - - /** - * Bank settings/flags - **/ - BANK_NOTE_FLAG(3958), - - - /** - * Spells being auto-casted - */ - AUTO_CAST_SPELL(276), - - /** - * Temple Trekking - */ - TREK_POINTS(1955), - TREK_STARTED(1956), - TREK_EVENT(1958), - TREK_STATUS(6719), - BLOAT_ENTERED_ROOM(6447), - - /** - * f2p Quest varbits, these don't hold the completion value. - */ - QUEST_DEMON_SLAYER(2561), - QUEST_GOBLIN_DIPLOMACY(2378), - QUEST_MISTHALIN_MYSTERY(3468), - QUEST_THE_CORSAIR_CURSE(6071), - QUEST_X_MARKS_THE_SPOT(8063), - QUEST_ERNEST_LEVER_A(1788), - QUEST_ERNEST_LEVER_B(1789), - QUEST_ERNEST_LEVER_C(1790), - QUEST_ERNEST_LEVER_D(1791), - QUEST_ERNEST_LEVER_E(1792), - QUEST_ERNEST_LEVER_F(1793), - - /** - * member Quest varbits, these don't hold the completion value. - */ - QUEST_ANIMAL_MAGNETISM(3185), - QUEST_BETWEEN_A_ROCK(299), - QUEST_CONTACT(3274), - QUEST_ZOGRE_FLESH_EATERS(487), - QUEST_DARKNESS_OF_HALLOWVALE(2573), - QUEST_DEATH_TO_THE_DORGESHUUN(2258), - QUEST_DESERT_TREASURE(358), - QUEST_DEVIOUS_MINDS(1465), - QUEST_EAGLES_PEAK(2780), - QUEST_ELEMENTAL_WORKSHOP_II(2639), - QUEST_ENAKHRAS_LAMENT(1560), - QUEST_ENLIGHTENED_JOURNEY(2866), - QUEST_THE_EYES_OF_GLOUPHRIE(2497), - QUEST_FAIRYTALE_I_GROWING_PAINS(1803), - QUEST_FAIRYTALE_II_CURE_A_QUEEN(2326), - QUEST_THE_FEUD(334), // 14 = able to pickpocket - QUEST_FORGETTABLE_TALE(822), - QUEST_GARDEN_OF_TRANQUILLITY(961), - QUEST_GHOSTS_AHOY(217), - QUEST_THE_GIANT_DWARF(571), - QUEST_THE_GOLEM(346), - QUEST_HORROR_FROM_THE_DEEP(34), - QUEST_ICTHLARINS_LITTLE_HELPER(418), - QUEST_IN_AID_OF_THE_MYREQUE(1990), - QUEST_THE_LOST_TRIBE(532), - QUEST_LUNAR_DIPLOMACY(2448), - QUEST_MAKING_HISTORY(1383), - QUEST_MOUNTAIN_DAUGHTER(260), - QUEST_MOURNINGS_END_PART_II(1103), - QUEST_MY_ARMS_BIG_ADVENTURE(2790), - QUEST_RATCATCHERS(1404), - QUEST_RECIPE_FOR_DISASTER(1850), - QUEST_RECRUITMENT_DRIVE(657), - QUEST_ROYAL_TROUBLE(2140), - QUEST_THE_SLUG_MENACE(2610), - QUEST_SHADOW_OF_THE_STORM(1372), - QUEST_A_SOULS_BANE(2011), - QUEST_SPIRITS_OF_THE_ELID(1444), - QUEST_SWAN_SONG(2098), - QUEST_A_TAIL_OF_TWO_CATS(1028), - QUEST_TEARS_OF_GUTHIX(451), - QUEST_WANTED(1051), - QUEST_COLD_WAR(3293), - QUEST_THE_FREMENNIK_ISLES(3311), - QUEST_TOWER_OF_LIFE(3337), - QUEST_WHAT_LIES_BELOW(3523), - QUEST_OLAFS_QUEST(3534), - QUEST_ANOTHER_SLICE_OF_HAM(3550), - QUEST_DREAM_MENTOR(3618), - QUEST_GRIM_TALES(2783), - QUEST_KINGS_RANSOM(3888), - QUEST_MONKEY_MADNESS_II(5027), - QUEST_CLIENT_OF_KOUREND(5619), - QUEST_BONE_VOYAGE(5795), - QUEST_THE_QUEEN_OF_THIEVES(6037), - QUEST_THE_DEPTHS_OF_DESPAIR(6027), - QUEST_DRAGON_SLAYER_II(6104), - QUEST_TALE_OF_THE_RIGHTEOUS(6358), - QUEST_A_TASTE_OF_HOPE(6396), - QUEST_MAKING_FRIENDS_WITH_MY_ARM(6528), - QUEST_THE_ASCENT_OF_ARCEUUS(7856), - QUEST_THE_FORSAKEN_TOWER(7796), - //TODO - QUEST_SONG_OF_THE_ELVES(7796), - - /** - * mini-quest varbits, these don't hold the completion value. - */ - QUEST_ARCHITECTURAL_ALLIANCE(4982), - QUEST_BEAR_YOUR_SOUL(5078), - QUEST_CURSE_OF_THE_EMPTY_LORD(821), - QUEST_ENCHANTED_KEY(1391), - QUEST_THE_GENERALS_SHADOW(3330), - QUEST_SKIPPY_AND_THE_MOGRES(1344), - QUEST_LAIR_OF_TARN_RAZORLOR(3290), - QUEST_FAMILY_PEST(5347), - QUEST_THE_MAGE_ARENA_II(6067), - //TODO - QUEST_IN_SEARCH_OF_KNOWLEDGE(6067), - - /** - * Spellbook filtering (1 = unfiltered, 0 = filtered) - */ - FILTER_SPELLBOOK(6718), - - /** - * POH Building mode (1 = yes, 0 = no) - */ - BUILDING_MODE(2176), - - /** - * 1 if in game, 0 if not - */ - LMS_IN_GAME(5314), - - /** - * Amount of pvp kills in current game - */ - LMS_KILLS(5315), - - /** - * The x coordinate of the final safespace (world coord) - */ - LMS_SAFE_X(5316), - - LMS_POISON_PROGRESS(5317), - - /** - * The y coordinate of the final safespace (world coord) - */ - LMS_SAFE_Y(5320), - - /** - * 1 is true, 0 is false. - */ - GAUNTLET_FINAL_ROOM_ENTERED(9177), - - /** - * 1 is true, 0 is false. - */ - GAUNTLET_ENTERED(9178), - - WITHDRAW_X_AMOUNT(3960), - - IN_PVP_AREA(8121), - - /** - * Value of hotkey varbits can be 0-13 - * 0 corresponds to no hotkey set - * 1-12 correspond to F1-F12 respectively - * 13 corresponds to escape - */ - COMBAT_TAB_HOTKEY(4675), - STATS_TAB_HOTKEY(4676), - QUESTS_TAB_HOTKEY(4677), - INVENTORY_TAB_HOTKEY(4678), - EQUIPMENT_TAB_HOTKEY(4679), - PRAYER_TAB_HOTKEY(4680), - SPELLBOOK_TAB_HOTKEY(4682), - FRIENDS_TAB_HOTKEY(4684), - ACCOUNT_MANAGEMENT_TAB_HOTKEY(6517), - LOGOUT_TAB_HOTKEY(4689), - OPTIONS_TAB_HOTKEY(4686), - EMOTES_TAB_HOTKEY(4687), - CLAN_TAB_HOTKEY(4683), - MUSIC_TAB_HOTKEY(4688), - - /** - * Chat Notifications settings - *
- * LOOT_DROP_NOTIFICATIONS: 1 is true, 0 is false - * LOOT_DROP_NOTIFICATIONS_VALUE: gp value - * UNTRADEABLE_LOOT_NOTIFICATIONS: 1 is true, 0 is false - * BOSS_KILL_COUNT_UPDATES: 1 is filtered, 0 is unfiltered - * DROP_ITEM_WARNINGS: 1 is true, 0 is false - * DROP_ITEM_WARNINGS_VALUE: gp value - */ - LOOT_DROP_NOTIFICATIONS(5399), - LOOT_DROP_NOTIFICATIONS_VALUE(5400), - UNTRADEABLE_LOOT_NOTIFICATIONS(5402), - BOSS_KILL_COUNT_UPDATES(4930), - DROP_ITEM_WARNINGS(5411), - DROP_ITEM_WARNINGS_VALUE(5412), - - PARASITE(10151), - ; - - /** - * The raw varbit ID. - */ - private final int id; -} diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java index 1a06b2dc7f..4a0a3eade0 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -290,4 +290,6 @@ public interface Actor extends Renderable, Locatable * @return */ boolean isDead(); + + boolean isMoving(); } diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java index 45b2369f23..7c4d40e61c 100644 --- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java @@ -30,7 +30,7 @@ package net.runelite.api; * Note: This class is not complete and may not contain a specific animation * required. */ -public class AnimationID +public final class AnimationID { public static final int IDLE = -1; public static final int HERBLORE_PESTLE_AND_MORTAR = 364; @@ -97,6 +97,7 @@ public class AnimationID public static final int CRAFTING_SPINNING = 894; public static final int CRAFTING_POTTERS_WHEEL = 883; public static final int CRAFTING_POTTERY_OVEN = 24975; + public static final int CRAFTING_LOOM = 2270; public static final int SMITHING_SMELTING = 899; public static final int SMITHING_CANNONBALL = 827; //cball smithing uses this and SMITHING_SMELTING public static final int SMITHING_ANVIL = 898; @@ -209,6 +210,12 @@ public class AnimationID public static final int HOME_MAKE_TABLET = 4067; public static final int DRAGONFIRE_SHIELD_SPECIAL = 6696; + // Ectofuntus animations + public static final int ECTOFUNTUS_FILL_SLIME_BUCKET = 4471; + public static final int ECTOFUNTUS_GRIND_BONES = 1648; + public static final int ECTOFUNTUS_INSERT_BONES = 1649; + public static final int ECTOFUNTUS_EMPTY_BIN = 1650; + // NPC animations public static final int TZTOK_JAD_MAGIC_ATTACK = 2656; public static final int TZTOK_JAD_RANGE_ATTACK = 2652; diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 5d548fb2ae..1a1ec1a921 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -124,12 +124,25 @@ public interface Client extends GameEngine /** * Adds a new chat message to the chatbox. * - * @param type the type of message - * @param name the name of the player that sent the message + * @param type the type of message + * @param name the name of the player that sent the message * @param message the message contents - * @param sender the sender/channel name + * @param sender the sender/channel name + * @return the message node for the message */ - void addChatMessage(ChatMessageType type, String name, String message, String sender); + MessageNode addChatMessage(ChatMessageType type, String name, String message, String sender); + + /** + * Adds a new chat message to the chatbox. + * + * @param type the type of message + * @param name the name of the player that sent the message + * @param message the message contents + * @param sender the sender/channel name + * @param postEvent whether to post the chat message event + * @return the message node for the message + */ + MessageNode addChatMessage(ChatMessageType type, String name, String message, String sender, boolean postEvent); /** * Gets the current game state. @@ -821,7 +834,7 @@ public interface Client extends GameEngine * @param varps passed varps * @param varpId the VarpPlayer id * @return the value - * @see VarPlayer#id + * @see VarPlayer#getId() */ int getVarpValue(int[] varps, int varpId); @@ -1542,7 +1555,7 @@ public interface Client extends GameEngine * * @param state the new player hidden state */ - void setPlayersHidden(boolean state); + void setOthersHidden(boolean state); /** * Sets whether 2D sprites related to the other players are hidden. @@ -1550,7 +1563,7 @@ public interface Client extends GameEngine * * @param state the new player 2D hidden state */ - void setPlayersHidden2D(boolean state); + void setOthersHidden2D(boolean state); /** * Sets whether or not friends are hidden. @@ -1566,6 +1579,13 @@ public interface Client extends GameEngine */ void setFriendsChatMembersHidden(boolean state); + /** + * Sets whether or not ignored players are hidden. + * + * @param state the new ignored player hidden state + */ + void setIgnoresHidden(boolean state); + /** * Sets whether the local player is hidden. * @@ -1588,62 +1608,6 @@ public interface Client extends GameEngine */ void setNPCsHidden(boolean state); - /** - * Increments the counter for how many times this npc has been selected to be hidden - * - * @param name npc name - */ - void addHiddenNpcName(String name); - - /** - * Decrements the counter for how many times this npc has been selected to be hidden - * - * @param name npc name - */ - void removeHiddenNpcName(String name); - - /** - * Forcibly unhides an npc by setting its counter to zero - * - * @param name npc name - */ - void forciblyUnhideNpcName(String name); - - /** - * Get the list of NPC indices that are currently hidden - * - * @return all of the current hidden NPC Indices - */ - List getHiddenNpcIndices(); - - /** - * If an NPC index is in this List then do not render it - * - * @param npcIndices the npc indices to hide - */ - void setHiddenNpcIndices(List npcIndices); - - /** - * Increments the counter for how many times this npc has been selected to be hidden on death - * - * @param name npc name - */ - void addHiddenNpcDeath(String name); - - /** - * Decrements the counter for how many times this npc has been selected to be hidden on death - * - * @param name npc name - */ - void removeHiddenNpcDeath(String name); - - /** - * Forcibly unhides a hidden-while-dead npc by setting its counter to zero - * - * @param name npc name - */ - void forciblyUnhideNpcDeath(String name); - /** * Sets whether 2D sprites (ie. overhead prayers) related to * the NPCs are hidden. diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index ce3659ae02..9998d54923 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -376,7 +376,7 @@ public class Perspective */ public static Polygon getCanvasTilePoly(@Nonnull Client client, @Nonnull LocalPoint localLocation, int zOffset) { - return getCanvasTileAreaPoly(client, localLocation, 1, zOffset); + return getCanvasTileAreaPoly(client, localLocation, 1, 1, zOffset); } /** @@ -389,7 +389,7 @@ public class Perspective */ public static Polygon getCanvasTileAreaPoly(@Nonnull Client client, @Nonnull LocalPoint localLocation, int size) { - return getCanvasTileAreaPoly(client, localLocation, size, 0); + return getCanvasTileAreaPoly(client, localLocation, size, size, 0); } /** @@ -397,23 +397,25 @@ public class Perspective * * @param client the game client * @param localLocation the center location of the AoE - * @param size the size of the area (ie. 3x3 AoE evaluates to size 3) + * @param sizeX the size of the area in tiles on the x axis + * @param sizeY the size of the area in tiles on the y axis * @param zOffset offset from ground plane * @return a polygon representing the tiles in the area */ public static Polygon getCanvasTileAreaPoly( @Nonnull Client client, @Nonnull LocalPoint localLocation, - int size, + int sizeX, + int sizeY, int zOffset) { final int plane = client.getPlane(); - final int swX = localLocation.getX() - (size * LOCAL_TILE_SIZE / 2); - final int swY = localLocation.getY() - (size * LOCAL_TILE_SIZE / 2); + final int swX = localLocation.getX() - (sizeX * LOCAL_TILE_SIZE / 2); + final int swY = localLocation.getY() - (sizeY * LOCAL_TILE_SIZE / 2); - final int neX = localLocation.getX() + (size * LOCAL_TILE_SIZE / 2); - final int neY = localLocation.getY() + (size * LOCAL_TILE_SIZE / 2); + final int neX = localLocation.getX() + (sizeX * LOCAL_TILE_SIZE / 2); + final int neY = localLocation.getY() + (sizeY * LOCAL_TILE_SIZE / 2); final byte[][][] tileSettings = client.getTileSettings(); diff --git a/runelite-api/src/main/java/net/runelite/api/Tile.java b/runelite-api/src/main/java/net/runelite/api/Tile.java index 13b4a14bd7..46ada0f10e 100644 --- a/runelite-api/src/main/java/net/runelite/api/Tile.java +++ b/runelite-api/src/main/java/net/runelite/api/Tile.java @@ -59,6 +59,13 @@ public interface Tile extends TileObject */ GroundObject getGroundObject(); + /** + * Sets the object on the ground layer of the tile. + * + * @param groundObject the ground object + */ + void setGroundObject(GroundObject groundObject); + /** * Gets the wall of the tile. * diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java index 32b000971e..0bdf355c33 100644 --- a/runelite-api/src/main/java/net/runelite/api/Varbits.java +++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java @@ -101,6 +101,10 @@ public enum Varbits PRAYER_RIGOUR(5464), PRAYER_AUGURY(5465), + RIGOUR_UNLOCKED(5451), + AUGURY_UNLOCKED(5452), + PRESERVE_UNLOCKED(5453), + /** * Diary Entries */ @@ -182,6 +186,10 @@ public enum Varbits * Defensive casting mode */ DEFENSIVE_CASTING_MODE(2668), + /** + * Spells being auto-casted + */ + AUTO_CAST_SPELL(276), /** * Options @@ -397,6 +405,19 @@ public enum Varbits */ QUEST_THE_HAND_IN_THE_SAND(1527), + /** + * 0 = Sir Bedivere + * 1 = Sir Pelleas + * 2 = Sir Tristram + * 3 = Sir Palomedes + * 4 = Sir Lucan + * 5 = Sir Gawain + * 6 = Sir Kay + * 7 = Sir Lancelot + * 8 = Completed (Chivalry and Piety are unlocked) + */ + CAMELOT_TRAINING_ROOM_STATUS(3909), + /** * Daily Tasks (Collection availability) */ @@ -546,6 +567,7 @@ public enum Varbits * 1 = sell */ GE_OFFER_CREATION_TYPE(4397), + GE_OFFER_PRICE_PER_ITEM(4398), /** * The active tab within the quest interface @@ -560,7 +582,7 @@ public enum Varbits EXPLORER_RING_ALCHS(4554), EXPLORER_RING_RUNENERGY(4553), - + WINTERTODT_TIMER(7980), /** * League relics @@ -596,291 +618,10 @@ public enum Varbits * * @see The OSRS Wiki's Minimap page */ - PVP_SPEC_ORB(8121), - - //OPENOSRS - - /** - * Grand Exchange - */ - GRAND_EXCHANGE_PRICE_PER_ITEM(4398), - - /* - * Kharedst's Memoirs Teleport Item - */ - KHAREDSTS_MEMOIRS_CHARGES(6035), - - LMS_POISON_PROGRESS(5317), - - /** - * The y coordinate of the final safespace (world coord) - */ - LMS_SAFE_Y(5320), - - /** - * 1 is true, 0 is false. - */ - GAUNTLET_FINAL_ROOM_ENTERED(9177), - - /** - * 1 is true, 0 is false. - */ - GAUNTLET_ENTERED(9178), - - WITHDRAW_X_AMOUNT(3960), - - IN_PVP_AREA(8121), - - /** - * Value of hotkey varbits can be 0-13 - * 0 corresponds to no hotkey set - * 1-12 correspond to F1-F12 respectively - * 13 corresponds to escape - */ - COMBAT_TAB_HOTKEY(4675), - STATS_TAB_HOTKEY(4676), - QUESTS_TAB_HOTKEY(4677), - INVENTORY_TAB_HOTKEY(4678), - EQUIPMENT_TAB_HOTKEY(4679), - PRAYER_TAB_HOTKEY(4680), - SPELLBOOK_TAB_HOTKEY(4682), - FRIENDS_TAB_HOTKEY(4684), - ACCOUNT_MANAGEMENT_TAB_HOTKEY(6517), - LOGOUT_TAB_HOTKEY(4689), - OPTIONS_TAB_HOTKEY(4686), - EMOTES_TAB_HOTKEY(4687), - CLAN_TAB_HOTKEY(4683), - MUSIC_TAB_HOTKEY(4688), - - /** - * Chat Notifications settings - *
- * LOOT_DROP_NOTIFICATIONS: 1 is true, 0 is false - * LOOT_DROP_NOTIFICATIONS_VALUE: gp value - * UNTRADEABLE_LOOT_NOTIFICATIONS: 1 is true, 0 is false - * BOSS_KILL_COUNT_UPDATES: 1 is filtered, 0 is unfiltered - * DROP_ITEM_WARNINGS: 1 is true, 0 is false - * DROP_ITEM_WARNINGS_VALUE: gp value - */ - LOOT_DROP_NOTIFICATIONS(5399), - LOOT_DROP_NOTIFICATIONS_VALUE(5400), - UNTRADEABLE_LOOT_NOTIFICATIONS(5402), - BOSS_KILL_COUNT_UPDATES(4930), - DROP_ITEM_WARNINGS(5411), - DROP_ITEM_WARNINGS_VALUE(5412), - /** - * Temple Trekking - */ - TREK_POINTS(1955), - TREK_STARTED(1956), - TREK_EVENT(1958), - TREK_STATUS(6719), - BLOAT_ENTERED_ROOM(6447), - - /** - * f2p Quest varbits, these don't hold the completion value. - */ - QUEST_DEMON_SLAYER(2561), - QUEST_GOBLIN_DIPLOMACY(2378), - QUEST_MISTHALIN_MYSTERY(3468), - QUEST_THE_CORSAIR_CURSE(6071), - QUEST_X_MARKS_THE_SPOT(8063), - QUEST_ERNEST_LEVER_A(1788), - QUEST_ERNEST_LEVER_B(1789), - QUEST_ERNEST_LEVER_C(1790), - QUEST_ERNEST_LEVER_D(1791), - QUEST_ERNEST_LEVER_E(1792), - QUEST_ERNEST_LEVER_F(1793), - - /** - * member Quest varbits, these don't hold the completion value. - */ - QUEST_ANIMAL_MAGNETISM(3185), - QUEST_BETWEEN_A_ROCK(299), - QUEST_CONTACT(3274), - QUEST_ZOGRE_FLESH_EATERS(487), - QUEST_DARKNESS_OF_HALLOWVALE(2573), - QUEST_DEATH_TO_THE_DORGESHUUN(2258), - QUEST_DESERT_TREASURE(358), - QUEST_DEVIOUS_MINDS(1465), - QUEST_EAGLES_PEAK(2780), - QUEST_ELEMENTAL_WORKSHOP_II(2639), - QUEST_ENAKHRAS_LAMENT(1560), - QUEST_ENLIGHTENED_JOURNEY(2866), - QUEST_THE_EYES_OF_GLOUPHRIE(2497), - QUEST_FAIRYTALE_I_GROWING_PAINS(1803), - QUEST_FAIRYTALE_II_CURE_A_QUEEN(2326), - QUEST_THE_FEUD(334), // 14 = able to pickpocket - QUEST_FORGETTABLE_TALE(822), - QUEST_GARDEN_OF_TRANQUILLITY(961), - QUEST_GHOSTS_AHOY(217), - QUEST_THE_GIANT_DWARF(571), - QUEST_THE_GOLEM(346), - QUEST_HORROR_FROM_THE_DEEP(34), - QUEST_ICTHLARINS_LITTLE_HELPER(418), - QUEST_IN_AID_OF_THE_MYREQUE(1990), - QUEST_THE_LOST_TRIBE(532), - QUEST_LUNAR_DIPLOMACY(2448), - QUEST_MAKING_HISTORY(1383), - QUEST_MOUNTAIN_DAUGHTER(260), - QUEST_MOURNINGS_END_PART_II(1103), - QUEST_MY_ARMS_BIG_ADVENTURE(2790), - QUEST_RATCATCHERS(1404), - QUEST_RECIPE_FOR_DISASTER(1850), - QUEST_RECRUITMENT_DRIVE(657), - QUEST_ROYAL_TROUBLE(2140), - QUEST_THE_SLUG_MENACE(2610), - QUEST_SHADOW_OF_THE_STORM(1372), - QUEST_A_SOULS_BANE(2011), - QUEST_SPIRITS_OF_THE_ELID(1444), - QUEST_SWAN_SONG(2098), - QUEST_A_TAIL_OF_TWO_CATS(1028), - QUEST_TEARS_OF_GUTHIX(451), - QUEST_WANTED(1051), - QUEST_COLD_WAR(3293), - QUEST_THE_FREMENNIK_ISLES(3311), - QUEST_TOWER_OF_LIFE(3337), - QUEST_WHAT_LIES_BELOW(3523), - QUEST_OLAFS_QUEST(3534), - QUEST_ANOTHER_SLICE_OF_HAM(3550), - QUEST_DREAM_MENTOR(3618), - QUEST_GRIM_TALES(2783), - QUEST_KINGS_RANSOM(3888), - QUEST_MONKEY_MADNESS_II(5027), - QUEST_CLIENT_OF_KOUREND(5619), - QUEST_BONE_VOYAGE(5795), - QUEST_THE_QUEEN_OF_THIEVES(6037), - QUEST_THE_DEPTHS_OF_DESPAIR(6027), - QUEST_DRAGON_SLAYER_II(6104), - QUEST_TALE_OF_THE_RIGHTEOUS(6358), - QUEST_A_TASTE_OF_HOPE(6396), - QUEST_MAKING_FRIENDS_WITH_MY_ARM(6528), - QUEST_THE_ASCENT_OF_ARCEUUS(7856), - QUEST_THE_FORSAKEN_TOWER(7796), - //TODO - QUEST_SONG_OF_THE_ELVES(7796), - - /** - * mini-quest varbits, these don't hold the completion value. - */ - QUEST_ARCHITECTURAL_ALLIANCE(4982), - QUEST_BEAR_YOUR_SOUL(5078), - QUEST_CURSE_OF_THE_EMPTY_LORD(821), - QUEST_ENCHANTED_KEY(1391), - QUEST_THE_GENERALS_SHADOW(3330), - QUEST_SKIPPY_AND_THE_MOGRES(1344), - QUEST_LAIR_OF_TARN_RAZORLOR(3290), - QUEST_FAMILY_PEST(5347), - QUEST_THE_MAGE_ARENA_II(6067), - //TODO - QUEST_IN_SEARCH_OF_KNOWLEDGE(6067), - - /** - * Spellbook filtering (1 = unfiltered, 0 = filtered) - */ - FILTER_SPELLBOOK(6718), - - /** - * POH Building mode (1 = yes, 0 = no) - */ - BUILDING_MODE(2176), - - WINTERTODT_TIMER(7980), - - /** - * 1 if in game, 0 if not - */ - LMS_IN_GAME(5314), - - /** - * Amount of pvp kills in current game - */ - LMS_KILLS(5315), - - /** - * The x coordinate of the final safespace (world coord) - */ - LMS_SAFE_X(5316), - - /** - * Locked Prayers - * 0-7 = Locked - * 8 = Unlocked - */ - CHIVPIETY_UNLOCKED(3909), - - /** - * Locked Prayers - * 0 = Locked - * 1 = Unlocked - */ - - RIGOUR_UNLOCKED(5451), - AUGURY_UNLOCKED(5452), - PRESERVE_UNLOCKED(5453), - - /** - * Spells being auto-casted - */ - AUTO_CAST_SPELL(276), - - /** - * Theatre of Blood 1=In Party, 2=Inside/Spectator, 3=Dead Spectating - */ - BLOAT_DOOR(6447), - - /** - * Theatre of Blood orb varbits each number stands for the player's health on a scale of 1-27 (I think), 0 hides the orb - */ - THEATRE_OF_BLOOD_ORB_1(6442), - THEATRE_OF_BLOOD_ORB_2(6443), - THEATRE_OF_BLOOD_ORB_3(6444), - THEATRE_OF_BLOOD_ORB_4(6445), - THEATRE_OF_BLOOD_ORB_5(6446), - - //NMZ - NMZ_OVERLOAD(3955), - - //Pyramid Plunder - PYRAMID_PLUNDER_SARCO_OPEN(2362), - PYRAMID_PLUNDER_CHEST_OPEN(2363), - - /** - * Varbit used for Slayer reward points - */ - SLAYER_REWARD_POINTS(4068), - - /** - * 0 = standard - * 1 = ancients - * 2 = lunars - * 3 = arrceus - **/ - SPELLBOOK(4070), - - /** - * Bank settings/flags - **/ - BANK_NOTE_FLAG(3958), - - KINGDOM_WORKERS_WOOD(81), - KINGDOM_WORKERS_HERBS(82), - KINGDOM_WORKERS_FISHING(83), - KINGDOM_WORKERS_MINING(84), - KINGDOM_WORKERS_FISH_COOKED_BUTTON(135), // 0 - Raw, 1 - Cooked - KINGDOM_WORKERS_HARDWOOD(2131), - KINGDOM_WORKERS_FARM(2132), - KINGDOM_WORKERS_HARDWOOD_BUTTON(2133), // 0 - Mahogany, 1 - Teak, 2 - Both - KINGDOM_WORKERS_HERBS_BUTTON(2134), // 0 - Herbs, 1 - Flax - - /** - * In the Wilderness - */ - IN_THE_WILDERNESS(5963), - ; + PVP_SPEC_ORB(8121); /** * The raw varbit ID. */ private final int id; -} \ No newline at end of file +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/ActorDeath.java b/runelite-api/src/main/java/net/runelite/api/events/ActorDeath.java index c976f2fe9c..b6e4588c0c 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ActorDeath.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ActorDeath.java @@ -31,7 +31,7 @@ import net.runelite.api.Actor; * An event fired when an actor dies. */ @Value -public class ActorDeath implements Event +public class ActorDeath { Actor actor; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/AnimationChanged.java b/runelite-api/src/main/java/net/runelite/api/events/AnimationChanged.java index 943337f132..5cbc26a97b 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/AnimationChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/AnimationChanged.java @@ -17,7 +17,7 @@ import lombok.Data; * @see net.runelite.api.AnimationID */ @Data -public class AnimationChanged implements Event +public class AnimationChanged { /** * The actor that has entered a new animation. diff --git a/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java b/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java index c40aab4119..4114cb22ca 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java +++ b/runelite-api/src/main/java/net/runelite/api/events/AreaSoundEffectPlayed.java @@ -29,7 +29,7 @@ import lombok.Data; import net.runelite.api.Actor; @Data -public class AreaSoundEffectPlayed implements Event +public class AreaSoundEffectPlayed { @Nullable private final Actor source; diff --git a/runelite-api/src/main/java/net/runelite/api/events/BeforeMenuRender.java b/runelite-api/src/main/java/net/runelite/api/events/BeforeMenuRender.java index 055fb37a41..5d1e10994b 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/BeforeMenuRender.java +++ b/runelite-api/src/main/java/net/runelite/api/events/BeforeMenuRender.java @@ -27,7 +27,7 @@ package net.runelite.api.events; import lombok.Data; @Data -public class BeforeMenuRender implements Event +public class BeforeMenuRender { private boolean consumed; diff --git a/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java b/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java index 70d7551ac0..bee23922dd 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java +++ b/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java @@ -27,7 +27,7 @@ package net.runelite.api.events; /** * Posted at the start of every frame */ -public class BeforeRender implements Event +public class BeforeRender { public static final BeforeRender INSTANCE = new BeforeRender(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/CannonChanged.java b/runelite-api/src/main/java/net/runelite/api/events/CannonChanged.java index 4782c8e8a1..4af81c6ccf 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/CannonChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/CannonChanged.java @@ -31,7 +31,7 @@ import lombok.Value; * an event posted when a cannonball is fired */ @Value -public class CannonChanged implements Event +public class CannonChanged { /** * The projectile id. diff --git a/runelite-api/src/main/java/net/runelite/api/events/CannonPlaced.java b/runelite-api/src/main/java/net/runelite/api/events/CannonPlaced.java index 8d0aa636c0..8b3f4402bd 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/CannonPlaced.java +++ b/runelite-api/src/main/java/net/runelite/api/events/CannonPlaced.java @@ -33,7 +33,7 @@ import net.runelite.api.coords.WorldPoint; * an event posted when a cannonball is fired */ @Value -public class CannonPlaced implements Event +public class CannonPlaced { /** * Cannon placed or picked up. diff --git a/runelite-api/src/main/java/net/runelite/api/events/CanvasSizeChanged.java b/runelite-api/src/main/java/net/runelite/api/events/CanvasSizeChanged.java index 66ad5dbcec..0efef18d0d 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/CanvasSizeChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/CanvasSizeChanged.java @@ -27,7 +27,7 @@ package net.runelite.api.events; /** * An event posted when the canvas size might have changed. */ -public class CanvasSizeChanged implements Event +public class CanvasSizeChanged { public static final CanvasSizeChanged INSTANCE = new CanvasSizeChanged(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java b/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java index 3c6230e6e9..fccf5a98d8 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ChatMessage.java @@ -41,7 +41,7 @@ import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor -public class ChatMessage implements Event +public class ChatMessage { /** * The underlying MessageNode for the message. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ClientTick.java b/runelite-api/src/main/java/net/runelite/api/events/ClientTick.java index 9ffef0d573..6874f4145c 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ClientTick.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ClientTick.java @@ -27,7 +27,7 @@ package net.runelite.api.events; /** * Posted every client tick */ -public class ClientTick implements Event +public class ClientTick { public static final ClientTick INSTANCE = new ClientTick(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/CommandExecuted.java b/runelite-api/src/main/java/net/runelite/api/events/CommandExecuted.java index a66acb9d5c..fe524a7e76 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/CommandExecuted.java +++ b/runelite-api/src/main/java/net/runelite/api/events/CommandExecuted.java @@ -42,7 +42,7 @@ import lombok.Value; * will set command to "" and arguments to ["hello", "world!"]. */ @Value -public class CommandExecuted implements Event +public class CommandExecuted { /** * The name of the command entered. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ConfigButtonClicked.java b/runelite-api/src/main/java/net/runelite/api/events/ConfigButtonClicked.java index 7aeb4ffa84..24556b1dff 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ConfigButtonClicked.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ConfigButtonClicked.java @@ -1,10 +1,9 @@ package net.runelite.api.events; import lombok.Data; -import net.runelite.api.events.Event; @Data -public class ConfigButtonClicked implements Event +public class ConfigButtonClicked { private String group, key; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectChanged.java b/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectChanged.java index a64a1c5c47..a18683d7b4 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectChanged.java @@ -33,7 +33,7 @@ import lombok.Data; * has been modified. */ @Data -public class DecorativeObjectChanged implements Event +public class DecorativeObjectChanged { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectDespawned.java index 6760c0d05d..94c06488c7 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectDespawned.java @@ -33,7 +33,7 @@ import lombok.Data; * is removed. */ @Data -public class DecorativeObjectDespawned implements Event +public class DecorativeObjectDespawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectSpawned.java index 37d0a5fab2..fe8d17862f 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/DecorativeObjectSpawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where a {@link DecorativeObject} is attached to a {@link Tile}. */ @Data -public class DecorativeObjectSpawned implements Event +public class DecorativeObjectSpawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/DialogProcessed.java b/runelite-api/src/main/java/net/runelite/api/events/DialogProcessed.java index e86b64af01..c94d82b194 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/DialogProcessed.java +++ b/runelite-api/src/main/java/net/runelite/api/events/DialogProcessed.java @@ -4,7 +4,7 @@ import lombok.Value; import net.runelite.api.DialogOption; @Value -public class DialogProcessed implements Event +public class DialogProcessed { DialogOption dialogOption; } \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/events/DraggingWidgetChanged.java b/runelite-api/src/main/java/net/runelite/api/events/DraggingWidgetChanged.java index 0f9bf09691..8aef02d570 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/DraggingWidgetChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/DraggingWidgetChanged.java @@ -31,7 +31,7 @@ import lombok.Data; * the cursor. */ @Data -public class DraggingWidgetChanged implements Event +public class DraggingWidgetChanged { /** * Whether a widget is currently being dragged. diff --git a/runelite-api/src/main/java/net/runelite/api/events/DynamicObjectAnimationChanged.java b/runelite-api/src/main/java/net/runelite/api/events/DynamicObjectAnimationChanged.java index 382cb706fb..1e4571e4fe 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/DynamicObjectAnimationChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/DynamicObjectAnimationChanged.java @@ -3,7 +3,7 @@ package net.runelite.api.events; import lombok.Data; @Data -public class DynamicObjectAnimationChanged implements Event +public class DynamicObjectAnimationChanged { /** * The object that has entered a new animation. diff --git a/runelite-api/src/main/java/net/runelite/api/events/Event.java b/runelite-api/src/main/java/net/runelite/api/events/Event.java deleted file mode 100644 index b031dce09e..0000000000 --- a/runelite-api/src/main/java/net/runelite/api/events/Event.java +++ /dev/null @@ -1,3 +0,0 @@ -package net.runelite.api.events; - -public interface Event {} diff --git a/runelite-api/src/main/java/net/runelite/api/events/FakeXpDrop.java b/runelite-api/src/main/java/net/runelite/api/events/FakeXpDrop.java index 0aac13728f..07a98b0eb4 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FakeXpDrop.java +++ b/runelite-api/src/main/java/net/runelite/api/events/FakeXpDrop.java @@ -28,7 +28,7 @@ import lombok.Value; import net.runelite.api.Skill; @Value -public class FakeXpDrop implements Event +public class FakeXpDrop { Skill skill; int xp; diff --git a/runelite-api/src/main/java/net/runelite/api/events/FocusChanged.java b/runelite-api/src/main/java/net/runelite/api/events/FocusChanged.java index 3f190fd331..fd9fcb6466 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FocusChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/FocusChanged.java @@ -37,7 +37,7 @@ import lombok.Data; * */ @Data -public class FocusChanged implements Event +public class FocusChanged { /** * The new focus state. diff --git a/runelite-api/src/main/java/net/runelite/api/events/FriendAdded.java b/runelite-api/src/main/java/net/runelite/api/events/FriendAdded.java index c7c2160759..37be825080 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FriendAdded.java +++ b/runelite-api/src/main/java/net/runelite/api/events/FriendAdded.java @@ -6,7 +6,7 @@ import lombok.Value; * An event where a request to add a friend is sent to the server. */ @Value -public class FriendAdded implements Event +public class FriendAdded { /** * The name of the added friend. diff --git a/runelite-api/src/main/java/net/runelite/api/events/FriendsChatChanged.java b/runelite-api/src/main/java/net/runelite/api/events/FriendsChatChanged.java index 12c203769a..271eb83e49 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FriendsChatChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/FriendsChatChanged.java @@ -30,7 +30,7 @@ import lombok.Value; * An event where the client has joined or left a friends chat. */ @Value -public class FriendsChatChanged implements Event +public class FriendsChatChanged { /** * Whether or not the client is now in a friends chat. diff --git a/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberJoined.java b/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberJoined.java index 7adbf159fe..e5a0cdb8a0 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberJoined.java +++ b/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberJoined.java @@ -28,7 +28,7 @@ import lombok.Value; import net.runelite.api.FriendsChatMember; @Value -public class FriendsChatMemberJoined implements Event +public class FriendsChatMemberJoined { /** * The member that joined diff --git a/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberLeft.java b/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberLeft.java index 49530eb870..0970b762c8 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberLeft.java +++ b/runelite-api/src/main/java/net/runelite/api/events/FriendsChatMemberLeft.java @@ -28,7 +28,7 @@ import lombok.Value; import net.runelite.api.FriendsChatMember; @Value -public class FriendsChatMemberLeft implements Event +public class FriendsChatMemberLeft { /** * The member that left diff --git a/runelite-api/src/main/java/net/runelite/api/events/GameObjectChanged.java b/runelite-api/src/main/java/net/runelite/api/events/GameObjectChanged.java index dee1f261ea..117ad81f60 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GameObjectChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GameObjectChanged.java @@ -32,7 +32,7 @@ import net.runelite.api.Tile; * An event where a {@link GameObject} on a {@link Tile} has been replaced. */ @Data -public class GameObjectChanged implements Event +public class GameObjectChanged { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GameObjectDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/GameObjectDespawned.java index ed7e11ae2f..5d2e9fad1a 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GameObjectDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GameObjectDespawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where a {@link GameObject} on a {@link Tile} is removed. */ @Data -public class GameObjectDespawned implements Event +public class GameObjectDespawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GameObjectSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/GameObjectSpawned.java index df2b4fcca4..dc9fdbdd24 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GameObjectSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GameObjectSpawned.java @@ -32,7 +32,7 @@ import net.runelite.api.Tile; * An event where a {@link GameObject} is added to a {@link Tile}. */ @Data -public class GameObjectSpawned implements Event +public class GameObjectSpawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GameStateChanged.java b/runelite-api/src/main/java/net/runelite/api/events/GameStateChanged.java index eb9612211c..2a0b61fc40 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GameStateChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GameStateChanged.java @@ -31,7 +31,7 @@ import lombok.Data; * An event where the clients game state has changed. */ @Data -public class GameStateChanged implements Event +public class GameStateChanged { /** * The new game state. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GameTick.java b/runelite-api/src/main/java/net/runelite/api/events/GameTick.java index ce3fbaf25f..499b2ae83c 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GameTick.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GameTick.java @@ -41,7 +41,7 @@ package net.runelite.api.events; * Note that occurrences that take place purely on the client, such as right * click menus, are independent of the game tick. */ -public class GameTick implements Event +public class GameTick { public static final GameTick INSTANCE = new GameTick(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java b/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java index 52ae0e6d92..88e4aa1fac 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeOfferChanged.java @@ -41,7 +41,7 @@ import lombok.Data; * can change into. */ @Data -public class GrandExchangeOfferChanged implements Event +public class GrandExchangeOfferChanged { /** * The offer that has been modified. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeSearched.java b/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeSearched.java index b357240149..aa96680d1c 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeSearched.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GrandExchangeSearched.java @@ -31,7 +31,7 @@ import lombok.Data; * An event where the Grand Exchange has been searched. */ @Data -public class GrandExchangeSearched implements Event +public class GrandExchangeSearched { /** * Whether or not the event has been consumed by a subscriber. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GraphicChanged.java b/runelite-api/src/main/java/net/runelite/api/events/GraphicChanged.java index ee7b781c82..775487c6d0 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GraphicChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GraphicChanged.java @@ -19,7 +19,7 @@ import net.runelite.api.Actor; * @see net.runelite.api.GraphicID */ @Data -public class GraphicChanged implements Event +public class GraphicChanged { /** * The actor that has had their graphic changed. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GraphicsObjectCreated.java b/runelite-api/src/main/java/net/runelite/api/events/GraphicsObjectCreated.java index b074e63d40..280cc77624 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GraphicsObjectCreated.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GraphicsObjectCreated.java @@ -31,7 +31,7 @@ import lombok.Value; * An event where a new {@link GraphicsObject} has been created. */ @Value -public class GraphicsObjectCreated implements Event +public class GraphicsObjectCreated { /** * The newly created graphics object. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GroundObjectChanged.java b/runelite-api/src/main/java/net/runelite/api/events/GroundObjectChanged.java index c58b46575d..8fa97ae503 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GroundObjectChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GroundObjectChanged.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where the {@link GroundObject} on a {@link Tile} has been changed. */ @Data -public class GroundObjectChanged implements Event +public class GroundObjectChanged { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GroundObjectDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/GroundObjectDespawned.java index 6e793f025e..54bb6dfbe0 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GroundObjectDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GroundObjectDespawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where a {@link GroundObject} on a {@link Tile} has been removed. */ @Data -public class GroundObjectDespawned implements Event +public class GroundObjectDespawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/GroundObjectSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/GroundObjectSpawned.java index 2ccce09136..af1f15ab6f 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/GroundObjectSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/GroundObjectSpawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where a {@link GroundObject} is added to a {@link Tile}. */ @Data -public class GroundObjectSpawned implements Event +public class GroundObjectSpawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java b/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java index d8b22ee2bc..f96a0f08d7 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java +++ b/runelite-api/src/main/java/net/runelite/api/events/HitsplatApplied.java @@ -36,7 +36,7 @@ import lombok.Data; * visible hitsplats. */ @Data -public class HitsplatApplied implements Event +public class HitsplatApplied { /** * The actor the hitsplat was applied to. diff --git a/runelite-api/src/main/java/net/runelite/api/events/InteractChanged.java b/runelite-api/src/main/java/net/runelite/api/events/InteractChanged.java index 1e08180b88..fcd2e54a43 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/InteractChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/InteractChanged.java @@ -28,7 +28,7 @@ import net.runelite.api.Actor; import lombok.Data; @Data -public class InteractChanged implements Event +public class InteractChanged { private Actor actor; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/InteractingChanged.java b/runelite-api/src/main/java/net/runelite/api/events/InteractingChanged.java index a4f22a3583..ef1da8baca 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/InteractingChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/InteractingChanged.java @@ -7,7 +7,7 @@ import lombok.Value; * An event called when the actor an actor is interacting with changes */ @Value -public class InteractingChanged implements Event +public class InteractingChanged { Actor source; diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java index 82dc290dd0..5a5ddef151 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemContainerChanged.java @@ -39,7 +39,7 @@ import lombok.Value; * */ @Value -public class ItemContainerChanged implements Event +public class ItemContainerChanged { /** * The modified container's ID. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java index 870a39f29f..22082bfedc 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java @@ -34,7 +34,7 @@ import lombok.Value; * all item piles are implicitly despawned, and despawn events will not be sent. */ @Value -public class ItemDespawned implements Event +public class ItemDespawned { Tile tile; TileItem item; diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java index 0e596b8397..7971da5f12 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java @@ -33,7 +33,7 @@ import lombok.Value; * Called when the quantity of an item pile changes. */ @Value -public class ItemQuantityChanged implements Event +public class ItemQuantityChanged { TileItem item; Tile tile; diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java index 9daa54077d..af3c034abc 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java @@ -34,7 +34,7 @@ import lombok.Value; * all item piles are implicitly reset and a new spawn event will be sent. */ @Value -public class ItemSpawned implements Event +public class ItemSpawned { Tile tile; TileItem item; diff --git a/runelite-api/src/main/java/net/runelite/api/events/Menu.java b/runelite-api/src/main/java/net/runelite/api/events/Menu.java index d344397221..d7a5af40cb 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/Menu.java +++ b/runelite-api/src/main/java/net/runelite/api/events/Menu.java @@ -4,7 +4,7 @@ package net.runelite.api.events; * Gets sent before menu handling code is ran, once per client tick. * Can be consumed, skipping this method this tick. */ -public class Menu implements Event +public class Menu { public static final Menu MENU = new Menu(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/MenuEntryAdded.java b/runelite-api/src/main/java/net/runelite/api/events/MenuEntryAdded.java index 7b9c0d3fb3..727476a035 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/MenuEntryAdded.java +++ b/runelite-api/src/main/java/net/runelite/api/events/MenuEntryAdded.java @@ -30,7 +30,7 @@ import net.runelite.api.MenuEntry; /** * An event when a new entry is added to a right-click menu. */ -public class MenuEntryAdded extends MenuEntry implements Event +public class MenuEntryAdded extends MenuEntry { public MenuEntryAdded(String option, String target, int identifier, int opcode, int param0, int param1, boolean forceLeftClick) { diff --git a/runelite-api/src/main/java/net/runelite/api/events/MenuOpened.java b/runelite-api/src/main/java/net/runelite/api/events/MenuOpened.java index ee31b0ad04..6d34000cea 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/MenuOpened.java +++ b/runelite-api/src/main/java/net/runelite/api/events/MenuOpened.java @@ -34,7 +34,7 @@ import lombok.Data; * An event where a menu has been opened. */ @Data -public class MenuOpened implements Event, Iterable +public class MenuOpened implements Iterable { /** * This should be set to true if anything about the menu diff --git a/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java b/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java index 0ed4fb0df1..4ff7b8138e 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java +++ b/runelite-api/src/main/java/net/runelite/api/events/MenuOptionClicked.java @@ -24,9 +24,8 @@ */ package net.runelite.api.events; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; +import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; /** @@ -40,32 +39,44 @@ import net.runelite.api.MenuEntry; * By default, when there is no action performed when left-clicking, * it seems that this event still triggers with the "Cancel" action. */ -@Getter -public class MenuOptionClicked extends MenuEntry implements Event +@Data +public class MenuOptionClicked { - public MenuOptionClicked(String option, String target, int identifier, int opcode, int param0, int param1, boolean forceLeftClick) - { - super(option, target, identifier, opcode, param0, param1, forceLeftClick); - authentic = true; - } - - public MenuOptionClicked(String option, String target, int identifier, int opcode, int param0, int param1, boolean forceLeftClick, boolean authentic, int mouseButton) - { - super(option, target, identifier, opcode, param0, param1, forceLeftClick); - this.authentic = authentic; - this.mouseButton = mouseButton; - } - + /** + * The action parameter used in the click. + */ + private int actionParam; + /** + * The option text added to the menu. + */ + private String menuOption; + /** + * The target of the action. + */ + private String menuTarget; + /** + * The action performed. + */ + private MenuAction menuAction; + /** + * The ID of the object, actor, or item that the interaction targets. + */ + private int id; + /** + * The ID of the widget where the menu was clicked. + * + * @see net.runelite.api.widgets.WidgetID + */ + private int widgetId; + /** + * The selected item index at the time of the option click. + */ + private int selectedItemIndex; /** * Whether or not the event has been consumed by a subscriber. */ private boolean consumed; - /** - * The mouse button will be 1 if a non draggable widget was clicked, - */ - private int mouseButton; - /** * Marks the event as having been consumed. *

@@ -78,35 +89,13 @@ public class MenuOptionClicked extends MenuEntry implements Event this.consumed = true; } - /** - * Whether or not the event is authentic. - */ - @Setter(AccessLevel.NONE) - private final boolean authentic; - - public void setMenuEntry(MenuEntry e) + public void setMenuEntry(MenuEntry entry) { - setOption(e.getOption()); - setTarget(e.getTarget()); - setIdentifier(e.getIdentifier()); - setOpcode(e.getOpcode()); - setActionParam(e.getActionParam()); - setActionParam1(e.getActionParam1()); - setForceLeftClick(e.isForceLeftClick()); - } - - public int getWidgetId() - { - return getActionParam1(); - } - - public String getMenuTarget() - { - return getTarget(); - } - - public String getMenuOption() - { - return getOption(); + this.setMenuOption(entry.getOption()); + this.setMenuTarget(entry.getTarget()); + this.setId(entry.getId()); + this.setMenuAction(MenuAction.of(entry.getOpcode())); + this.setActionParam(entry.getActionParam()); + this.setWidgetId(entry.getActionParam1()); } } diff --git a/runelite-api/src/main/java/net/runelite/api/events/MenuShouldLeftClick.java b/runelite-api/src/main/java/net/runelite/api/events/MenuShouldLeftClick.java index 9652721791..c36372ff2c 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/MenuShouldLeftClick.java +++ b/runelite-api/src/main/java/net/runelite/api/events/MenuShouldLeftClick.java @@ -31,7 +31,7 @@ import lombok.Data; * opened on left click. */ @Data -public class MenuShouldLeftClick implements Event +public class MenuShouldLeftClick { /** * If set to true, the menu will open on left click. diff --git a/runelite-api/src/main/java/net/runelite/api/events/NameableNameChanged.java b/runelite-api/src/main/java/net/runelite/api/events/NameableNameChanged.java index fcf0556e71..18b106ec9f 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/NameableNameChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/NameableNameChanged.java @@ -31,7 +31,7 @@ import lombok.Value; * An event where a {@link Nameable} has had their name changed. */ @Value -public class NameableNameChanged implements Event +public class NameableNameChanged { /** * The nameable that changed names. diff --git a/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java b/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java index 20f831e328..7539821167 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java @@ -31,7 +31,7 @@ import net.runelite.api.NPCComposition; * An event where an action of an {@link NPCComposition} has changed. */ @Data -public class NpcActionChanged implements Event +public class NpcActionChanged { /** * The NPC composition that has been changed. diff --git a/runelite-api/src/main/java/net/runelite/api/events/NpcChanged.java b/runelite-api/src/main/java/net/runelite/api/events/NpcChanged.java index 606ebcbb7e..f01da68ae2 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/NpcChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/NpcChanged.java @@ -32,7 +32,7 @@ import net.runelite.api.NPCComposition; * Fires after the composition of an {@link NPC} changes. */ @Value -public class NpcChanged implements Event +public class NpcChanged { /** * The NPC of which the composition changed. diff --git a/runelite-api/src/main/java/net/runelite/api/events/NpcDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/NpcDespawned.java index 6b7bd48b29..9d2fed4200 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/NpcDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/NpcDespawned.java @@ -32,7 +32,7 @@ import lombok.Value; * An event where an {@link NPC} has despawned. */ @Value -public class NpcDespawned implements Event +public class NpcDespawned { /** * The despawned NPC. diff --git a/runelite-api/src/main/java/net/runelite/api/events/NpcSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/NpcSpawned.java index 0bb3c3f3cb..aab89fffb8 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/NpcSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/NpcSpawned.java @@ -32,7 +32,7 @@ import lombok.Value; * An event where an {@link NPC} has spawned. */ @Value -public class NpcSpawned implements Event +public class NpcSpawned { /** * The spawned NPC. diff --git a/runelite-api/src/main/java/net/runelite/api/events/OverheadPrayerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/OverheadPrayerChanged.java new file mode 100644 index 0000000000..46bf0f4a5c --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/OverheadPrayerChanged.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, ThatGamerBlue + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.api.events; + +import lombok.Data; +import net.runelite.api.HeadIcon; +import net.runelite.api.Player; + +@Data +public class OverheadPrayerChanged +{ + private final Player player; + + private final HeadIcon oldHeadIcon; + + private final HeadIcon newHeadIcon; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/OverheadTextChanged.java b/runelite-api/src/main/java/net/runelite/api/events/OverheadTextChanged.java index 78df69b9de..9e77178776 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/OverheadTextChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/OverheadTextChanged.java @@ -4,7 +4,7 @@ import net.runelite.api.Actor; import lombok.Value; @Value -public class OverheadTextChanged implements Event +public class OverheadTextChanged { Actor actor; diff --git a/runelite-api/src/main/java/net/runelite/api/events/PlayerDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/PlayerDespawned.java index 0970c08e9b..ed5f2a9057 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/PlayerDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/PlayerDespawned.java @@ -34,7 +34,7 @@ import lombok.Value; * Note: This event does not get called for the local player. */ @Value -public class PlayerDespawned implements Event +public class PlayerDespawned { /** * The despawned player. diff --git a/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionClicked.java b/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionClicked.java index 14a85b6025..dcfb0421ca 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionClicked.java +++ b/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionClicked.java @@ -31,7 +31,7 @@ import lombok.Data; * been clicked (ie. HiScore Lookup). */ @Data -public class PlayerMenuOptionClicked implements Event +public class PlayerMenuOptionClicked { /** * The menu option clicked. diff --git a/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionsChanged.java b/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionsChanged.java index a75af8acf3..d33aa4d06c 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionsChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/PlayerMenuOptionsChanged.java @@ -27,7 +27,7 @@ package net.runelite.api.events; import lombok.Data; @Data -public class PlayerMenuOptionsChanged implements Event +public class PlayerMenuOptionsChanged { /** * Index in playerOptions which changed. diff --git a/runelite-api/src/main/java/net/runelite/api/events/PlayerSkullChanged.java b/runelite-api/src/main/java/net/runelite/api/events/PlayerSkullChanged.java new file mode 100644 index 0000000000..931c415ed1 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/PlayerSkullChanged.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, ThatGamerBlue + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.runelite.api.events; + +import lombok.Data; +import net.runelite.api.Player; +import net.runelite.api.SkullIcon; + +@Data +public class PlayerSkullChanged +{ + private final Player player; + + private final SkullIcon oldSkullIcon; + + private final SkullIcon newSkullIcon; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/PlayerSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/PlayerSpawned.java index 9ba53bbc4f..73e1932285 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/PlayerSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/PlayerSpawned.java @@ -32,7 +32,7 @@ import lombok.Value; * An event where a {@link Player} has spawned. */ @Value -public class PlayerSpawned implements Event +public class PlayerSpawned { /** * The spawned player. diff --git a/runelite-api/src/main/java/net/runelite/api/events/PostHealthBar.java b/runelite-api/src/main/java/net/runelite/api/events/PostHealthBar.java index 1f327ec13a..c8896ff945 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/PostHealthBar.java +++ b/runelite-api/src/main/java/net/runelite/api/events/PostHealthBar.java @@ -28,7 +28,7 @@ import net.runelite.api.HealthBar; import lombok.Data; @Data -public class PostHealthBar implements Event +public class PostHealthBar { private HealthBar healthBar; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java b/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java index 14ec4d197c..1e9000cfd1 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java +++ b/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java @@ -32,7 +32,7 @@ import net.runelite.api.ItemComposition; * its data is initialized. */ @Data -public class PostItemComposition implements Event +public class PostItemComposition { /** * The newly created item. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ProjectileMoved.java b/runelite-api/src/main/java/net/runelite/api/events/ProjectileMoved.java index 82590778be..70d73d7026 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ProjectileMoved.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ProjectileMoved.java @@ -35,7 +35,7 @@ import lombok.Data; * once (ie. AoE from Lizardman Shaman). */ @Data -public class ProjectileMoved implements Event +public class ProjectileMoved { /** * The projectile being moved. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ProjectileSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/ProjectileSpawned.java index 7c11ea215f..7b7bda43ad 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ProjectileSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ProjectileSpawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event called whenever a {@link Projectile} has spawned. */ @Data -public class ProjectileSpawned implements Event +public class ProjectileSpawned { /** * The spawned projectile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ResizeableChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ResizeableChanged.java index ecc5c0d380..146f92ac38 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ResizeableChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ResizeableChanged.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where the game has changed from fixed to resizable mode or vice versa. */ @Data -public class ResizeableChanged implements Event +public class ResizeableChanged { /** * Whether the game is in resizable mode. diff --git a/runelite-api/src/main/java/net/runelite/api/events/ScriptCallbackEvent.java b/runelite-api/src/main/java/net/runelite/api/events/ScriptCallbackEvent.java index 6461cad9e7..716f7bbe90 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ScriptCallbackEvent.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ScriptCallbackEvent.java @@ -31,7 +31,7 @@ import lombok.Data; * A callback from a runelite_callback opcode in a cs2 */ @Data -public class ScriptCallbackEvent implements Event +public class ScriptCallbackEvent { /** * The script that is currently being executed diff --git a/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java b/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java index 75677640c1..55338bd87a 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java @@ -30,7 +30,7 @@ import lombok.Value; * An event that is fired after the designated script is ran */ @Value -public class ScriptPostFired implements Event +public class ScriptPostFired { /** * The script id of the invoked script diff --git a/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java b/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java index 55c58ac4a3..f8e9549281 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java +++ b/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java @@ -31,7 +31,7 @@ import net.runelite.api.ScriptEvent; * An event that is fired before the designated script is ran */ @Value -public class ScriptPreFired implements Event +public class ScriptPreFired { /** * The script id of the invoked script diff --git a/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java b/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java index b2edb71708..7c111d1d8d 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java +++ b/runelite-api/src/main/java/net/runelite/api/events/SoundEffectPlayed.java @@ -29,7 +29,7 @@ import lombok.Data; import net.runelite.api.Actor; @Data -public class SoundEffectPlayed implements Event +public class SoundEffectPlayed { @Nullable private final Actor source; diff --git a/runelite-api/src/main/java/net/runelite/api/events/StatChanged.java b/runelite-api/src/main/java/net/runelite/api/events/StatChanged.java index b329e9dbbc..809636f446 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/StatChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/StatChanged.java @@ -31,7 +31,7 @@ import net.runelite.api.Skill; * An event where the experience, level, or boosted level of a {@link Skill} has been modified. */ @Value -public class StatChanged implements Event +public class StatChanged { Skill skill; int xp; diff --git a/runelite-api/src/main/java/net/runelite/api/events/UsernameChanged.java b/runelite-api/src/main/java/net/runelite/api/events/UsernameChanged.java index 802df61b63..3dea001150 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/UsernameChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/UsernameChanged.java @@ -30,7 +30,7 @@ package net.runelite.api.events; * This event triggers for every character change to the username * in the login screen. */ -public class UsernameChanged implements Event +public class UsernameChanged { public static final UsernameChanged INSTANCE = new UsernameChanged(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/VarClientIntChanged.java b/runelite-api/src/main/java/net/runelite/api/events/VarClientIntChanged.java index adc2be50e1..521e8a62ae 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/VarClientIntChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/VarClientIntChanged.java @@ -32,7 +32,7 @@ import lombok.Value; * @see net.runelite.api.VarClientInt */ @Value -public class VarClientIntChanged implements Event +public class VarClientIntChanged { int index; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/VarClientStrChanged.java b/runelite-api/src/main/java/net/runelite/api/events/VarClientStrChanged.java index c63e088470..2fff4da47e 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/VarClientStrChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/VarClientStrChanged.java @@ -32,7 +32,7 @@ import lombok.Value; * @see net.runelite.api.VarClientStr */ @Value -public class VarClientStrChanged implements Event +public class VarClientStrChanged { int index; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/VarbitChanged.java b/runelite-api/src/main/java/net/runelite/api/events/VarbitChanged.java index 945222c072..fbac6d5e01 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/VarbitChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/VarbitChanged.java @@ -37,7 +37,7 @@ import lombok.Data; * if the VarPlayer has special engine behavior assigned to it. */ @Data -public class VarbitChanged implements Event +public class VarbitChanged { /** * Index in the varp array that was changed. diff --git a/runelite-api/src/main/java/net/runelite/api/events/VolumeChanged.java b/runelite-api/src/main/java/net/runelite/api/events/VolumeChanged.java index 38701712c3..30e87b9c79 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/VolumeChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/VolumeChanged.java @@ -28,7 +28,7 @@ package net.runelite.api.events; import lombok.Value; @Value -public class VolumeChanged implements Event +public class VolumeChanged { public enum Type { diff --git a/runelite-api/src/main/java/net/runelite/api/events/WallObjectChanged.java b/runelite-api/src/main/java/net/runelite/api/events/WallObjectChanged.java index 6cf5fd8f25..bea7686602 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WallObjectChanged.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WallObjectChanged.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where the {@link WallObject} of a {@link Tile} has been changed. */ @Data -public class WallObjectChanged implements Event +public class WallObjectChanged { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/WallObjectDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/WallObjectDespawned.java index 8a510a7bae..6ac609a46f 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WallObjectDespawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WallObjectDespawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where a {@link WallObject} on a {@link Tile} has been removed. */ @Data -public class WallObjectDespawned implements Event +public class WallObjectDespawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/WallObjectSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/WallObjectSpawned.java index a1da8542a4..dc22e07f7d 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WallObjectSpawned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WallObjectSpawned.java @@ -32,7 +32,7 @@ import lombok.Data; * An event where a {@link WallObject} is added to a {@link Tile}. */ @Data -public class WallObjectSpawned implements Event +public class WallObjectSpawned { /** * The affected tile. diff --git a/runelite-api/src/main/java/net/runelite/api/events/WidgetHiddenChanged.java b/runelite-api/src/main/java/net/runelite/api/events/WidgetHiddenChanged.java new file mode 100644 index 0000000000..705202468e --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/WidgetHiddenChanged.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +import net.runelite.api.widgets.Widget; +import lombok.Data; + +/** + * An event where the hidden state of a {@link Widget} has been modified. + */ +@Data +public class WidgetHiddenChanged +{ + /** + * The affected widget. + */ + private Widget widget; + /** + * The new hidden state of the widget. + */ + private boolean hidden; +} \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/events/WidgetLoaded.java b/runelite-api/src/main/java/net/runelite/api/events/WidgetLoaded.java index 5f337603d1..c71bc41634 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WidgetLoaded.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WidgetLoaded.java @@ -30,7 +30,7 @@ import lombok.Data; * An event where a {@link net.runelite.api.widgets.Widget} has been loaded. */ @Data -public class WidgetLoaded implements Event +public class WidgetLoaded { /** * The group ID of the loaded widget. diff --git a/runelite-api/src/main/java/net/runelite/api/events/WidgetMenuOptionClicked.java b/runelite-api/src/main/java/net/runelite/api/events/WidgetMenuOptionClicked.java index 492767d832..c0e3596085 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WidgetMenuOptionClicked.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WidgetMenuOptionClicked.java @@ -31,7 +31,7 @@ import lombok.Data; * A MenuManager widget menu was clicked. This event is NOT fired for non-MenuManager menu options */ @Data -public class WidgetMenuOptionClicked implements Event +public class WidgetMenuOptionClicked { /** * The clicked menu option. diff --git a/runelite-api/src/main/java/net/runelite/api/events/WidgetPositioned.java b/runelite-api/src/main/java/net/runelite/api/events/WidgetPositioned.java index 4835db0aa4..6f58e9fe07 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WidgetPositioned.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WidgetPositioned.java @@ -28,7 +28,7 @@ package net.runelite.api.events; * An event where the position of a {@link net.runelite.api.widgets.Widget} * relative to its parent has changed. */ -public class WidgetPositioned implements Event +public class WidgetPositioned { public static final WidgetPositioned INSTANCE = new WidgetPositioned(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/WidgetPressed.java b/runelite-api/src/main/java/net/runelite/api/events/WidgetPressed.java index b7fd18af4c..cabceb377b 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WidgetPressed.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WidgetPressed.java @@ -31,7 +31,7 @@ import lombok.Data; * An event where a draggable widget has been pressed. */ @Data -public class WidgetPressed implements Event +public class WidgetPressed { public static final WidgetPressed INSTANCE = new WidgetPressed(); diff --git a/runelite-api/src/main/java/net/runelite/api/events/WorldListLoad.java b/runelite-api/src/main/java/net/runelite/api/events/WorldListLoad.java index ab0d05e5c1..c45f4cf1ae 100644 --- a/runelite-api/src/main/java/net/runelite/api/events/WorldListLoad.java +++ b/runelite-api/src/main/java/net/runelite/api/events/WorldListLoad.java @@ -31,7 +31,7 @@ import lombok.Value; * Event when the world list is loaded for the world switcher */ @Value -public class WorldListLoad implements Event +public class WorldListLoad { World[] worlds; } diff --git a/runelite-api/src/main/java/net/runelite/api/events/player/headicon/OverheadPrayerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/player/headicon/OverheadPrayerChanged.java deleted file mode 100644 index 687a17f6dd..0000000000 --- a/runelite-api/src/main/java/net/runelite/api/events/player/headicon/OverheadPrayerChanged.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.runelite.api.events.player.headicon; - -import lombok.Data; -import net.runelite.api.HeadIcon; -import net.runelite.api.Player; -import net.runelite.api.events.Event; - -@Data -public class OverheadPrayerChanged implements Event -{ - private final Player player; - - private final HeadIcon oldHeadIcon; - - private final HeadIcon newHeadIcon; - -} diff --git a/runelite-api/src/main/java/net/runelite/api/events/player/headicon/PlayerSkullChanged.java b/runelite-api/src/main/java/net/runelite/api/events/player/headicon/PlayerSkullChanged.java deleted file mode 100644 index b66c558888..0000000000 --- a/runelite-api/src/main/java/net/runelite/api/events/player/headicon/PlayerSkullChanged.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.runelite.api.events.player.headicon; - -import lombok.Data; -import net.runelite.api.Player; -import net.runelite.api.SkullIcon; -import net.runelite.api.events.Event; - -@Data -public class PlayerSkullChanged implements Event -{ - private final Player player; - - private final SkullIcon oldSkullIcon; - - private final SkullIcon newSkullIcon; - -} diff --git a/runelite-api/src/main/java/net/runelite/api/kit/KitType.java b/runelite-api/src/main/java/net/runelite/api/kit/KitType.java index 930e91d1dc..82c6781949 100644 --- a/runelite-api/src/main/java/net/runelite/api/kit/KitType.java +++ b/runelite-api/src/main/java/net/runelite/api/kit/KitType.java @@ -26,7 +26,6 @@ package net.runelite.api.kit; import lombok.AllArgsConstructor; import lombok.Getter; -import net.runelite.api.widgets.WidgetInfo; /** * Represents an equipment slot in a players composition. @@ -40,27 +39,30 @@ import net.runelite.api.widgets.WidgetInfo; @AllArgsConstructor public enum KitType { - HEAD("Head", 0, WidgetInfo.EQUIPMENT_HELMET), - CAPE("Cape", 1, WidgetInfo.EQUIPMENT_CAPE), - AMULET("Amulet", 2, WidgetInfo.EQUIPMENT_AMULET), - WEAPON("Weapon", 3, WidgetInfo.EQUIPMENT_WEAPON), - TORSO("Torso", 4, WidgetInfo.EQUIPMENT_BODY), - SHIELD("Shield", 5, WidgetInfo.EQUIPMENT_SHIELD), - ARMS("Arms", 6, null), - LEGS("Legs", 7, WidgetInfo.EQUIPMENT_LEGS), - HAIR("Hair", 8, null), - HANDS("Hands", 9, WidgetInfo.EQUIPMENT_GLOVES), - BOOTS("Boots", 10, WidgetInfo.EQUIPMENT_BOOTS), - JAW("Jaw", 11, null), - RING("Ring", 12, WidgetInfo.EQUIPMENT_RING), - AMMUNITION("Ammo", 13, WidgetInfo.EQUIPMENT_AMMO); + HEAD("Head"), + CAPE("Cape"), + AMULET("Amulet"), + WEAPON("Weapon"), + TORSO("Torso"), + SHIELD("Shield"), + ARMS("Arms"), + LEGS("Legs"), + HAIR("Hair"), + HANDS("Hands"), + BOOTS("Boots"), + JAW("Jaw"), + RING("Ring"), + AMMUNITION("Ammo"); private final String name; /** - * Gets the raw equipment index for use in {PlayerAppearance#getEquipmentIds()}. + * Gets the raw equipment index for use in {@link net.runelite.api.PlayerComposition#getEquipmentIds()}. + * + * @return raw equipment index */ - private final int index; - - private final WidgetInfo widgetInfo; + public int getIndex() + { + return ordinal(); + } } \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java b/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java index a768df8928..0bd789fbac 100644 --- a/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java +++ b/runelite-api/src/main/java/net/runelite/api/queries/ShopItemQuery.java @@ -32,7 +32,6 @@ import java.util.stream.Collectors; import net.runelite.api.Client; import net.runelite.api.QueryResults; import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetItem; public class ShopItemQuery extends WidgetItemQuery @@ -50,7 +49,7 @@ public class ShopItemQuery extends WidgetItemQuery private Collection getShopItems(Client client) { Collection widgetItems = new ArrayList<>(); - Widget shop = client.getWidget(WidgetInfo.SHOP_ITEMS_CONTAINER); + Widget shop = client.getWidget(300, 2); if (shop != null && !shop.isHidden()) { Widget[] children = shop.getDynamicChildren(); diff --git a/runelite-client/runelite-client.gradle.kts b/runelite-client/runelite-client.gradle.kts index deccc7b0f3..cfc8667c28 100644 --- a/runelite-client/runelite-client.gradle.kts +++ b/runelite-client/runelite-client.gradle.kts @@ -83,6 +83,7 @@ dependencies { implementation(project(":http-api")) implementation(group = "net.runelite.gluegen", name = "gluegen-rt", version = "2.4.0-rc-20200429") implementation(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429") + implementation(group = "net.runelite.jocl", name = "jocl", version = "1.0") runtimeOnly(group = "org.pushing-pixels", name = "radiance-trident", version = "2.5.1") runtimeOnly(project(":runescape-api")) @@ -94,6 +95,8 @@ dependencies { runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-windows-amd64") runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-windows-i586") runtimeOnly(group = "net.runelite.jogl", name = "jogl-all", version = "2.4.0-rc-20200429", classifier = "natives-macosx-universal") + runtimeOnly(group = "net.runelite.jocl", name = "jocl", version = "1.0", classifier = "macos-x64") + runtimeOnly(group = "net.runelite.jocl", name = "jocl", version = "1.0", classifier = "macos-arm64") testAnnotationProcessor(group = "org.projectlombok", name = "lombok", version = "1.18.16") diff --git a/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java b/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java index 88af60539d..4d64898bce 100644 --- a/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java +++ b/runelite-client/src/main/java/com/openosrs/client/OpenOSRS.java @@ -1,27 +1,35 @@ package com.openosrs.client; -import com.openosrs.client.game.NPCStats; - import java.io.File; +import java.io.IOException; +import java.util.Properties; import java.util.UUID; public class OpenOSRS { public static final File OPENOSRS_DIR = new File(System.getProperty("user.home"), ".openosrs"); public static final File EXTERNALPLUGIN_DIR = new File(OPENOSRS_DIR, "plugins"); - public static final String SYSTEM_VERSION = "0.0.1"; + public static final String SYSTEM_VERSION; + public static final String SYSTEM_API_VERSION; public static String uuid = UUID.randomUUID().toString(); - public static void init() + static { + Properties properties = new Properties(); try { - NPCStats.loadStats(); + properties.load(OpenOSRS.class.getResourceAsStream("/openosrs.properties")); } - catch (Exception e) + catch (IOException e) { e.printStackTrace(); } + SYSTEM_VERSION = properties.getProperty("oprs.version", "0.0.0"); + SYSTEM_API_VERSION = properties.getProperty("oprs.api.version"); + } + + public static void preload() + { } } diff --git a/runelite-client/src/main/java/com/openosrs/client/config/OpenOSRSConfig.java b/runelite-client/src/main/java/com/openosrs/client/config/OpenOSRSConfig.java index 9c416f5708..88faf46cb5 100644 --- a/runelite-client/src/main/java/com/openosrs/client/config/OpenOSRSConfig.java +++ b/runelite-client/src/main/java/com/openosrs/client/config/OpenOSRSConfig.java @@ -35,7 +35,7 @@ import net.runelite.client.config.ConfigItem; import net.runelite.client.config.Keybind; import net.runelite.client.config.Range; import net.runelite.client.config.Units; -import com.openosrs.client.plugins.ExternalPluginManager; +import net.runelite.client.plugins.OPRSExternalPluginManager; @ConfigGroup("openosrs") public interface OpenOSRSConfig extends Config @@ -106,22 +106,11 @@ public interface OpenOSRSConfig extends Config return true; } - @ConfigItem( - keyName = "keyboardPin", - name = "Keyboard bank pin", - description = "Enables you to type your bank pin", - position = 22 - ) - default boolean keyboardPin() - { - return false; - } - @ConfigItem( keyName = "detachHotkey", name = "Detach Cam", - description = "Detach Camera hotkey, press this and it will activate detatched camera.", - position = 23 + description = "Detach Camera hotkey, press this and it will activate detached camera.", + position = 22 ) default Keybind detachHotkey() { @@ -136,7 +125,7 @@ public interface OpenOSRSConfig extends Config ) default String getExternalRepositories() { - return ExternalPluginManager.DEFAULT_PLUGIN_REPOS; + return OPRSExternalPluginManager.DEFAULT_PLUGIN_REPOS; } @ConfigItem( diff --git a/runelite-client/src/main/java/com/openosrs/client/events/ExternalPluginChanged.java b/runelite-client/src/main/java/com/openosrs/client/events/OPRSPluginChanged.java similarity index 94% rename from runelite-client/src/main/java/com/openosrs/client/events/ExternalPluginChanged.java rename to runelite-client/src/main/java/com/openosrs/client/events/OPRSPluginChanged.java index c7bc255929..3dc10eeddf 100644 --- a/runelite-client/src/main/java/com/openosrs/client/events/ExternalPluginChanged.java +++ b/runelite-client/src/main/java/com/openosrs/client/events/OPRSPluginChanged.java @@ -25,11 +25,10 @@ package com.openosrs.client.events; import lombok.Data; -import net.runelite.api.events.Event; import net.runelite.client.plugins.Plugin; @Data -public class ExternalPluginChanged implements Event +public class OPRSPluginChanged { private final String pluginId; private final Plugin plugin; diff --git a/runelite-client/src/main/java/com/openosrs/client/events/ExternalRepositoryChanged.java b/runelite-client/src/main/java/com/openosrs/client/events/OPRSRepositoryChanged.java similarity index 94% rename from runelite-client/src/main/java/com/openosrs/client/events/ExternalRepositoryChanged.java rename to runelite-client/src/main/java/com/openosrs/client/events/OPRSRepositoryChanged.java index ff4e46c795..c356d89539 100644 --- a/runelite-client/src/main/java/com/openosrs/client/events/ExternalRepositoryChanged.java +++ b/runelite-client/src/main/java/com/openosrs/client/events/OPRSRepositoryChanged.java @@ -25,10 +25,9 @@ package com.openosrs.client.events; import lombok.Data; -import net.runelite.api.events.Event; @Data -public class ExternalRepositoryChanged implements Event +public class OPRSRepositoryChanged { private final String owner; private final boolean added; diff --git a/runelite-client/src/main/java/com/openosrs/client/game/NPCStats.java b/runelite-client/src/main/java/com/openosrs/client/game/NPCStats.java deleted file mode 100644 index 93071976a7..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/game/NPCStats.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2019, TheStonedTurtle - * 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 com.openosrs.client.game; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; - -import lombok.Builder; -import lombok.Value; - -@Value -@Builder(builderClassName = "Builder") -public class NPCStats -{ - public static ImmutableMap statsMap; - - String name; - - int hitpoints; - int combatLevel; - int slayerLevel; - int attackSpeed; - - int attackLevel; - int strengthLevel; - int defenceLevel; - int rangeLevel; - int magicLevel; - - int stab; - int slash; - int crush; - int range; - int magic; - - int stabDef; - int slashDef; - int crushDef; - int rangeDef; - int magicDef; - - int bonusAttack; - int bonusStrength; - int bonusRangeStrength; - int bonusMagicDamage; - - boolean poisonImmune; - boolean venomImmune; - - boolean dragon; - boolean demon; - boolean undead; - - /** - * Based off the formula found here: http://services.runescape.com/m=forum/c=PLuJ4cy6gtA/forums.ws?317,318,712,65587452,209,337584542#209 - * - * @return bonus XP modifier - */ - public double calculateXpModifier() - { - final double averageLevel = Math.floor((attackLevel + strengthLevel + defenceLevel + hitpoints) / 4); - final double averageDefBonus = Math.floor((stabDef + slashDef + crushDef) / 3); - - return (1 + Math.floor(averageLevel * (averageDefBonus + bonusStrength + bonusAttack) / 5120) / 40); - } - - // Because this class is here we can't add the TypeAdapter to gson (easily) - // doesn't mean we can't use one to do it a bit quicker - public static final TypeAdapter NPC_STATS_TYPE_ADAPTER = new TypeAdapter() - { - @Override - public void write(JsonWriter out, NPCStats value) - { - throw new UnsupportedOperationException("Not supported"); - } - - @Override - public NPCStats read(JsonReader in) throws IOException - { - in.beginObject(); - NPCStats.Builder builder = NPCStats.builder(); - - // Name is the only one that's guaranteed - in.skipValue(); - builder.name(in.nextString()); - - while (in.hasNext()) - { - switch (in.nextName()) - { - case "hitpoints": - builder.hitpoints(in.nextInt()); - break; - case "combatLevel": - builder.combatLevel(in.nextInt()); - break; - case "slayerLevel": - builder.slayerLevel(in.nextInt()); - break; - case "attackSpeed": - builder.attackSpeed(in.nextInt()); - break; - case "attackLevel": - builder.attackLevel(in.nextInt()); - break; - case "strengthLevel": - builder.strengthLevel(in.nextInt()); - break; - case "defenceLevel": - builder.defenceLevel(in.nextInt()); - break; - case "rangeLevel": - builder.rangeLevel(in.nextInt()); - break; - case "magicLevel": - builder.magicLevel(in.nextInt()); - break; - case "stab": - builder.stab(in.nextInt()); - break; - case "slash": - builder.slash(in.nextInt()); - break; - case "crush": - builder.crush(in.nextInt()); - break; - case "range": - builder.range(in.nextInt()); - break; - case "magic": - builder.magic(in.nextInt()); - break; - case "stabDef": - builder.stabDef(in.nextInt()); - break; - case "slashDef": - builder.slashDef(in.nextInt()); - break; - case "crushDef": - builder.crushDef(in.nextInt()); - break; - case "rangeDef": - builder.rangeDef(in.nextInt()); - break; - case "magicDef": - builder.magicDef(in.nextInt()); - break; - case "bonusAttack": - builder.bonusAttack(in.nextInt()); - break; - case "bonusStrength": - builder.bonusStrength(in.nextInt()); - break; - case "bonusRangeStrength": - builder.bonusRangeStrength(in.nextInt()); - break; - case "bonusMagicDamage": - builder.bonusMagicDamage(in.nextInt()); - break; - case "poisonImmune": - builder.poisonImmune(in.nextBoolean()); - break; - case "venomImmune": - builder.venomImmune(in.nextBoolean()); - break; - case "dragon": - builder.dragon(in.nextBoolean()); - break; - case "demon": - builder.demon(in.nextBoolean()); - break; - case "undead": - builder.undead(in.nextBoolean()); - break; - } - } - - in.endObject(); - return builder.build(); - } - }; - - public static void loadStats() throws IOException - { - try (JsonReader reader = new JsonReader(new InputStreamReader(NPCStats.class.getResourceAsStream("npc_stats.json"), StandardCharsets.UTF_8))) - { - ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(2821); - reader.beginObject(); - - while (reader.hasNext()) - { - builder.put( - Integer.parseInt(reader.nextName()), - NPCStats.NPC_STATS_TYPE_ADAPTER.read(reader) - ); - } - - reader.endObject(); - statsMap = builder.build(); - } - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/com/openosrs/client/game/Sound.java b/runelite-client/src/main/java/com/openosrs/client/game/Sound.java index 24b7588040..27ca3e5111 100644 --- a/runelite-client/src/main/java/com/openosrs/client/game/Sound.java +++ b/runelite-client/src/main/java/com/openosrs/client/game/Sound.java @@ -1,42 +1,67 @@ +/* + * Copyright (c) 2021, ThatGamerBlue + * 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 com.openosrs.client.game; -public enum Sound +import java.io.File; +import java.net.URL; +import lombok.EqualsAndHashCode; +import lombok.SneakyThrows; +import lombok.Value; +import net.runelite.client.RuneLite; + +public abstract class Sound { - FIFTEEN_SECONDS(1, "net/runelite/client/game/sounds/15seconds.wav"), - FIVE_SECONDS(2, "net/runelite/client/game/sounds/5seconds.wav"), - ATTACK_WITH_MAGIC(3, "net/runelite/client/game/sounds/attackmagic.wav"), - ATTACK_WITH_MELEE(4, "net/runelite/client/game/sounds/attackmelee.wav"), - ATTACK_WITH_RANGED(5, "net/runelite/client/game/sounds/attackranged.wav"), - INCOMING(6, "net/runelite/client/game/sounds/incoming.wav"), - MOVE(7, "net/runelite/client/game/sounds/move.wav"), - PRAY_MAGIC(8, "net/runelite/client/game/sounds/praymagic.wav"), - PRAY_MELEE(9, "net/runelite/client/game/sounds/praymelee.wav"), - PRAY_RANGED(10, "net/runelite/client/game/sounds/prayranged.wav"), - REENABLE_PRAYER(11, "net/runelite/client/game/sounds/reenableprayer.wav"), - RUNAWAY(12, "net/runelite/client/game/sounds/runaway.wav"), - LOW_HEATLH(13, "net/runelite/client/game/sounds/lowhealth.wav"), - LOW_PRAYER(14, "net/runelite/client/game/sounds/lowprayer.wav"), - OUT_OF_COMBAT(15, "net/runelite/client/game/sounds/outofcombat.wav"), - RESTORED_SPECIAL_ATTACK(16, "net/runelite/client/game/sounds/restorespec.wav"), - IDLE(17, "net/runelite/client/game/sounds/idle.wav"), - BREAK(18, "net/runelite/client/game/sounds/break.wav"); + public abstract URL getPath(); - private final String filePath; - private final int id; + // TODO: these inner classes should probably be removed - Sound(int id, String filePath) + @Value + @EqualsAndHashCode(callSuper = true) // stop the warning + static class SoundJarResource extends Sound { - this.id = id; - this.filePath = filePath; + String path; + + @Override + public URL getPath() + { + return RuneLite.class.getResource(path); + } } - public String getFilePath() + @Value + @EqualsAndHashCode(callSuper = true) // stop the warning + static class SoundFileResource extends Sound { - return this.filePath; - } + File file; - public int getId() - { - return this.id; + @Override + @SneakyThrows + public URL getPath() + { + return file.toURI().toURL(); + } } } \ No newline at end of file diff --git a/runelite-client/src/main/java/com/openosrs/client/game/SoundManager.java b/runelite-client/src/main/java/com/openosrs/client/game/SoundManager.java index 0f46afb1f8..0c1330cd5b 100644 --- a/runelite-client/src/main/java/com/openosrs/client/game/SoundManager.java +++ b/runelite-client/src/main/java/com/openosrs/client/game/SoundManager.java @@ -1,6 +1,30 @@ +/* + * Copyright (c) 2021, ThatGamerBlue + * 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 com.openosrs.client.game; -import com.google.inject.Inject; import java.io.IOException; import javax.inject.Singleton; import javax.sound.sampled.AudioFormat; @@ -12,36 +36,20 @@ import javax.sound.sampled.FloatControl; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.UnsupportedAudioFileException; -import net.runelite.client.config.RuneLiteConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Singleton public class SoundManager { - private static final Logger log = LoggerFactory.getLogger(SoundManager.class); - private final RuneLiteConfig runeliteConfig; - - @Inject - private SoundManager(RuneLiteConfig runeLiteConfig) + public void play(final Sound sound) { - this.runeliteConfig = runeLiteConfig; - } - - public void playSound(final Sound sound) - { - new Thread(new Runnable() + new Thread(() -> { - - @Override - public void run() + try (AudioInputStream in = AudioSystem.getAudioInputStream(sound.getPath())) { - try + AudioFormat outFormat = SoundManager.this.getOutFormat(in.getFormat()); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat); + try (SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) { - AudioInputStream in = AudioSystem.getAudioInputStream(this.getClass().getClassLoader().getResource(sound.getFilePath())); - AudioFormat outFormat = SoundManager.this.getOutFormat(in.getFormat()); - DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat); - SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info); if (line != null) { line.open(outFormat, 2200); @@ -50,26 +58,19 @@ public class SoundManager int volume = 50; FloatControl gainControl = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN); BooleanControl muteControl = (BooleanControl) line.getControl(BooleanControl.Type.MUTE); - if (volume == 0) - { - muteControl.setValue(true); - } - else - { - muteControl.setValue(false); - gainControl.setValue((float) (Math.log((double) volume / 100.0) / Math.log(10.0) * 20.0)); - } + muteControl.setValue(false); + gainControl.setValue((float) (Math.log((double) volume / 100.0) / Math.log(10.0) * 20.0)); } line.start(); - SoundManager.this.stream(AudioSystem.getAudioInputStream(outFormat, in), line); + stream(AudioSystem.getAudioInputStream(outFormat, in), line); line.drain(); line.stop(); } } - catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) - { - throw new IllegalStateException(e); - } + } + catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) + { + throw new IllegalStateException(e); } }).start(); } diff --git a/runelite-client/src/main/java/com/openosrs/client/game/WorldLocation.java b/runelite-client/src/main/java/com/openosrs/client/game/WorldLocation.java index 5a65f8ec98..0782f79406 100644 --- a/runelite-client/src/main/java/com/openosrs/client/game/WorldLocation.java +++ b/runelite-client/src/main/java/com/openosrs/client/game/WorldLocation.java @@ -1,13 +1,28 @@ -/******************************************************************************* - * Copyright (c) 2019 openosrs - * Redistributions and modifications of this software are permitted as long as this notice remains in its original unmodified state at the top of this file. - * If there are any questions comments, or feedback about this software, please direct all inquiries directly to the file authors: - * ST0NEWALL#9112 - * Macweese#1169 UID 159941566994186240, macweese@pm.me - * openosrs Discord: https://discord.gg/Q7wFtCe - * openosrs website: https://openosrs.com - ******************************************************************************/ - +/* + * Copyright (c) 2019, ST0NEWALL + * Copyright (c) 2020, Macweese + * 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 com.openosrs.client.game; import com.google.common.collect.ImmutableMap; @@ -18,9 +33,9 @@ import java.util.stream.Collectors; import lombok.Getter; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; -import com.openosrs.client.util.PvPUtil; +import net.runelite.client.util.PvPUtil; - /* +/* * Enums sorted alphabetically by main regions (Kingdoms) and then their sub-regions or notable features * Example: * Wilderness diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginClasspath.java b/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginClasspath.java deleted file mode 100644 index 0e0e72842b..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginClasspath.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.openosrs.client.plugins; - -import org.pf4j.DevelopmentPluginClasspath; - -class ExternalPluginClasspath extends DevelopmentPluginClasspath -{ - static final String GRADLE_DEPS_PATH = "build/deps"; - - ExternalPluginClasspath() - { - addJarsDirectories(GRADLE_DEPS_PATH); - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginFileFilter.java b/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginFileFilter.java deleted file mode 100644 index 39b261d861..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginFileFilter.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.openosrs.client.plugins; - -import java.io.File; -import java.io.FileFilter; -import java.util.Arrays; -import java.util.List; - -/** - * Determines whether a {@link File} is an external plugin folder. To be considered a plugin a folder must: - *

- * * Must not be a blacklisted name - * * Have a {@code .gradle.kts} file in the root named after the folder - * * Have a {@code MANIFEST.MF} located at {@code build/tmp/jar/MANIFEST.MF} - */ -public class ExternalPluginFileFilter implements FileFilter -{ - private static final List blacklist = Arrays.asList( - ".git", - "build", - "target", - "release" - ); - - private static final List buildFiles = Arrays.asList( - "%s.gradle.kts", - "%s.gradle" - ); - - @Override - public boolean accept(File pathName) - { - // Check if this path looks like a plugin development directory - if (!pathName.isDirectory()) - { - return false; - } - - String dirName = pathName.getName(); - if (blacklist.contains(dirName)) - { - return false; - } - - // Check if the plugin directory has a MANIFEST.MF which si required for loading - if (!new File(pathName, ExternalPluginManager.DEVELOPMENT_MANIFEST_PATH).exists()) - { - return false; - } - - // By convention plugins their directory is $name and they have a $name.gradle.kts or $name.gradle file in their root - for (String buildFile : buildFiles) - { - if (new File(pathName, String.format(buildFile, dirName)).exists()) - { - return true; - } - } - - return false; - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/Plugin.java b/runelite-client/src/main/java/com/openosrs/client/plugins/Plugin.java deleted file mode 100644 index 81e661de09..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/Plugin.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.openosrs.client.plugins; - -import com.google.inject.Injector; -import org.pf4j.ExtensionPoint; - -public class Plugin extends net.runelite.client.plugins.Plugin implements ExtensionPoint -{ - public Injector injector; -} diff --git a/runelite-client/src/main/java/com/openosrs/client/ui/components/InfoPanel.java b/runelite-client/src/main/java/com/openosrs/client/ui/components/InfoPanel.java index 07efae32cb..f0d0fa1b2f 100644 --- a/runelite-client/src/main/java/com/openosrs/client/ui/components/InfoPanel.java +++ b/runelite-client/src/main/java/com/openosrs/client/ui/components/InfoPanel.java @@ -24,6 +24,7 @@ */ package com.openosrs.client.ui.components; +import com.openosrs.client.OpenOSRS; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; @@ -46,7 +47,7 @@ import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import com.openosrs.client.ui.OpenOSRSSplashScreen; import net.runelite.client.util.ImageUtil; -import com.openosrs.client.util.LinkBrowser; +import net.runelite.client.util.LinkBrowser; @Slf4j public class InfoPanel extends JPanel @@ -88,7 +89,7 @@ public class InfoPanel extends JPanel c.weighty = 0; // OpenOSRS version - this.add(createPanelTextButton("OpenOSRS Version: " + RuneLiteProperties.getLauncherVersion()), c); + this.add(createPanelTextButton("OpenOSRS Version: " + OpenOSRS.SYSTEM_VERSION), c); c.gridy++; final JLabel logsFolder = createPanelButton("Open logs folder", null, () -> LinkBrowser.openLocalFile(LOGS_DIR)); diff --git a/runelite-client/src/main/java/com/openosrs/client/ui/overlay/OverlayUtil.java b/runelite-client/src/main/java/com/openosrs/client/ui/overlay/OverlayUtil.java deleted file mode 100644 index 0c4f611be5..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/ui/overlay/OverlayUtil.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.openosrs.client.ui.overlay; - - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.image.BufferedImage; -import java.util.List; -import net.runelite.api.*; -import net.runelite.api.Point; -import net.runelite.api.coords.LocalPoint; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.vars.InterfaceTab; -import net.runelite.api.widgets.Widget; - - -public class OverlayUtil extends net.runelite.client.ui.overlay.OverlayUtil -{ - public static void drawTiles(Graphics2D graphics, Client client, WorldPoint point, WorldPoint playerPoint, Color color, int strokeWidth, int outlineAlpha, int fillAlpha) - { - if (point.distanceTo(playerPoint) >= 32) - { - return; - } - LocalPoint lp = LocalPoint.fromWorld(client, point); - if (lp == null) - { - return; - } - - Polygon poly = Perspective.getCanvasTilePoly(client, lp); - if (poly == null) - { - return; - } - drawStrokeAndFillPoly(graphics, color, strokeWidth, outlineAlpha, fillAlpha, poly); - } - - public static void drawStrokeAndFillPoly(Graphics2D graphics, Color color, int strokeWidth, int outlineAlpha, int fillAlpha, Polygon poly) - { - graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), outlineAlpha)); - graphics.setStroke(new BasicStroke(strokeWidth)); - graphics.draw(poly); - graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), fillAlpha)); - graphics.fill(poly); - } - - public static Rectangle renderPrayerOverlay(Graphics2D graphics, Client client, Prayer prayer, Color color) - { - Widget widget = client.getWidget(prayer.getWidgetInfo()); - - if (widget == null || client.getVar(VarClientInt.INVENTORY_TAB) != InterfaceTab.PRAYER.getId()) - { - return null; - } - - Rectangle bounds = widget.getBounds(); - renderPolygon(graphics, rectangleToPolygon(bounds), color); - return bounds; - } - - private static Polygon rectangleToPolygon(Rectangle rect) - { - int[] xpoints = {rect.x, rect.x + rect.width, rect.x + rect.width, rect.x}; - int[] ypoints = {rect.y, rect.y, rect.y + rect.height, rect.y + rect.height}; - - return new Polygon(xpoints, ypoints, 4); - } - - public static void renderTextLocation(Graphics2D graphics, String txtString, int fontSize, int fontStyle, Color fontColor, Point canvasPoint, boolean shadows, int yOffset) - { - graphics.setFont(new Font("Arial", fontStyle, fontSize)); - if (canvasPoint != null) - { - final Point canvasCenterPoint = new Point( - canvasPoint.getX(), - canvasPoint.getY() + yOffset); - final Point canvasCenterPoint_shadow = new Point( - canvasPoint.getX() + 1, - canvasPoint.getY() + 1 + yOffset); - if (shadows) - { - renderTextLocation(graphics, canvasCenterPoint_shadow, txtString, Color.BLACK); - } - renderTextLocation(graphics, canvasCenterPoint, txtString, fontColor); - } - } - - public static void setProgressIcon(Graphics2D graphics, Point point, BufferedImage currentPhaseIcon, int totalWidth, int bgPadding, int currentPosX, Color colorIconBackground, int overlayIconDistance, Color colorIconBorder, Color colorIconBorderFill) - { - graphics.setStroke(new BasicStroke(2)); - graphics.setColor(colorIconBackground); - graphics.fillOval( - point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - currentPhaseIcon.getHeight() / 2 - overlayIconDistance - bgPadding, - currentPhaseIcon.getWidth() + bgPadding * 2, - currentPhaseIcon.getHeight() + bgPadding * 2); - - graphics.setColor(colorIconBorder); - graphics.drawOval( - point.getX() - totalWidth / 2 + currentPosX - bgPadding, - point.getY() - currentPhaseIcon.getHeight() / 2 - overlayIconDistance - bgPadding, - currentPhaseIcon.getWidth() + bgPadding * 2, - currentPhaseIcon.getHeight() + bgPadding * 2); - - graphics.drawImage( - currentPhaseIcon, - point.getX() - totalWidth / 2 + currentPosX, - point.getY() - currentPhaseIcon.getHeight() / 2 - overlayIconDistance, - null); - - graphics.setColor(colorIconBorderFill); - } - - public static List getHitSquares(WorldPoint npcLoc, int npcSize, int thickness, boolean includeUnder) - { - List little = new WorldArea(npcLoc, npcSize, npcSize).toWorldPointList(); - List big = new WorldArea(npcLoc.getX() - thickness, npcLoc.getY() - thickness, npcSize + (thickness * 2), npcSize + (thickness * 2), npcLoc.getPlane()).toWorldPointList(); - if (!includeUnder) - { - big.removeIf(little::contains); - } - return big; - } - - public static void renderFilledPolygon(Graphics2D graphics, Shape poly, Color color) - { - graphics.setColor(color); - final Stroke originalStroke = graphics.getStroke(); - graphics.setStroke(new BasicStroke(2)); - graphics.draw(poly); - graphics.fill(poly); - graphics.setStroke(originalStroke); - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/ColorUtil.java b/runelite-client/src/main/java/com/openosrs/client/util/ColorUtil.java deleted file mode 100644 index 57f68c4703..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/util/ColorUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.openosrs.client.util; - -import java.awt.Color; - -public class ColorUtil extends net.runelite.client.util.ColorUtil -{ - /** - * Modifies the alpha component on a Color - * - * @param color The color to set the alpha value on - * @param alpha The alpha value to set on the color - * @return color - */ - public static int setAlphaComponent(Color color, int alpha) - { - return setAlphaComponent(color.getRGB(), alpha); - } - - /** - * Modifies the alpha component on a Color - * - * @param color The color to set the alpha value on - * @param alpha The alpha value to set on the color - * @return color - */ - public static int setAlphaComponent(int color, int alpha) - { - if (alpha < 0 || alpha > 255) - { - throw new IllegalArgumentException("alpha must be between 0 and 255."); - } - return (color & 0x00ffffff) | (alpha << 24); - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/DeferredDocumentChangedListener.java b/runelite-client/src/main/java/com/openosrs/client/util/DeferredDocumentChangedListener.java deleted file mode 100644 index b9728e03be..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/util/DeferredDocumentChangedListener.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.openosrs.client.util; - -import java.util.ArrayList; -import java.util.List; -import javax.swing.Timer; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -public class DeferredDocumentChangedListener implements DocumentListener -{ - private final Timer timer; - private final List listeners; - - public DeferredDocumentChangedListener() - { - listeners = new ArrayList<>(25); - timer = new Timer(200, e -> fireStateChanged()); - timer.setRepeats(false); - } - - public void addChangeListener(ChangeListener listener) - { - listeners.add(listener); - } - - private void fireStateChanged() - { - if (!listeners.isEmpty()) - { - ChangeEvent evt = new ChangeEvent(this); - for (ChangeListener listener : listeners) - { - listener.stateChanged(evt); - } - } - } - - @Override - public void insertUpdate(DocumentEvent e) - { - timer.restart(); - } - - @Override - public void removeUpdate(DocumentEvent e) - { - timer.restart(); - } - - @Override - public void changedUpdate(DocumentEvent e) - { - timer.restart(); - } - -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/ImageUtil.java b/runelite-client/src/main/java/com/openosrs/client/util/ImageUtil.java deleted file mode 100644 index 599eff1ac4..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/util/ImageUtil.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.openosrs.client.util; - -import net.runelite.api.Client; -import net.runelite.api.SpritePixels; - -import java.awt.Color; -import java.awt.image.BufferedImage; -import java.awt.image.WritableRaster; -import java.util.Arrays; -import java.util.function.Predicate; - -public class ImageUtil extends net.runelite.client.util.ImageUtil -{ - /** - * Recolors pixels of the given image with the given color based on a given recolor condition - * predicate. - * - * @param image The image which should have its non-transparent pixels recolored. - * @param color The color with which to recolor pixels. - * @param recolorCondition The condition on which to recolor pixels with the given color. - * @return The given image with all pixels fulfilling the recolor condition predicate - * set to the given color. - */ - public static BufferedImage recolorImage(final BufferedImage image, final Color color, final Predicate recolorCondition) - { - final BufferedImage recoloredImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); - for (int x = 0; x < recoloredImage.getWidth(); x++) - { - for (int y = 0; y < recoloredImage.getHeight(); y++) - { - final Color pixelColor = new Color(image.getRGB(x, y), true); - if (!recolorCondition.test(pixelColor)) - { - recoloredImage.setRGB(x, y, image.getRGB(x, y)); - continue; - } - - recoloredImage.setRGB(x, y, color.getRGB()); - } - } - return recoloredImage; - } - - public static BufferedImage recolorImage(BufferedImage image, final Color color) - { - int width = image.getWidth(); - int height = image.getHeight(); - WritableRaster raster = image.getRaster(); - - for (int xx = 0; xx < width; xx++) - { - for (int yy = 0; yy < height; yy++) - { - int[] pixels = raster.getPixel(xx, yy, (int[]) null); - pixels[0] = color.getRed(); - pixels[1] = color.getGreen(); - pixels[2] = color.getBlue(); - raster.setPixel(xx, yy, pixels); - } - } - return image; - } - - /** - * Draw fg centered on top of bg - */ - public static SpritePixels mergeSprites(final Client client, final SpritePixels bg, final SpritePixels fg) - { - assert fg.getHeight() <= bg.getHeight() && fg.getWidth() <= bg.getWidth() : "Background has to be larger than foreground"; - - final int[] canvas = Arrays.copyOf(bg.getPixels(), bg.getWidth() * bg.getHeight()); - final SpritePixels result = client.createSpritePixels(canvas, bg.getWidth(), bg.getHeight()); - - final int bgWid = bg.getWidth(); - final int fgHgt = fg.getHeight(); - final int fgWid = fg.getWidth(); - - final int xOffset = (bgWid - fgWid) / 2; - final int yOffset = (bg.getHeight() - fgHgt) / 2; - - final int[] fgPixels = fg.getPixels(); - - for (int y1 = yOffset, y2 = 0; y2 < fgHgt; y1++, y2++) - { - int i1 = y1 * bgWid + xOffset; - int i2 = y2 * fgWid; - - for (int x = 0; x < fgWid; x++, i1++, i2++) - { - if (fgPixels[i2] > 0) - { - canvas[i1] = fgPixels[i2]; - } - } - } - - return result; - } - - /** - * Resize Sprite sprite to given width (newW) and height (newH) - */ - public static SpritePixels resizeSprite(final Client client, final SpritePixels sprite, int newW, int newH) - { - assert newW > 0 && newH > 0; - - final int oldW = sprite.getWidth(); - final int oldH = sprite.getHeight(); - - if (oldW == newW && oldH == newH) - { - return sprite; - } - - final int[] canvas = new int[newW * newH]; - final int[] pixels = sprite.getPixels(); - - final SpritePixels result = client.createSpritePixels(canvas, newW, newH); - - int pixelX = 0; - int pixelY = 0; - - final int oldMaxW = sprite.getMaxWidth(); - final int oldMaxH = sprite.getMaxHeight(); - - final int pixelW = (oldMaxW << 16) / newW; - final int pixelH = (oldMaxH << 16) / newH; - - int xOffset = 0; - int yOffset = 0; - - int canvasIdx; - if (sprite.getOffsetX() > 0) - { - canvasIdx = (pixelW + (sprite.getOffsetX() << 16) - 1) / pixelW; - xOffset += canvasIdx; - pixelX += canvasIdx * pixelW - (sprite.getOffsetX() << 16); - } - - if (sprite.getOffsetY() > 0) - { - canvasIdx = (pixelH + (sprite.getOffsetY() << 16) - 1) / pixelH; - yOffset += canvasIdx; - pixelY += canvasIdx * pixelH - (sprite.getOffsetY() << 16); - } - - if (oldW < oldMaxW) - { - newW = (pixelW + ((oldW << 16) - pixelX) - 1) / pixelW; - } - - if (oldH < oldMaxH) - { - newH = (pixelH + ((oldH << 16) - pixelY) - 1) / pixelH; - } - - canvasIdx = xOffset + yOffset * newW; - int canvasOffset = 0; - if (yOffset + newH > newH) - { - newH -= yOffset + newH - newH; - } - - int tmp; - if (yOffset < 0) - { - tmp = -yOffset; - newH -= tmp; - canvasIdx += tmp * newW; - pixelY += pixelH * tmp; - } - - if (newW + xOffset > newW) - { - tmp = newW + xOffset - newW; - newW -= tmp; - canvasOffset += tmp; - } - - if (xOffset < 0) - { - tmp = -xOffset; - newW -= tmp; - canvasIdx += tmp; - pixelX += pixelW * tmp; - canvasOffset += tmp; - } - - client.scaleSprite(canvas, pixels, 0, pixelX, pixelY, canvasIdx, canvasOffset, newW, newH, pixelW, pixelH, oldW); - - return result; - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/LinkBrowser.java b/runelite-client/src/main/java/com/openosrs/client/util/LinkBrowser.java deleted file mode 100644 index 64df5e1127..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/util/LinkBrowser.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.openosrs.client.util; - -import java.awt.Desktop; -import java.awt.Toolkit; -import java.awt.datatransfer.StringSelection; -import java.io.File; -import java.io.IOException; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class LinkBrowser extends net.runelite.client.util.LinkBrowser -{ - /** - * Tries to open the specified {@code File} with the systems default text editor. If operation fails - * an error message is displayed with the option to copy the absolute file path to clipboard. - * - * @param file the File instance of the log file - * @return did the file open successfully? - */ - public static boolean openLocalFile(final File file) - { - if (file == null || !file.exists()) - { - return false; - } - - if (attemptOpenLocalFile(file)) - { - log.debug("Opened log file through Desktop#edit to {}", file); - return true; - } - - showMessageBox("Unable to open log file. Press 'OK' and the file path will be copied to your clipboard", file.getAbsolutePath()); - return false; - } - - private static boolean attemptOpenLocalFile(final File file) - { - if (!Desktop.isDesktopSupported()) - { - return false; - } - - final Desktop desktop = Desktop.getDesktop(); - - if (!desktop.isSupported(Desktop.Action.OPEN)) - { - return false; - } - - try - { - desktop.open(file); - return true; - } - catch (IOException ex) - { - log.warn("Failed to open Desktop#edit {}", file, ex); - return false; - } - } - - /** - * Open swing message box with specified message and copy data to clipboard - * @param message message to show - */ - private static void showMessageBox(final String message, final String data) - { - SwingUtilities.invokeLater(() -> - { - final int result = JOptionPane.showConfirmDialog(null, message, "Message", - JOptionPane.OK_CANCEL_OPTION); - - if (result == JOptionPane.OK_OPTION) - { - final StringSelection stringSelection = new StringSelection(data); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); - } - }); - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/MiscUtils.java b/runelite-client/src/main/java/com/openosrs/client/util/MiscUtils.java deleted file mode 100644 index d5bc562121..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/util/MiscUtils.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.openosrs.client.util; - -import java.awt.Polygon; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import net.runelite.api.Client; -import net.runelite.api.Player; -import net.runelite.api.WorldType; -import net.runelite.api.coords.WorldPoint; - -public class MiscUtils -{ - private static final int[] abovePointsX = {2944, 3392, 3392, 2944}; - private static final int[] abovePointsY = {3523, 3523, 3971, 3971}; - private static final int[] belowPointsX = {2944, 2944, 3264, 3264}; - private static final int[] belowPointsY = {9918, 10360, 10360, 9918}; - - private static final Polygon abovePoly = new Polygon(abovePointsX, abovePointsY, abovePointsX.length); - private static final Polygon belowPoly = new Polygon(belowPointsX, belowPointsY, belowPointsX.length); - - private static final ChronoUnit[] ORDERED_CHRONOS = new ChronoUnit[] - { - ChronoUnit.YEARS, - ChronoUnit.MONTHS, - ChronoUnit.WEEKS, - ChronoUnit.DAYS, - ChronoUnit.HOURS, - ChronoUnit.MINUTES, - ChronoUnit.SECONDS - }; - - //test replacement so private for now - private static boolean inWildy(WorldPoint point) - { - if (point == null) - { - return false; - } - - return abovePoly.contains(point.getX(), point.getY()) || belowPoly.contains(point.getX(), point.getY()); - } - - public static int getWildernessLevelFrom(Client client, WorldPoint point) - { - if (client == null) - { - return 0; - } - - if (point == null) - { - return 0; - } - - int x = point.getX(); - - if (point.getPlane() == 0 && (x < 2940 || x > 3391)) - { - return 0; - } - - int y = point.getY(); - //v underground //v above ground - int wildernessLevel = clamp(y > 6400 ? ((y - 9920) / 8) + 1 : ((y - 3520) / 8) + 1, 0, 56); - - if (point.getPlane() > 0 && y < 9920) - { - wildernessLevel = 0; - } - - if (client.getWorldType().stream().anyMatch(worldType -> worldType == WorldType.PVP || worldType == WorldType.HIGH_RISK)) - { - wildernessLevel += 15; - } - - return Math.max(0, wildernessLevel); - } - - public static int clamp(int val, int min, int max) - { - return Math.max(min, Math.min(max, val)); - } - - public static float clamp(float val, float min, float max) - { - return Math.max(min, Math.min(max, val)); - } - - public static boolean inWilderness(Client client) - { - Player localPlayer = client.getLocalPlayer(); - - if (localPlayer == null) - { - return false; - } - - return inWildy(localPlayer.getWorldLocation()); - - //return getWildernessLevelFrom(client, localPlayer.getWorldLocation()) > 0; - } - - public static String formatTimeAgo(Duration dur) - { - long dA = 0, dB = 0, rm; - ChronoUnit cA = null, cB = null; - for (int i = 0; i < ORDERED_CHRONOS.length; i++) - { - cA = ORDERED_CHRONOS[i]; - dA = dur.getSeconds() / cA.getDuration().getSeconds(); - rm = dur.getSeconds() % cA.getDuration().getSeconds(); - if (dA <= 0) - { - cA = null; - continue; - } - - if (i + 1 < ORDERED_CHRONOS.length) - { - cB = ORDERED_CHRONOS[i + 1]; - dB = rm / cB.getDuration().getSeconds(); - - if (dB <= 0) - { - cB = null; - } - } - - break; - } - - if (cA == null) - { - return "just now."; - } - - String str = formatUnit(cA, dA); - - if (cB != null) - { - str += " and " + formatUnit(cB, dB); - } - - return str + " ago."; - } - - private static String formatUnit(ChronoUnit chrono, long val) - { - boolean multiple = val != 1; - String str; - if (multiple) - { - str = val + " "; - } - else - { - str = "a" + (chrono == ChronoUnit.HOURS ? "n " : " "); - } - str += chrono.name().toLowerCase(); - if (!multiple) - { - if (str.charAt(str.length() - 1) == 's') - { - str = str.substring(0, str.length() - 1); - } - } - else if (str.charAt(str.length() - 1) != 's') - { - str += "s"; - } - return str; - } - - /** - * Mostly stolen from {@link java.net.URLStreamHandler#toExternalForm(URL)} - * - * @param url URL to encode - * @return URL, with path, query and ref encoded - */ - public static String urlToStringEncoded(URL url) - { - String s; - String path = url.getPath() != null ? Stream.of(url.getPath().split("/")) - .map(s2 -> URLEncoder.encode(s2, StandardCharsets.UTF_8)).collect(Collectors.joining("/")) : ""; - return url.getProtocol() - + ':' - + (((s = url.getAuthority()) != null && s.length() > 0) ? "//" + s : "") - + (path) - + (((s = url.getQuery()) != null) ? '?' + urlEncode(s) : "") - + (((s = url.getRef()) != null) ? '#' + urlEncode(s) : ""); - } - - private static String urlEncode(String s) - { - return URLEncoder.encode(s, StandardCharsets.UTF_8); - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/SwingUtil.java b/runelite-client/src/main/java/com/openosrs/client/util/SwingUtil.java deleted file mode 100644 index 799b67bfa4..0000000000 --- a/runelite-client/src/main/java/com/openosrs/client/util/SwingUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.openosrs.client.util; - -import java.awt.EventQueue; -import java.lang.reflect.InvocationTargetException; - -public class SwingUtil extends net.runelite.client.util.SwingUtil -{ - public static void syncExec(final Runnable r) throws InvocationTargetException, InterruptedException - { - if (EventQueue.isDispatchThread()) - { - r.run(); - } - else - { - EventQueue.invokeAndWait(r); - } - } -} diff --git a/runelite-client/src/main/java/com/openosrs/client/util/NonScheduledExecutorServiceExceptionLogger.java b/runelite-client/src/main/java/net/runelite/client/NonScheduledExecutorServiceExceptionLogger.java similarity index 65% rename from runelite-client/src/main/java/com/openosrs/client/util/NonScheduledExecutorServiceExceptionLogger.java rename to runelite-client/src/main/java/net/runelite/client/NonScheduledExecutorServiceExceptionLogger.java index 08436116a5..0f0986a703 100644 --- a/runelite-client/src/main/java/com/openosrs/client/util/NonScheduledExecutorServiceExceptionLogger.java +++ b/runelite-client/src/main/java/net/runelite/client/NonScheduledExecutorServiceExceptionLogger.java @@ -1,4 +1,29 @@ -package com.openosrs.client.util; +/* + * Copyright (c) 2020, SwazRGB + * 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; import java.util.Collection; import java.util.List; diff --git a/runelite-client/src/main/java/net/runelite/client/Notifier.java b/runelite-client/src/main/java/net/runelite/client/Notifier.java index 30a9ad4c3b..e36c43626b 100644 --- a/runelite-client/src/main/java/net/runelite/client/Notifier.java +++ b/runelite-client/src/main/java/net/runelite/client/Notifier.java @@ -59,6 +59,7 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.Constants; import net.runelite.api.GameState; +import net.runelite.api.Player; import net.runelite.client.chat.ChatColorType; import net.runelite.client.chat.ChatMessageBuilder; import net.runelite.client.chat.ChatMessageManager; @@ -175,7 +176,7 @@ public class Notifier if (runeLiteConfig.enableTrayNotifications()) { - sendNotification(appName, message, type); + sendNotification(buildTitle(), message, type); } switch (runeLiteConfig.notificationSound()) @@ -210,6 +211,23 @@ public class Notifier log.debug(message); } + private String buildTitle() + { + Player player = client.getLocalPlayer(); + if (player == null) + { + return appName; + } + + String name = player.getName(); + if (Strings.isNullOrEmpty(name)) + { + return appName; + } + + return appName + " - " + name; + } + public void processFlash(final Graphics2D graphics) { FlashNotification flashNotification = runeLiteConfig.flashNotification(); diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 9d728a6865..d30305ae59 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -30,6 +30,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; +import com.openosrs.client.graphics.ModelOutlineRenderer; import java.io.File; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; @@ -70,8 +71,10 @@ import net.runelite.client.game.ItemManager; import net.runelite.client.game.LootManager; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.menus.MenuManager; +import net.runelite.client.plugins.OPRSExternalPluginManager; import net.runelite.client.rs.ClientLoader; import net.runelite.client.rs.ClientUpdateCheckMode; +import net.runelite.client.task.Scheduler; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.DrawManager; import net.runelite.client.ui.FatalErrorDialog; @@ -93,9 +96,9 @@ import org.slf4j.LoggerFactory; @Slf4j public class RuneLite { - public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite"); + public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".openosrs"); public static final File CACHE_DIR = new File(RUNELITE_DIR, "cache"); - public static final File PLUGINS_DIR = new File(RUNELITE_DIR, "plugins"); + public static final File PLUGINS_DIR = new File(RUNELITE_DIR, "plugin-hub"); public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles"); public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots"); public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs"); @@ -114,7 +117,7 @@ public class RuneLite private ExternalPluginManager externalPluginManager; @Inject - private com.openosrs.client.plugins.ExternalPluginManager oprsExternalPluginManager; + private OPRSExternalPluginManager oprsExternalPluginManager; @Inject private EventBus eventBus; @@ -170,6 +173,9 @@ public class RuneLite @Inject private Provider worldMapOverlay; + @Inject + private Provider modelOutlineRenderer; + @Inject private Provider lootManager; @@ -183,6 +189,9 @@ public class RuneLite @Nullable private Client client; + @Inject + private Scheduler scheduler; + public static void main(String[] args) throws Exception { Locale.setDefault(Locale.ENGLISH); @@ -241,6 +250,8 @@ public class RuneLite } }); + OpenOSRS.preload(); + OkHttpClient.Builder okHttpClientBuilder = RuneLiteAPI.CLIENT.newBuilder() .cache(new Cache(new File(CACHE_DIR, "okhttp"), MAX_OKHTTP_CACHE_SIZE)); @@ -286,8 +297,6 @@ public class RuneLite injector.getInstance(RuneLite.class).start(); - OpenOSRS.init(); - final long end = System.currentTimeMillis(); final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean(); final long uptime = rb.getUptime(); @@ -396,6 +405,12 @@ public class RuneLite // Start plugins pluginManager.startPlugins(); + // Register additional schedulers + if (this.client != null) + { + scheduler.registerObject(modelOutlineRenderer.get()); + } + SplashScreen.stop(); clientUI.show(); diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java index c20df7d8e5..19dd5c0c65 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java @@ -25,11 +25,11 @@ package net.runelite.client; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.gson.Gson; import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.name.Names; import com.openosrs.client.config.OpenOSRSConfig; -import com.openosrs.client.util.NonScheduledExecutorServiceExceptionLogger; import java.applet.Applet; import java.io.File; import java.util.Properties; @@ -58,6 +58,7 @@ import net.runelite.client.plugins.PluginManager; import net.runelite.client.task.Scheduler; import net.runelite.client.util.DeferredEventBus; import net.runelite.client.util.ExecutorServiceExceptionLogger; +import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.chat.ChatClient; import okhttp3.OkHttpClient; import org.slf4j.Logger; @@ -95,6 +96,8 @@ public class RuneLiteModule extends AbstractModule bind(PluginManager.class); bind(SessionManager.class); + bind(Gson.class).toInstance(RuneLiteAPI.GSON); + bind(Callbacks.class).to(Hooks.class); bind(EventBus.class) diff --git a/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java b/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java index cadf0b4562..4b6bf1674e 100644 --- a/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java +++ b/runelite-client/src/main/java/net/runelite/client/account/SessionManager.java @@ -64,6 +64,7 @@ public class SessionManager private final WSClient wsClient; private final File sessionFile; private final AccountClient accountClient; + private final Gson gson; @Inject private SessionManager( @@ -71,13 +72,15 @@ public class SessionManager ConfigManager configManager, EventBus eventBus, WSClient wsClient, - OkHttpClient okHttpClient) + OkHttpClient okHttpClient, + Gson gson) { this.configManager = configManager; this.eventBus = eventBus; this.wsClient = wsClient; this.sessionFile = sessionfile; this.accountClient = new AccountClient(okHttpClient); + this.gson = gson; eventBus.register(this); } @@ -94,7 +97,7 @@ public class SessionManager try (FileInputStream in = new FileInputStream(sessionFile)) { - session = new Gson().fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), AccountSession.class); + session = gson.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), AccountSession.class); log.debug("Loaded session for {}", session.getUsername()); } @@ -124,7 +127,7 @@ public class SessionManager try (Writer fw = new OutputStreamWriter(new FileOutputStream(sessionFile), StandardCharsets.UTF_8)) { - new Gson().toJson(accountSession, fw); + gson.toJson(accountSession, fw); log.debug("Saved session to {}", sessionFile); } diff --git a/runelite-client/src/main/java/net/runelite/client/chat/ChatCommandManager.java b/runelite-client/src/main/java/net/runelite/client/chat/ChatCommandManager.java index 03bc5960b6..0c3a0bfcbc 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/ChatCommandManager.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/ChatCommandManager.java @@ -106,11 +106,6 @@ public class ChatCommandManager implements ChatboxInputListener String message = chatMessage.getMessage(); String command = extractCommand(message); - if (command == null) - { - return; - } - ChatCommand chatCommand = commands.get(command.toLowerCase()); if (chatCommand == null) { @@ -137,11 +132,6 @@ public class ChatCommandManager implements ChatboxInputListener } String command = extractCommand(message); - if (command == null) - { - return false; - } - ChatCommand chatCommand = commands.get(command.toLowerCase()); if (chatCommand == null) { @@ -163,11 +153,6 @@ public class ChatCommandManager implements ChatboxInputListener final String message = privateMessageInput.getMessage(); String command = extractCommand(message); - if (command == null) - { - return false; - } - ChatCommand chatCommand = commands.get(command.toLowerCase()); if (chatCommand == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java index 7faa7ac564..6e888ce8f1 100644 --- a/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java +++ b/runelite-client/src/main/java/net/runelite/client/chat/ChatMessageManager.java @@ -39,22 +39,22 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Inject; import javax.inject.Singleton; -import net.runelite.api.ChatLineBuffer; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.MessageNode; import net.runelite.api.Player; import net.runelite.api.Varbits; import net.runelite.api.events.ChatMessage; -import net.runelite.client.events.ConfigChanged; import net.runelite.api.events.ResizeableChanged; import net.runelite.api.events.ScriptCallbackEvent; import net.runelite.api.events.VarbitChanged; import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ChatColorConfig; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; import net.runelite.client.ui.JagexColors; import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.Text; @Singleton public class ChatMessageManager @@ -126,7 +126,8 @@ public class ChatMessageManager case PUBLICCHAT: case MODCHAT: { - boolean isFriend = client.isFriended(chatMessage.getName(), true) && !client.getLocalPlayer().getName().equals(chatMessage.getName()); + String sanitizedUsername = Text.removeTags(chatMessage.getName()); + boolean isFriend = client.isFriended(sanitizedUsername, true) && !client.getLocalPlayer().getName().equals(sanitizedUsername); if (isFriend) { @@ -571,18 +572,15 @@ public class ChatMessageManager return; } + final String formattedMessage = formatRuneLiteMessage(message.getRuneLiteFormattedMessage(), message.getType()); + // this updates chat cycle - client.addChatMessage( + final MessageNode line = client.addChatMessage( message.getType(), MoreObjects.firstNonNull(message.getName(), ""), - MoreObjects.firstNonNull(message.getValue(), message.getRuneLiteFormattedMessage()), + MoreObjects.firstNonNull(formattedMessage, message.getValue()), message.getSender()); - // Get last message from line buffer (the one we just added) - final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(message.getType().getType()); - final MessageNode[] lines = chatLineBuffer.getLines(); - final MessageNode line = lines[0]; - // Update the message with RuneLite additions line.setRuneLiteFormatMessage(message.getRuneLiteFormattedMessage()); @@ -590,34 +588,38 @@ public class ChatMessageManager { line.setTimestamp(message.getTimestamp()); } - - update(line); } - public void update(final MessageNode target) + /** + * Rebuild the message node message from the RuneLite format message + * + * @param messageNode message node + */ + public void update(final MessageNode messageNode) { - if (Strings.isNullOrEmpty(target.getRuneLiteFormatMessage())) + String message = formatRuneLiteMessage(messageNode.getRuneLiteFormatMessage(), messageNode.getType()); + if (message != null) { - return; + messageNode.setValue(message); + } + } + + private String formatRuneLiteMessage(String runeLiteFormatMessage, ChatMessageType type) + { + if (Strings.isNullOrEmpty(runeLiteFormatMessage)) + { + return null; } final boolean transparent = client.isResized() && transparencyVarbit != 0; - final Collection chatColors = colorCache.get(target.getType()); + final Collection chatColors = colorCache.get(type); - // If we do not have any colors cached, simply set clean message if (chatColors == null || chatColors.isEmpty()) { - target.setValue(target.getRuneLiteFormatMessage()); - return; + return runeLiteFormatMessage; } - target.setValue(recolorMessage(transparent, target.getRuneLiteFormatMessage(), target.getType())); - } - - private String recolorMessage(boolean transparent, String message, ChatMessageType messageType) - { - final Collection chatColors = colorCache.get(messageType); - final AtomicReference resultMessage = new AtomicReference<>(message); + final AtomicReference resultMessage = new AtomicReference<>(runeLiteFormatMessage); // Replace custom formatting with actual colors chatColors.stream() diff --git a/runelite-client/src/main/java/net/runelite/client/config/Button.java b/runelite-client/src/main/java/net/runelite/client/config/Button.java new file mode 100644 index 0000000000..591332ce05 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/config/Button.java @@ -0,0 +1,5 @@ +package net.runelite.client.config; + +public class Button +{ +} diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java index f76f32154a..a9a511cc08 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigItem.java @@ -24,8 +24,6 @@ */ package net.runelite.client.config; -import com.openosrs.client.OpenOSRS; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,11 +48,4 @@ public @interface ConfigItem boolean secret() default false; String section() default ""; - - /* - OpenOSRS Lazy Helpers tm - */ - Class enumClass() default OpenOSRS.class; - String unhide() default ""; - String hide() default ""; } diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 01d8ebfe1d..549ef0b123 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -152,6 +152,11 @@ public class ConfigManager scheduledExecutorService.scheduleWithFixedDelay(this::sendConfig, 30, 30, TimeUnit.SECONDS); } + public String getRSProfileKey() + { + return rsProfileKey; + } + public final void switchSession(AccountSession session) { // Ensure existing config is saved @@ -394,7 +399,7 @@ public class ConfigManager parent.mkdirs(); - File tempFile = new File(parent, RuneLite.DEFAULT_CONFIG_FILE.getName() + ".tmp"); + File tempFile = File.createTempFile("runelite", null, parent); try (FileOutputStream out = new FileOutputStream(tempFile)) { @@ -507,6 +512,11 @@ public class ConfigManager public void setConfiguration(String groupName, String profile, String key, String value) { + if (Strings.isNullOrEmpty(groupName) || Strings.isNullOrEmpty(key)) + { + throw new IllegalArgumentException(); + } + assert !key.startsWith(RSPROFILE_GROUP + "."); String wholeKey = getWholeKey(groupName, profile, key); String oldValue = (String) properties.setProperty(wholeKey, value); diff --git a/runelite-client/src/main/java/net/runelite/client/config/Keybind.java b/runelite-client/src/main/java/net/runelite/client/config/Keybind.java index e7e338a6b3..eba500b859 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/Keybind.java +++ b/runelite-client/src/main/java/net/runelite/client/config/Keybind.java @@ -159,7 +159,7 @@ public class Keybind String mod = ""; if (modifiers != 0) { - mod = getModifiersExText(modifiers); + mod = InputEvent.getModifiersExText(modifiers); } if (mod.isEmpty() && key.isEmpty()) @@ -177,33 +177,6 @@ public class Keybind return mod; } - public static String getModifiersExText(int modifiers) - { - StringBuilder buf = new StringBuilder(); - if ((modifiers & InputEvent.META_DOWN_MASK) != 0) - { - buf.append("Meta+"); - } - if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) - { - buf.append("Ctrl+"); - } - if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) - { - buf.append("Alt+"); - } - if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) - { - buf.append("Shift+"); - } - - if (buf.length() > 0) - { - buf.setLength(buf.length() - 1); // remove trailing '+' - } - return buf.toString(); - } - @Nullable public static Integer getModifierForKeyCode(int keyCode) { diff --git a/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java b/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java index 2340e306dd..4195484553 100644 --- a/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java +++ b/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java @@ -138,7 +138,10 @@ public class EventBus } final String preferredName = "on" + parameterClazz.getSimpleName(); - Preconditions.checkArgument(method.getName().equals(preferredName), "Subscribed method " + method + " should be named " + preferredName); + if (!method.getName().equals(preferredName)) + { + log.warn("Subscribed method {} should be named {}", method, preferredName); + } method.setAccessible(true); SubscriberMethod lambda = null; diff --git a/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClassLoader.java b/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClassLoader.java index 054a7779b5..5690c3d61e 100644 --- a/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClassLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClassLoader.java @@ -24,18 +24,32 @@ */ package net.runelite.client.externalplugins; +import java.lang.invoke.MethodHandles; import java.net.URL; import java.net.URLClassLoader; import lombok.Getter; +import lombok.Setter; +import net.runelite.client.util.ReflectUtil; -class ExternalPluginClassLoader extends URLClassLoader +class ExternalPluginClassLoader extends URLClassLoader implements ReflectUtil.PrivateLookupableClassLoader { @Getter private final ExternalPluginManifest manifest; + @Getter + @Setter + private MethodHandles.Lookup lookup; + ExternalPluginClassLoader(ExternalPluginManifest manifest, URL[] urls) { super(urls, ExternalPluginClassLoader.class.getClassLoader()); this.manifest = manifest; + ReflectUtil.installLookupHelper(this); + } + + @Override + public Class defineClass0(String name, byte[] b, int off, int len) throws ClassFormatError + { + return super.defineClass(name, b, off, len); } } diff --git a/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClient.java b/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClient.java index e6bda75032..ccf4b9fad3 100644 --- a/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClient.java +++ b/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginClient.java @@ -25,6 +25,7 @@ package net.runelite.client.externalplugins; import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; @@ -59,11 +60,13 @@ import okio.BufferedSource; public class ExternalPluginClient { private final OkHttpClient okHttpClient; + private final Gson gson; @Inject - private ExternalPluginClient(OkHttpClient okHttpClient) + private ExternalPluginClient(OkHttpClient okHttpClient, Gson gson) { this.okHttpClient = okHttpClient; + this.gson = gson; } public List downloadManifest() throws IOException, VerificationException @@ -94,7 +97,7 @@ public class ExternalPluginClient throw new VerificationException("Unable to verify external plugin manifest"); } - return RuneLiteAPI.GSON.fromJson(new String(data, StandardCharsets.UTF_8), + return gson.fromJson(new String(data, StandardCharsets.UTF_8), new TypeToken>() { }.getType()); @@ -156,7 +159,7 @@ public class ExternalPluginClient Request request = new Request.Builder() .url(url) - .post(RequestBody.create(RuneLiteAPI.JSON, RuneLiteAPI.GSON.toJson(plugins))) + .post(RequestBody.create(RuneLiteAPI.JSON, gson.toJson(plugins))) .build(); okHttpClient.newCall(request).enqueue(new Callback() @@ -190,7 +193,7 @@ public class ExternalPluginClient } // CHECKSTYLE:OFF - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(res.body().byteStream()), new TypeToken>(){}.getType()); + return gson.fromJson(new InputStreamReader(res.body().byteStream()), new TypeToken>(){}.getType()); // CHECKSTYLE:ON } catch (JsonSyntaxException ex) diff --git a/runelite-client/src/main/java/net/runelite/client/game/FishingSpot.java b/runelite-client/src/main/java/net/runelite/client/game/FishingSpot.java index 14d7b96316..a3de9412a2 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/FishingSpot.java +++ b/runelite-client/src/main/java/net/runelite/client/game/FishingSpot.java @@ -29,6 +29,9 @@ import java.util.Map; import lombok.Getter; import net.runelite.api.ItemID; import static net.runelite.api.NpcID.FISHING_SPOT; +import static net.runelite.api.NpcID.FISHING_SPOT_10513; +import static net.runelite.api.NpcID.FISHING_SPOT_10514; +import static net.runelite.api.NpcID.FISHING_SPOT_10515; import static net.runelite.api.NpcID.FISHING_SPOT_1497; import static net.runelite.api.NpcID.FISHING_SPOT_1498; import static net.runelite.api.NpcID.FISHING_SPOT_1499; @@ -132,13 +135,14 @@ public enum FishingSpot FISHING_SPOT_1525, FISHING_SPOT_1528, FISHING_SPOT_1530, FISHING_SPOT_1544, FISHING_SPOT_3913, FISHING_SPOT_7155, FISHING_SPOT_7459, FISHING_SPOT_7462, FISHING_SPOT_7467, - FISHING_SPOT_7469, FISHING_SPOT_7947 + FISHING_SPOT_7469, FISHING_SPOT_7947, FISHING_SPOT_10513 ), LOBSTER("Lobster, Swordfish, Tuna", "Lobster", ItemID.RAW_LOBSTER, FISHING_SPOT_1510, FISHING_SPOT_1519, FISHING_SPOT_1522, FISHING_SPOT_3914, FISHING_SPOT_5820, FISHING_SPOT_7199, FISHING_SPOT_7460, FISHING_SPOT_7465, FISHING_SPOT_7470, - FISHING_SPOT_7946, FISHING_SPOT_9173, FISHING_SPOT_9174 + FISHING_SPOT_7946, FISHING_SPOT_9173, FISHING_SPOT_9174, + FISHING_SPOT_10515 ), SHARK("Shark, Bass", "Shark", ItemID.RAW_SHARK, FISHING_SPOT_1511, FISHING_SPOT_1520, FISHING_SPOT_3419, @@ -146,7 +150,7 @@ public enum FishingSpot FISHING_SPOT_5233, FISHING_SPOT_5234, FISHING_SPOT_5821, FISHING_SPOT_7200, FISHING_SPOT_7461, FISHING_SPOT_7466, FISHING_SPOT_8525, FISHING_SPOT_8526, FISHING_SPOT_8527, - FISHING_SPOT_9171, FISHING_SPOT_9172 + FISHING_SPOT_9171, FISHING_SPOT_9172, FISHING_SPOT_10514 ), MONKFISH("Monkfish", ItemID.RAW_MONKFISH, FISHING_SPOT_4316 diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPf4jPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java similarity index 95% rename from runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPf4jPluginManager.java rename to runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java index e151ba3df2..30abbfa8eb 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPf4jPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPf4jPluginManager.java @@ -1,4 +1,4 @@ -package com.openosrs.client.plugins; +package net.runelite.client.plugins; import com.openosrs.client.OpenOSRS; import java.io.Closeable; @@ -33,11 +33,11 @@ import org.pf4j.PluginWrapper; import org.pf4j.RuntimeMode; @Slf4j -class ExternalPf4jPluginManager extends DefaultPluginManager +class OPRSExternalPf4jPluginManager extends DefaultPluginManager { - private final ExternalPluginManager externalPluginManager; + private final OPRSExternalPluginManager externalPluginManager; - public ExternalPf4jPluginManager(ExternalPluginManager externalPluginManager) + public OPRSExternalPf4jPluginManager(OPRSExternalPluginManager externalPluginManager) { super(OpenOSRS.EXTERNALPLUGIN_DIR.toPath()); this.externalPluginManager = externalPluginManager; @@ -55,7 +55,7 @@ class ExternalPf4jPluginManager extends DefaultPluginManager // The superclass performs a find, which is slow in development mode since we're pointing // at a sources directory, which can have a lot of files. The external plugin template // will always output the manifest at the following location, so we can hardcode this path. - return pluginPath.resolve(ExternalPluginManager.DEVELOPMENT_MANIFEST_PATH); + return pluginPath.resolve(OPRSExternalPluginManager.DEVELOPMENT_MANIFEST_PATH); } return super.getManifestPath(pluginPath); @@ -78,7 +78,7 @@ class ExternalPf4jPluginManager extends DefaultPluginManager protected PluginLoader createPluginLoader() { return new CompoundPluginLoader() - .add(new BasePluginLoader(this, new ExternalPluginClasspath()), this::isDevelopment) + .add(new BasePluginLoader(this, new OPRSExternalPluginClasspath()), this::isDevelopment) .add(new JarPluginLoader(this), this::isNotDevelopment); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginClasspath.java b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginClasspath.java new file mode 100644 index 0000000000..9415590a95 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginClasspath.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020, SwazRGB + * 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; + +import org.pf4j.DevelopmentPluginClasspath; + +class OPRSExternalPluginClasspath extends DevelopmentPluginClasspath +{ + static final String GRADLE_DEPS_PATH = "build/deps"; + + OPRSExternalPluginClasspath() + { + addJarsDirectories(GRADLE_DEPS_PATH); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginFileFilter.java b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginFileFilter.java new file mode 100644 index 0000000000..c9f44068e9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginFileFilter.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020, SwazRGB + * 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; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.List; + +/** + * Determines whether a {@link File} is an external plugin folder. To be considered a plugin a folder must: + *

+ * * Must not be a blacklisted name + * * Have a {@code .gradle.kts} file in the root named after the folder + * * Have a {@code MANIFEST.MF} located at {@code build/tmp/jar/MANIFEST.MF} + */ +public class OPRSExternalPluginFileFilter implements FileFilter +{ + private static final List blacklist = Arrays.asList( + ".git", + "build", + "target", + "release" + ); + + private static final List buildFiles = Arrays.asList( + "%s.gradle.kts", + "%s.gradle" + ); + + @Override + public boolean accept(File pathName) + { + // Check if this path looks like a plugin development directory + if (!pathName.isDirectory()) + { + return false; + } + + String dirName = pathName.getName(); + if (blacklist.contains(dirName)) + { + return false; + } + + // Check if the plugin directory has a MANIFEST.MF which si required for loading + if (!new File(pathName, OPRSExternalPluginManager.DEVELOPMENT_MANIFEST_PATH).exists()) + { + return false; + } + + // By convention plugins their directory is $name and they have a $name.gradle.kts or $name.gradle file in their root + for (String buildFile : buildFiles) + { + if (new File(pathName, String.format(buildFile, dirName)).exists()) + { + return true; + } + } + + return false; + } +} diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java similarity index 92% rename from runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginManager.java rename to runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java index add932f725..dd6c86d1ee 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/ExternalPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/OPRSExternalPluginManager.java @@ -22,7 +22,7 @@ * (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 com.openosrs.client.plugins; +package net.runelite.client.plugins; import com.google.common.collect.Lists; import com.google.common.graph.GraphBuilder; @@ -34,17 +34,17 @@ import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import static com.openosrs.client.OpenOSRS.EXTERNALPLUGIN_DIR; -import static com.openosrs.client.OpenOSRS.SYSTEM_VERSION; +import static com.openosrs.client.OpenOSRS.SYSTEM_API_VERSION; import com.openosrs.client.config.OpenOSRSConfig; -import com.openosrs.client.events.ExternalPluginChanged; -import com.openosrs.client.events.ExternalRepositoryChanged; +import com.openosrs.client.events.OPRSPluginChanged; +import com.openosrs.client.events.OPRSRepositoryChanged; import com.openosrs.client.ui.OpenOSRSSplashScreen; import com.openosrs.client.util.Groups; -import com.openosrs.client.util.MiscUtils; -import com.openosrs.client.util.SwingUtil; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -75,10 +75,9 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.eventbus.EventBus; import net.runelite.client.events.ConfigChanged; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.PluginInstantiationException; -import net.runelite.client.plugins.PluginManager; +import net.runelite.client.events.ExternalPluginsChanged; import net.runelite.client.ui.ClientUI; +import net.runelite.client.util.SwingUtil; import org.jgroups.Message; import org.pf4j.DefaultPluginManager; import org.pf4j.DependencyResolver; @@ -92,13 +91,14 @@ import org.pf4j.update.UpdateRepository; @Slf4j @Singleton -public class ExternalPluginManager +public class OPRSExternalPluginManager { - public static final String DEFAULT_PLUGIN_REPOS = "OpenOSRS:https://raw.githubusercontent.com/zeruth/runelite-plugins-release/master/"; + public static final String DEFAULT_PLUGIN_REPOS = ""; static final String DEVELOPMENT_MANIFEST_PATH = "build/tmp/jar/MANIFEST.MF"; public static ArrayList pluginClassLoaders = new ArrayList<>(); private final PluginManager runelitePluginManager; + @Getter(AccessLevel.PUBLIC) private org.pf4j.PluginManager externalPluginManager; @Getter(AccessLevel.PUBLIC) private final List repositories = new ArrayList<>(); @@ -115,7 +115,7 @@ public class ExternalPluginManager private final boolean safeMode; @Inject - public ExternalPluginManager( + public OPRSExternalPluginManager( @Named("safeMode") final boolean safeMode, PluginManager pluginManager, OpenOSRSConfig openOSRSConfig, @@ -143,8 +143,8 @@ public class ExternalPluginManager private void initPluginManager() { - externalPluginManager = new ExternalPf4jPluginManager(this); - externalPluginManager.setSystemVersion(SYSTEM_VERSION); + externalPluginManager = new OPRSExternalPf4jPluginManager(this); + externalPluginManager.setSystemVersion(SYSTEM_API_VERSION); } public boolean doesGhRepoExist(String owner, String name) @@ -327,30 +327,24 @@ public class ExternalPluginManager { DefaultUpdateRepository respository = new DefaultUpdateRepository(key, url); updateManager.addRepository(respository); - eventBus.post(new ExternalRepositoryChanged(key, true)); + eventBus.post(new OPRSRepositoryChanged(key, true)); saveConfig(); } public void removeRepository(String owner) { updateManager.removeRepository(owner); - eventBus.post(new ExternalRepositoryChanged(owner, false)); + eventBus.post(new OPRSRepositoryChanged(owner, false)); saveConfig(); } private void saveConfig() { - StringBuilder config = new StringBuilder(); + String config = updateManager.getRepositories().stream() + .map(r -> r.getId() + "|" + urlToStringEncoded(r.getUrl())) + .collect(Collectors.joining(";")); - for (UpdateRepository repository : updateManager.getRepositories()) - { - config.append(repository.getId()); - config.append("|"); - config.append(MiscUtils.urlToStringEncoded(repository.getUrl())); - config.append(";"); - } - config.deleteCharAt(config.lastIndexOf(";")); - openOSRSConfig.setExternalRepositories(config.toString()); + openOSRSConfig.setExternalRepositories(config); } public void setWarning(boolean val) @@ -531,8 +525,7 @@ public class ExternalPluginManager } @SuppressWarnings("unchecked") - private Plugin instantiate(List scannedPlugins, Class clazz, boolean init, boolean initConfig) - throws PluginInstantiationException + private Plugin instantiate(List scannedPlugins, Class clazz, boolean init, boolean initConfig) throws PluginInstantiationException { net.runelite.client.plugins.PluginDependency[] pluginDependencies = clazz.getAnnotationsByType(net.runelite.client.plugins.PluginDependency.class); @@ -629,7 +622,7 @@ public class ExternalPluginManager { runelitePluginManager.add(plugin); runelitePluginManager.startPlugin(plugin); - eventBus.post(new ExternalPluginChanged(pluginsMap.get(plugin.getClass().getSimpleName()), + eventBus.post(new OPRSPluginChanged(pluginsMap.get(plugin.getClass().getSimpleName()), plugin, true)); } catch (PluginInstantiationException e) @@ -796,7 +789,7 @@ public class ExternalPluginManager runelitePluginManager.remove(plugin); pluginClassLoaders.remove(plugin.getClass().getClassLoader()); - eventBus.post(new ExternalPluginChanged(pluginId, plugin, false)); + eventBus.post(new OPRSPluginChanged(pluginId, plugin, false)); return pluginWrapper.getPluginPath(); } @@ -820,6 +813,8 @@ public class ExternalPluginManager groups.broadcastSring("STARTEXTERNAL;" + pluginId); scanAndInstantiate(loadPlugin(pluginId), true, false); + ExternalPluginsChanged event = new ExternalPluginsChanged(null); + eventBus.post(event); return true; } @@ -853,9 +848,9 @@ public class ExternalPluginManager } updateManager.installPlugin(pluginId, null); - scanAndInstantiate(loadPlugin(pluginId), true, true); - + ExternalPluginsChanged event = new ExternalPluginsChanged(null); + eventBus.post(event); groups.broadcastSring("STARTEXTERNAL;" + pluginId); } catch (DependencyResolver.DependenciesNotFoundException ex) @@ -1069,4 +1064,27 @@ public class ExternalPluginManager } } + /** + * Mostly stolen from {@link java.net.URLStreamHandler#toExternalForm(URL)} + * + * @param url URL to encode + * @return URL, with path, query and ref encoded + */ + private static String urlToStringEncoded(URL url) + { + String s; + String path = url.getPath() != null ? Stream.of(url.getPath().split("/")) + .map(s2 -> URLEncoder.encode(s2, StandardCharsets.UTF_8)).collect(Collectors.joining("/")) : ""; + return url.getProtocol() + + ':' + + (((s = url.getAuthority()) != null && s.length() > 0) ? "//" + s : "") + + (path) + + (((s = url.getQuery()) != null) ? '?' + urlEncode(s) : "") + + (((s = url.getRef()) != null) ? '#' + urlEncode(s) : ""); + } + + private static String urlEncode(String s) + { + return URLEncoder.encode(s, StandardCharsets.UTF_8); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java index a36a1f40e6..ac68df9b48 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/Plugin.java @@ -27,9 +27,12 @@ package net.runelite.client.plugins; import com.google.inject.Binder; import com.google.inject.Injector; import com.google.inject.Module; +import lombok.Getter; +import org.pf4j.ExtensionPoint; -public abstract class Plugin implements Module +public abstract class Plugin implements Module, ExtensionPoint { + @Getter protected Injector injector; @Override @@ -49,11 +52,6 @@ public abstract class Plugin implements Module { } - public final Injector getInjector() - { - return injector; - } - public String getName() { return getClass().getAnnotation(PluginDescriptor.class).name(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index b4d5934f2f..8aedb0f158 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -38,13 +38,7 @@ import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import java.io.IOException; -import java.lang.invoke.CallSite; -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -72,12 +66,9 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.PluginChanged; import net.runelite.client.events.SessionClose; import net.runelite.client.events.SessionOpen; -import net.runelite.client.task.Schedule; -import net.runelite.client.task.ScheduledMethod; import net.runelite.client.task.Scheduler; import net.runelite.client.ui.SplashScreen; import net.runelite.client.util.GameEventManager; -import net.runelite.client.util.ReflectUtil; @Singleton @Slf4j @@ -87,7 +78,6 @@ public class PluginManager * Base package where the core plugins are */ private static final String PLUGIN_PACKAGE = "net.runelite.client.plugins"; - private static final String OPENOSRS_PACKAGE = "com.openosrs.client.plugins"; private final boolean developerMode; private final boolean safeMode; @@ -164,8 +154,21 @@ public class PluginManager { try { - final Injector injector = plugin.getInjector(); - + Injector injector = plugin.getInjector(); + if (injector == null) + { + // Create injector for the module + Module pluginModule = (Binder binder) -> + { + // Since the plugin itself is a module, it won't bind itself, so we'll bind it here + binder.bind((Class) plugin.getClass()).toInstance(plugin); + binder.install(plugin); + }; + Injector pluginInjector = RuneLite.getInjector().createChildInjector(pluginModule); + pluginInjector.injectMembers(plugin); + plugin.injector = pluginInjector; + injector = pluginInjector; + } for (Key key : injector.getBindings().keySet()) { Class type = key.getTypeLiteral().getRawType(); @@ -291,10 +294,6 @@ public class PluginManager .map(ClassInfo::load) .collect(Collectors.toList()); - plugins.addAll(classPath.getTopLevelClassesRecursive(OPENOSRS_PACKAGE).stream() - .map(ClassInfo::load) - .collect(Collectors.toList())); - loadPlugins(plugins, (loaded, total) -> SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, total, false)); } @@ -628,59 +627,14 @@ public class PluginManager private void schedule(Plugin plugin) { - for (Method method : plugin.getClass().getMethods()) - { - Schedule schedule = method.getAnnotation(Schedule.class); - - if (schedule == null) - { - continue; - } - - Runnable runnable = null; - try - { - final Class clazz = method.getDeclaringClass(); - final MethodHandles.Lookup caller = ReflectUtil.privateLookupIn(clazz); - final MethodType subscription = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); - final MethodHandle target = caller.findVirtual(clazz, method.getName(), subscription); - final CallSite site = LambdaMetafactory.metafactory( - caller, - "run", - MethodType.methodType(Runnable.class, clazz), - subscription, - target, - subscription); - - final MethodHandle factory = site.getTarget(); - runnable = (Runnable) factory.bindTo(plugin).invokeExact(); - } - catch (Throwable e) - { - log.warn("Unable to create lambda for method {}", method, e); - } - - ScheduledMethod scheduledMethod = new ScheduledMethod(schedule, method, plugin, runnable); - log.debug("Scheduled task {}", scheduledMethod); - - scheduler.addScheduledMethod(scheduledMethod); - } + // note to devs: this method will almost certainly merge conflict in the future, just apply the changes in the scheduler instead + scheduler.registerObject(plugin); } private void unschedule(Plugin plugin) { - List methods = new ArrayList<>(scheduler.getScheduledMethods()); - - for (ScheduledMethod method : methods) - { - if (method.getObject() != plugin) - { - continue; - } - - log.debug("Removing scheduled task {}", method); - scheduler.removeScheduledMethod(method); - } + // note to devs: this method will almost certainly merge conflict in the future, just apply the changes in the scheduler instead + scheduler.unregisterObject(plugin); } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java new file mode 100644 index 0000000000..74926bc292 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTag.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, Adam + * 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.banktags; + +public interface BankTag +{ + boolean contains(int itemId); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java index 1cffe75379..4e56358252 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java @@ -75,13 +75,11 @@ import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.input.MouseManager; import net.runelite.client.input.MouseWheelListener; import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDependency; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.banktags.tabs.TabInterface; import static net.runelite.client.plugins.banktags.tabs.TabInterface.FILTERED_CHARS; import net.runelite.client.plugins.banktags.tabs.TabSprites; import net.runelite.client.plugins.banktags.tabs.TagTab; -import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.util.Text; @PluginDescriptor( @@ -89,7 +87,6 @@ import net.runelite.client.util.Text; description = "Enable tagging of bank items and searching of bank tags", tags = {"searching", "tagging"} ) -@PluginDependency(ClueScrollPlugin.class) public class BankTagsPlugin extends Plugin implements MouseWheelListener { public static final String CONFIG_GROUP = "banktags"; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java index fe697034d0..3cfac27242 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java @@ -27,24 +27,17 @@ package net.runelite.client.plugins.banktags; import com.google.common.base.Strings; import java.util.Collection; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; -import net.runelite.api.ItemID; import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemVariationMapping; import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; -import net.runelite.client.plugins.cluescrolls.ClueScrollService; -import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; -import net.runelite.client.plugins.cluescrolls.clues.CoordinateClue; -import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; -import net.runelite.client.plugins.cluescrolls.clues.FairyRingClue; -import net.runelite.client.plugins.cluescrolls.clues.HotColdClue; -import net.runelite.client.plugins.cluescrolls.clues.MapClue; -import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; import net.runelite.client.util.Text; @Singleton @@ -53,17 +46,15 @@ public class TagManager static final String ITEM_KEY_PREFIX = "item_"; private final ConfigManager configManager; private final ItemManager itemManager; - private final ClueScrollService clueScrollService; + private final Map customTags = new HashMap<>(); @Inject private TagManager( final ItemManager itemManager, - final ConfigManager configManager, - final ClueScrollService clueScrollService) + final ConfigManager configManager) { this.itemManager = itemManager; this.configManager = configManager; - this.clueScrollService = clueScrollService; } String getTagString(int itemId, boolean variation) @@ -123,7 +114,8 @@ public class TagManager boolean findTag(int itemId, String search) { - if (search.equals("clue") && testClue(itemId)) + BankTag bankTag = customTags.get(search); + if (bankTag != null && bankTag.contains(itemId)) { return true; } @@ -194,38 +186,13 @@ public class TagManager return itemId; } - private boolean testClue(int itemId) + public void registerTag(String name, BankTag tag) { - ClueScroll c = clueScrollService.getClue(); + customTags.put(name, tag); + } - if (c == null) - { - return false; - } - - if (c instanceof EmoteClue) - { - EmoteClue emote = (EmoteClue) c; - - for (ItemRequirement ir : emote.getItemRequirements()) - { - if (ir.fulfilledBy(itemId)) - { - return true; - } - } - } - else if (c instanceof CoordinateClue || c instanceof HotColdClue || c instanceof FairyRingClue) - { - return itemId == ItemID.SPADE; - } - else if (c instanceof MapClue) - { - MapClue mapClue = (MapClue) c; - - return mapClue.getObjectId() == -1 && itemId == ItemID.SPADE; - } - - return false; + public void unregisterTag(String name) + { + customTags.remove(name); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsBrotherSlainOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsBrotherSlainOverlay.java index c7cdef0058..4e4eba0175 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsBrotherSlainOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/barrows/BarrowsBrotherSlainOverlay.java @@ -34,6 +34,7 @@ import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; import net.runelite.api.Varbits; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.ui.FontManager; import static net.runelite.client.ui.overlay.OverlayManager.OPTION_CONFIGURE; import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayPanel; @@ -82,6 +83,7 @@ public class BarrowsBrotherSlainOverlay extends OverlayPanel panelComponent.getChildren().add(LineComponent.builder() .left(brother.getName()) .right(slain) + .rightFont(FontManager.getDefaultFont()) .rightColor(brotherSlain ? Color.GREEN : Color.RED) .build()); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java index 4d426ae9b6..1f9aeeaa25 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonPlugin.java @@ -304,7 +304,8 @@ public class CannonPlugin extends Plugin } if (event.getMessage().contains("You pick up the cannon") - || event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!")) + || event.getMessage().contains("Your cannon has decayed. Speak to Nulodion to get a new one!") + || event.getMessage().contains("Your cannon has been destroyed!")) { cannonPlaced = false; cballsLeft = 0; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonSpots.java b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonSpots.java index f5b6f4a4a2..4596cd4e35 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonSpots.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonSpots.java @@ -49,7 +49,7 @@ enum CannonSpots DUST_DEVIL(new WorldPoint(3218, 9366, 0)), EARTH_WARRIOR(new WorldPoint(3120, 9987, 0)), ELDER_CHAOS_DRUID(new WorldPoint(3237, 3622, 0)), - ELVES(new WorldPoint(2044, 4635, 0), new WorldPoint(3278, 6098, 0)), + ELVES(new WorldPoint(3278, 6098, 0)), FIRE_GIANTS(new WorldPoint(2393, 9782, 0), new WorldPoint(2412, 9776, 0), new WorldPoint(2401, 9780, 0), new WorldPoint(3047, 10340, 0)), GREATER_DEMONS(new WorldPoint(1435, 10086, 2), new WorldPoint(3224, 10132, 0)), GREEN_DRAGON(new WorldPoint(3225, 10068, 0)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index 8923f1684a..8c06945a66 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -666,7 +666,7 @@ public class ChatCommandsPlugin extends Plugin .append(ChatColorType.NORMAL) .append(" kill count: ") .append(ChatColorType.HIGHLIGHT) - .append(Integer.toString(kc)) + .append(String.format("%,d", kc)) .build(); log.debug("Setting response {}", response); @@ -748,15 +748,15 @@ public class ChatCommandsPlugin extends Plugin .append(ChatColorType.NORMAL) .append("Duel Arena wins: ") .append(ChatColorType.HIGHLIGHT) - .append(Integer.toString(wins)) + .append(String.format("%,d", wins)) .append(ChatColorType.NORMAL) .append(" losses: ") .append(ChatColorType.HIGHLIGHT) - .append(Integer.toString(losses)) + .append(String.format("%,d", losses)) .append(ChatColorType.NORMAL) .append(" streak: ") .append(ChatColorType.HIGHLIGHT) - .append(Integer.toString((winningStreak != 0 ? winningStreak : -losingStreak))) + .append(String.format("%,d", winningStreak != 0 ? winningStreak : -losingStreak)) .build(); log.debug("Setting response {}", response); @@ -957,7 +957,7 @@ public class ChatCommandsPlugin extends Plugin .append(ChatColorType.NORMAL) .append("Barbarian Assault High-level gambles: ") .append(ChatColorType.HIGHLIGHT) - .append(Integer.toString(gc)) + .append(String.format("%,d", gc)) .build(); log.debug("Setting response {}", response); @@ -1420,7 +1420,7 @@ public class ChatCommandsPlugin extends Plugin .append(ChatColorType.NORMAL) .append("Clue scroll (" + level + ")").append(": ") .append(ChatColorType.HIGHLIGHT) - .append(Integer.toString(quantity)); + .append(String.format("%,d", quantity)); if (rank != -1) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java index 8305c40bde..cc94cfc801 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java @@ -56,8 +56,6 @@ import net.runelite.api.widgets.WidgetInfo; import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; import net.runelite.client.callback.ClientThread; -import net.runelite.client.chat.ChatMessageManager; -import net.runelite.client.chat.QueuedMessage; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.input.KeyListener; @@ -82,7 +80,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB; private static final int FRIENDS_MAX_SIZE = 5; - private Queue messageQueue; + private Queue messageQueue; private Deque friends; private String currentMessage = null; @@ -99,9 +97,6 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener @Inject private KeyManager keyManager; - @Inject - private ChatMessageManager chatMessageManager; - @Provides ChatHistoryConfig getConfig(ConfigManager configManager) { @@ -111,6 +106,9 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener @Override protected void startUp() { + // The client reuses MessageNodes after 100 chat messages of + // the same type, so this must be 100 (or maybe a map of + // size 100 evicting queues) messageQueue = EvictingQueue.create(100); friends = new ArrayDeque<>(FRIENDS_MAX_SIZE + 1); keyManager.registerKeyListener(this); @@ -140,11 +138,16 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener return; } - QueuedMessage queuedMessage; - - while ((queuedMessage = messageQueue.poll()) != null) + for (MessageNode queuedMessage : messageQueue) { - chatMessageManager.queue(queuedMessage); + final MessageNode node = client.addChatMessage( + queuedMessage.getType(), + queuedMessage.getName(), + queuedMessage.getValue(), + queuedMessage.getSender(), + false); + node.setRuneLiteFormatMessage(queuedMessage.getRuneLiteFormatMessage()); + node.setTimestamp(queuedMessage.getTimestamp()); } return; @@ -171,19 +174,7 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener case MODCHAT: case FRIENDSCHAT: case CONSOLE: - final QueuedMessage queuedMessage = QueuedMessage.builder() - .type(chatMessageType) - .name(chatMessage.getName()) - .sender(chatMessage.getSender()) - .value(nbsp(chatMessage.getMessage())) - .runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage())) - .timestamp(chatMessage.getTimestamp()) - .build(); - - if (!messageQueue.contains(queuedMessage)) - { - messageQueue.offer(queuedMessage); - } + messageQueue.offer(chatMessage.getMessageNode()); } } @@ -348,21 +339,6 @@ public class ChatHistoryPlugin extends Plugin implements KeyListener clearMessageQueue(tab); } - /** - * Small hack to prevent plugins checking for specific messages to match - * @param message message - * @return message with nbsp - */ - private static String nbsp(final String message) - { - if (message != null) - { - return message.replace(' ', '\u00A0'); - } - - return null; - } - @Override public void keyPressed(KeyEvent e) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsConfig.java index 80044fc340..8c254a2c2a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsConfig.java @@ -108,4 +108,15 @@ public interface ChatNotificationsConfig extends Config { return false; } + + @ConfigItem( + position = 7, + keyName = "notifyOnPM", + name = "Notify on private message", + description = "Notifies you whenever you receive a private message" + ) + default boolean notifyOnPM() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java index afe574d3c6..092b8cf376 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatnotifications/ChatNotificationsPlugin.java @@ -165,6 +165,13 @@ public class ChatNotificationsPlugin extends Plugin notifier.notify(Text.removeFormattingTags(broadcast)); } break; + case PRIVATECHAT: + case MODPRIVATECHAT: + if (config.notifyOnPM()) + { + notifier.notify(Text.removeTags(chatMessage.getName()) + ": " + chatMessage.getMessage()); + } + break; case CONSOLE: // Don't notify for notification messages if (chatMessage.getName().equals(runeliteTitle)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java index 06dee6142a..d33a0e6b29 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java @@ -96,7 +96,10 @@ import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.OverlayMenuClicked; 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.banktags.BankTagsPlugin; +import net.runelite.client.plugins.banktags.TagManager; import net.runelite.client.plugins.cluescrolls.clues.AnagramClue; import net.runelite.client.plugins.cluescrolls.clues.BeginnerMapClue; import net.runelite.client.plugins.cluescrolls.clues.CipherClue; @@ -117,6 +120,7 @@ import net.runelite.client.plugins.cluescrolls.clues.ObjectClueScroll; import net.runelite.client.plugins.cluescrolls.clues.SkillChallengeClue; import net.runelite.client.plugins.cluescrolls.clues.TextClueScroll; import net.runelite.client.plugins.cluescrolls.clues.ThreeStepCrypticClue; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayUtil; @@ -131,6 +135,7 @@ import org.apache.commons.lang3.ArrayUtils; description = "Show answers to clue scroll riddles, anagrams, ciphers, and cryptic clues", tags = {"arrow", "hints", "world", "map", "coordinates", "emotes"} ) +@PluginDependency(BankTagsPlugin.class) @Slf4j public class ClueScrollPlugin extends Plugin { @@ -144,6 +149,7 @@ public class ClueScrollPlugin extends Plugin 13150, 9011, 13151, 9012 }; + private static final String CLUE_TAG_NAME = "clue"; @Getter private ClueScroll clue; @@ -191,6 +197,9 @@ public class ClueScrollPlugin extends Plugin @Inject private WorldMapPointManager worldMapPointManager; + @Inject + private TagManager tagManager; + @Inject @Named("developerMode") boolean developerMode; @@ -227,11 +236,13 @@ public class ClueScrollPlugin extends Plugin overlayManager.add(clueScrollEmoteOverlay); overlayManager.add(clueScrollWorldOverlay); overlayManager.add(clueScrollMusicOverlay); + tagManager.registerTag(CLUE_TAG_NAME, this::testClueTag); } @Override protected void shutDown() throws Exception { + tagManager.unregisterTag(CLUE_TAG_NAME); overlayManager.remove(clueScrollOverlay); overlayManager.remove(clueScrollEmoteOverlay); overlayManager.remove(clueScrollWorldOverlay); @@ -484,6 +495,10 @@ public class ClueScrollPlugin extends Plugin { resetClue(true); } + else if (state == GameState.HOPPING) + { + namedObjectCheckThisTick = true; + } } @Subscribe @@ -550,6 +565,7 @@ public class ClueScrollPlugin extends Plugin } // Load the current plane's tiles if a tick has elapsed since the player has changed planes + // or upon reaching a logged in state after hopping worlds if (namedObjectCheckThisTick) { namedObjectCheckThisTick = false; @@ -569,7 +585,7 @@ public class ClueScrollPlugin extends Plugin if (chatDialogClueItem != null && (chatDialogClueItem.getItemId() == ItemID.CLUE_SCROLL_BEGINNER || chatDialogClueItem.getItemId() == ItemID.CLUE_SCROLL_MASTER)) { - resetClue(true); + resetClue(false); } final Widget clueScrollText = client.getWidget(WidgetInfo.CLUE_SCROLL_TEXT); @@ -1109,4 +1125,38 @@ public class ClueScrollPlugin extends Plugin } return worldPoint; } + + private boolean testClueTag(int itemId) + { + ClueScroll c = clue; + if (c == null) + { + return false; + } + + if (c instanceof EmoteClue) + { + EmoteClue emote = (EmoteClue) c; + + for (ItemRequirement ir : emote.getItemRequirements()) + { + if (ir.fulfilledBy(itemId)) + { + return true; + } + } + } + else if (c instanceof CoordinateClue || c instanceof HotColdClue || c instanceof FairyRingClue) + { + return itemId == ItemID.SPADE; + } + else if (c instanceof MapClue) + { + MapClue mapClue = (MapClue) c; + + return mapClue.getObjectId() == -1 && itemId == ItemID.SPADE; + } + + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java index 8d9a462118..5c0e093b6c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java @@ -205,6 +205,8 @@ public class CoordinateClue extends ClueScroll implements TextClueScroll, Locati .put(new WorldPoint(2484, 4016, 0), new CoordinateClueInfo("Northeast corner of the Island of Stone.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) .put(new WorldPoint(2222, 3331, 0), new CoordinateClueInfo("Prifddinas, west of the Tower of Voices", ARMADYLIAN_OR_BANDOSIAN_GUARD)) .put(new WorldPoint(3560, 3987, 0), new CoordinateClueInfo("Lithkren. Digsite pendant teleport if unlocked, otherwise take rowboat from west of Mushroom Meadow Mushtree.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2318, 2954, 0), new CoordinateClueInfo("North-east corner of the Isle of Souls.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) + .put(new WorldPoint(2094, 2889, 0), new CoordinateClueInfo("West side of the Isle of Souls.", ARMADYLIAN_OR_BANDOSIAN_GUARD)) // Master .put(new WorldPoint(2178, 3209, 0), new CoordinateClueInfo("South of Iorwerth Camp.", BRASSICAN_MAGE)) .put(new WorldPoint(2155, 3100, 0), new CoordinateClueInfo("South of Port Tyras (BJS if 76 Agility).", BRASSICAN_MAGE)) @@ -227,7 +229,7 @@ public class CoordinateClue extends ClueScroll implements TextClueScroll, Locati .put(new WorldPoint(2202, 3825, 0), new CoordinateClueInfo("Pirates' Cove, between Lunar Isle and Rellekka.", ANCIENT_WIZARDS)) .put(new WorldPoint(1761, 3853, 0), new CoordinateClueInfo("Arceuus essence mine (CIS).", BRASSICAN_MAGE)) .put(new WorldPoint(2090, 3863, 0), new CoordinateClueInfo("South of Lunar Isle, west of Astral altar.", ANCIENT_WIZARDS)) - .put(new WorldPoint(1442, 3878, 0), new CoordinateClueInfo("Sulphur Mine.", BRASSICAN_MAGE)) + .put(new WorldPoint(1442, 3878, 0), new CoordinateClueInfo("Northern area of the Lovakengj Sulphur Mine. Facemask or Slayer Helmet recommended.", BRASSICAN_MAGE)) .put(new WorldPoint(3380, 3929, 0), new CoordinateClueInfo("Wilderness. Near Volcano.", ANCIENT_WIZARDS)) .put(new WorldPoint(3188, 3939, 0), new CoordinateClueInfo("Wilderness. Resource Area.", BRASSICAN_MAGE)) .put(new WorldPoint(3304, 3941, 0), new CoordinateClueInfo("Wilderness. East of Rogues' Castle.", ANCIENT_WIZARDS)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java index b59be0da14..ab3ac38982 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java @@ -45,6 +45,7 @@ import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import static net.runelite.client.plugins.cluescrolls.clues.Enemy.*; import net.runelite.client.plugins.cluescrolls.clues.emote.Emote; import static net.runelite.client.plugins.cluescrolls.clues.emote.Emote.*; import static net.runelite.client.plugins.cluescrolls.clues.emote.Emote.BULL_ROARER; @@ -53,7 +54,7 @@ import static net.runelite.client.plugins.cluescrolls.clues.emote.STASHUnit.*; import static net.runelite.client.plugins.cluescrolls.clues.emote.STASHUnit.SHANTAY_PASS; import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; import static net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirements.*; -import static net.runelite.client.plugins.cluescrolls.clues.Enemy.*; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.overlay.OverlayUtil; import net.runelite.client.ui.overlay.components.LineComponent; import net.runelite.client.ui.overlay.components.PanelComponent; @@ -258,6 +259,7 @@ public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClu panelComponent.getChildren().add(LineComponent.builder() .left("STASH Unit:") .right(stashUnitBuilt ? UNICODE_CHECK_MARK : UNICODE_BALLOT_X) + .rightFont(FontManager.getDefaultFont()) .rightColor(stashUnitBuilt ? Color.GREEN : Color.RED) .build()); } @@ -292,6 +294,7 @@ public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClu .left(requirement.getCollectiveName(client)) .leftColor(TITLED_CONTENT_COLOR) .right(combinedFulfilled ? UNICODE_CHECK_MARK : UNICODE_BALLOT_X) + .rightFont(FontManager.getDefaultFont()) .rightColor(equipmentFulfilled ? Color.GREEN : (combinedFulfilled ? Color.ORANGE : Color.RED)) .build()); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java index 16974ffd16..c208d5238f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/FaloTheBardClue.java @@ -41,6 +41,7 @@ import net.runelite.client.plugins.cluescrolls.clues.item.AnyRequirementCollecti import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; import net.runelite.client.plugins.cluescrolls.clues.item.RangeItemRequirement; import net.runelite.client.plugins.cluescrolls.clues.item.SingleItemRequirement; +import net.runelite.client.ui.FontManager; import net.runelite.client.ui.overlay.OverlayUtil; import net.runelite.client.ui.overlay.components.LineComponent; import net.runelite.client.ui.overlay.components.PanelComponent; @@ -134,6 +135,7 @@ public class FaloTheBardClue extends ClueScroll implements TextClueScroll, NpcCl .left(requirement.getCollectiveName(plugin.getClient())) .leftColor(TITLED_CONTENT_COLOR) .right(inventoryFulfilled ? "\u2713" : "\u2717") + .rightFont(FontManager.getDefaultFont()) .rightColor(inventoryFulfilled ? Color.GREEN : Color.RED) .build()); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java index 67de8ad26a..41f6f283cb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/SkillChallengeClue.java @@ -25,6 +25,11 @@ package net.runelite.client.plugins.cluescrolls.clues; import com.google.common.collect.ImmutableSet; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -34,25 +39,21 @@ import net.runelite.api.ItemID; import net.runelite.api.NPC; import net.runelite.api.Point; import net.runelite.api.TileObject; +import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_BORDER_COLOR; import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_FILL_COLOR; import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.CLICKBOX_HOVER_BORDER_COLOR; +import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; import net.runelite.client.plugins.cluescrolls.clues.item.AnyRequirementCollection; -import static net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirements.*; import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import static net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirements.*; import net.runelite.client.plugins.cluescrolls.clues.item.SingleItemRequirement; +import net.runelite.client.ui.FontManager; 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.components.TitleComponent; -import java.awt.Color; -import java.awt.Graphics2D; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import static net.runelite.client.plugins.cluescrolls.ClueScrollOverlay.TITLED_CONTENT_COLOR; -import static net.runelite.client.plugins.cluescrolls.ClueScrollWorldOverlay.IMAGE_Z_OFFSET; @Getter public class SkillChallengeClue extends ClueScroll implements NpcClueScroll, NamedObjectClueScroll @@ -379,6 +380,7 @@ public class SkillChallengeClue extends ClueScroll implements NpcClueScroll, Nam .left(requirement.getCollectiveName(plugin.getClient())) .leftColor(TITLED_CONTENT_COLOR) .right(combinedFulfilled ? "\u2713" : "\u2717") + .rightFont(FontManager.getDefaultFont()) .rightColor(equipmentFulfilled || (combinedFulfilled && !requireEquipped) ? Color.GREEN : (combinedFulfilled ? Color.ORANGE : Color.RED)) .build()); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java index 02ef88a083..74e71b934d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java @@ -96,6 +96,7 @@ public enum HotColdLocation FREMENNIK_PROVINCE_LUNAR_VILLAGE(new WorldPoint(2084, 3916, 0), FREMENNIK_PROVINCE, "Lunar Isle, inside the village.", ANCIENT_WIZARDS), FREMENNIK_PROVINCE_LUNAR_NORTH(new WorldPoint(2106, 3949, 0), FREMENNIK_PROVINCE, "Lunar Isle, north of the village.", ANCIENT_WIZARDS), ICE_MOUNTAIN(new WorldPoint(3007, 3475, 0), MISTHALIN, "Atop Ice Mountain"), + ISLE_OF_SOULS_MINE(new WorldPoint(2189, 2794, 0), KANDARIN, "Isle of Souls Mine, south of the Soul Wars lobby"), KANDARIN_SINCLAR_MANSION(new WorldPoint(2730, 3588, 0), KANDARIN, "North-west of the Sinclair Mansion, near the log balance shortcut.", BRASSICAN_MAGE), KANDARIN_CATHERBY(new WorldPoint(2774, 3436, 0), KANDARIN, "Catherby, between the bank and the beehives, near small rock formation.", BRASSICAN_MAGE), KANDARIN_GRAND_TREE(new WorldPoint(2448, 3503, 0), KANDARIN, "Grand Tree, just east of the terrorchick gnome enclosure.", BRASSICAN_MAGE), @@ -164,7 +165,7 @@ public enum HotColdLocation ZEAH_BLASTMINE_NORTH(new WorldPoint(1488, 3881, 0), ZEAH, "Northern part of the Lovakengj blast mine.", BRASSICAN_MAGE), ZEAH_LOVAKITE_FURNACE(new WorldPoint(1507, 3819, 0), ZEAH, "Next to the lovakite furnace in Lovakengj.", ANCIENT_WIZARDS), ZEAH_LOVAKENGJ_MINE(new WorldPoint(1477, 3778, 0), ZEAH, "Next to mithril rock in the Lovakengj mine.", ANCIENT_WIZARDS), - ZEAH_SULPHR_MINE(new WorldPoint(1428, 3869, 0), ZEAH, "Western entrance in the Lovakengj sulphur mine.", BRASSICAN_MAGE), + ZEAH_SULPHR_MINE(new WorldPoint(1428, 3869, 0), ZEAH, "Western entrance in the Lovakengj sulphur mine. Facemask or Slayer Helmet recommended.", BRASSICAN_MAGE), ZEAH_SHAYZIEN_BANK(new WorldPoint(1517, 3603, 0), ZEAH, "South-east of the bank in Shayzien.", ANCIENT_WIZARDS), ZEAH_OVERPASS(new WorldPoint(1467, 3714, 0), ZEAH, "Overpass between Lovakengj and Shayzien.", BRASSICAN_MAGE), ZEAH_LIZARDMAN(new WorldPoint(1490, 3698, 0), ZEAH, "Within Lizardman Canyon, east of the ladder. Requires 5% favour with Shayzien.", ANCIENT_WIZARDS), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java index b72286e3b1..b176bf154b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPanel.java @@ -66,6 +66,8 @@ import javax.swing.border.MatteBorder; import javax.swing.event.ChangeListener; import javax.swing.text.JTextComponent; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.events.ConfigButtonClicked; +import net.runelite.client.config.Button; import net.runelite.client.config.ConfigDescriptor; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; @@ -78,6 +80,7 @@ import net.runelite.client.config.Keybind; import net.runelite.client.config.ModifierlessKeybind; import net.runelite.client.config.Range; import net.runelite.client.config.Units; +import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ExternalPluginsChanged; import net.runelite.client.events.PluginChanged; @@ -130,6 +133,9 @@ class ConfigPanel extends PluginPanel @Inject private ColorPickerManager colorPickerManager; + @Inject + private EventBus eventBus; + private PluginConfigurationDescriptor pluginConfig = null; static @@ -334,6 +340,28 @@ class ConfigPanel extends PluginPanel PluginListItem.addLabelPopupMenu(configEntryName, createResetMenuItem(pluginConfig, cid)); item.add(configEntryName, BorderLayout.CENTER); + if (cid.getType() == Button.class) + { + try + { + ConfigItem cidItem = cid.getItem(); + JButton button = new JButton(cidItem.name()); + button.addActionListener((e) -> + { + ConfigButtonClicked event = new ConfigButtonClicked(); + event.setGroup(cd.getGroup().value()); + event.setKey(cid.getItem().keyName()); + eventBus.post(event); + }); + item.add(button); + } + catch (Exception ex) + { + log.error("Adding action listener failed: {}", ex.getMessage()); + ex.printStackTrace(); + } + } + if (cid.getType() == boolean.class) { JCheckBox checkbox = new JCheckBox(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java index 55b9160c5b..131aef04bc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/HotkeyButton.java @@ -32,6 +32,7 @@ import javax.swing.JButton; import lombok.Getter; import net.runelite.client.config.Keybind; import net.runelite.client.config.ModifierlessKeybind; +import net.runelite.client.ui.FontManager; class HotkeyButton extends JButton { @@ -40,6 +41,7 @@ class HotkeyButton extends JButton public HotkeyButton(Keybind value, boolean modifierless) { + setFont(FontManager.getDefaultFont().deriveFont(12.f)); setValue(value); addMouseListener(new MouseAdapter() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java index 185a5e7ecc..60f6256048 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginToggleButton.java @@ -29,8 +29,8 @@ import java.awt.Dimension; import java.awt.image.BufferedImage; import javax.swing.ImageIcon; import javax.swing.JToggleButton; -import com.openosrs.client.util.ImageUtil; import net.runelite.client.ui.ColorScheme; +import net.runelite.client.util.ImageUtil; import net.runelite.client.util.SwingUtil; class PluginToggleButton extends JToggleButton diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java index 661071a786..09d737c10f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/crowdsourcing/CrowdsourcingManager.java @@ -32,7 +32,6 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import net.runelite.http.api.RuneLiteAPI; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; @@ -47,11 +46,13 @@ public class CrowdsourcingManager { private static final String CROWDSOURCING_BASE = "https://crowdsource.runescape.wiki/runelite"; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final Gson GSON = RuneLiteAPI.GSON; @Inject private OkHttpClient okHttpClient; + @Inject + private Gson gson; + private List data = new ArrayList<>(); public void storeEvent(Object event) @@ -77,7 +78,7 @@ public class CrowdsourcingManager Request r = new Request.Builder() .url(CROWDSOURCING_BASE) - .post(RequestBody.create(JSON, GSON.toJson(temp))) + .post(RequestBody.create(JSON, gson.toJson(temp))) .build(); okHttpClient.newCall(r).enqueue(new Callback() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index b4a9f65337..208107e836 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -43,8 +43,6 @@ import net.runelite.api.Constants; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GraphicsObject; -import net.runelite.api.TileItem; -import net.runelite.api.GroundObject; import net.runelite.api.ItemLayer; import net.runelite.api.NPC; import net.runelite.api.NPCComposition; @@ -55,7 +53,8 @@ import net.runelite.api.Point; import net.runelite.api.Projectile; import net.runelite.api.Scene; import net.runelite.api.Tile; -import net.runelite.api.WallObject; +import net.runelite.api.TileItem; +import net.runelite.api.TileObject; import net.runelite.api.coords.LocalPoint; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; @@ -154,7 +153,6 @@ class DevToolsOverlay extends Overlay String text = local.getName() + " (A: " + local.getAnimation() + ") (P: " + local.getPoseAnimation() + ") (G: " + local.getGraphic() + ")"; OverlayUtil.renderActorOverlay(graphics, local, text, CYAN); - renderPlayerWireframe(graphics, local, CYAN); } private void renderNpcs(Graphics2D graphics) @@ -214,7 +212,7 @@ class DevToolsOverlay extends Overlay if (plugin.getGroundObjects().isActive()) { - renderGroundObject(graphics, tile, player); + renderTileObject(graphics, tile.getGroundObject(), player, PURPLE); } if (plugin.getGameObjects().isActive()) @@ -224,7 +222,7 @@ class DevToolsOverlay extends Overlay if (plugin.getWalls().isActive()) { - renderWallObject(graphics, tile, player); + renderTileObject(graphics, tile.getWallObject(), player, GRAY); } if (plugin.getDecorations().isActive()) @@ -309,45 +307,21 @@ class DevToolsOverlay extends Overlay { for (GameObject gameObject : gameObjects) { - if (gameObject != null) + if (gameObject != null && gameObject.getSceneMinLocation().equals(tile.getSceneLocation())) { - if (player.getLocalLocation().distanceTo(gameObject.getLocalLocation()) <= MAX_DISTANCE) - { - OverlayUtil.renderTileOverlay(graphics, gameObject, "ID: " + gameObject.getId(), GREEN); - } - - // Draw a polygon around the convex hull - // of the model vertices - Shape p = gameObject.getConvexHull(); - if (p != null) - { - graphics.draw(p); - } + renderTileObject(graphics, gameObject, player, GREEN); } } } } - private void renderGroundObject(Graphics2D graphics, Tile tile, Player player) + private void renderTileObject(Graphics2D graphics, TileObject tileObject, Player player, Color color) { - GroundObject groundObject = tile.getGroundObject(); - if (groundObject != null) + if (tileObject != null) { - if (player.getLocalLocation().distanceTo(groundObject.getLocalLocation()) <= MAX_DISTANCE) + if (player.getLocalLocation().distanceTo(tileObject.getLocalLocation()) <= MAX_DISTANCE) { - OverlayUtil.renderTileOverlay(graphics, groundObject, "ID: " + groundObject.getId(), PURPLE); - } - } - } - - private void renderWallObject(Graphics2D graphics, Tile tile, Player player) - { - WallObject wallObject = tile.getWallObject(); - if (wallObject != null) - { - if (player.getLocalLocation().distanceTo(wallObject.getLocalLocation()) <= MAX_DISTANCE) - { - OverlayUtil.renderTileOverlay(graphics, wallObject, "ID: " + wallObject.getId(), GRAY); + OverlayUtil.renderTileOverlay(graphics, tileObject, "ID: " + tileObject.getId(), color); } } } @@ -447,22 +421,4 @@ class DevToolsOverlay extends Overlay } } } - - private void renderPlayerWireframe(Graphics2D graphics, Player player, Color color) - { - Polygon[] polys = player.getPolygons(); - - if (polys == null) - { - return; - } - - graphics.setColor(color); - - for (Polygon p : polys) - { - graphics.drawPolygon(p); - } - } - } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java index 7e07a12ee4..4bfe4652ed 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordGameEventType.java @@ -26,16 +26,12 @@ */ package net.runelite.client.plugins.discord; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import java.util.List; import java.util.Map; import javax.annotation.Nullable; import lombok.AllArgsConstructor; import lombok.Getter; -import net.runelite.api.Client; import net.runelite.api.Skill; -import net.runelite.api.Varbits; @AllArgsConstructor @Getter @@ -187,6 +183,7 @@ enum DiscordGameEventType DUNGEON_CRABCLAW_CAVES("Crabclaw Caves", DiscordAreaType.DUNGEONS, 6553, 6809), DUNGEON_CRANDOR("Crandor Dungeon", DiscordAreaType.DUNGEONS, 11414), DUNGEON_CRASH_SITE_CAVERN("Crash Site Cavern", DiscordAreaType.DUNGEONS, 8280, 8536), + DUNGEON_CRUMBLING_TOWER("Crumbling Tower", DiscordAreaType.DUNGEONS, 7827), DUNGEON_DAEYALT_ESSENCE_MINE("Daeyalt Essence Mine", DiscordAreaType.DUNGEONS, 14744), DUNGEON_DIGSITE("Digsite Dungeon", DiscordAreaType.DUNGEONS, 13464, 13465), DUNGEON_DORGESHKAAN("Dorgesh-Kaan South Dungeon", DiscordAreaType.DUNGEONS, 10833), @@ -210,6 +207,7 @@ enum DiscordGameEventType DUNGEON_HAM_STORE_ROOM("H.A.M. Store room", DiscordAreaType.DUNGEONS, 10321), DUNGEON_HEROES_GUILD("Heroes' Guild Mine", DiscordAreaType.DUNGEONS, 11674), DUNGEON_IORWERTH("Iorwerth Dungeon", DiscordAreaType.DUNGEONS, 12737, 12738, 12993, 12994), + DUNGEON_ISLE_OF_SOULS("Isle of Souls Dungeon", DiscordAreaType.DUNGEONS, 8593), DUNGEON_JATIZSO_MINES("Jatizso Mines", DiscordAreaType.DUNGEONS, 9631), DUNGEON_JIGGIG_BURIAL_TOMB("Jiggig Burial Tomb", DiscordAreaType.DUNGEONS, 9875, 9874), DUNGEON_JOGRE("Jogre Dungeon", DiscordAreaType.DUNGEONS, 11412), @@ -307,8 +305,8 @@ enum DiscordGameEventType MG_VOLCANIC_MINE("Volcanic Mine", DiscordAreaType.MINIGAMES, 15263, 15262), // Raids - RAIDS_CHAMBERS_OF_XERIC("Chambers of Xeric", DiscordAreaType.RAIDS, Varbits.IN_RAID), - RAIDS_THEATRE_OF_BLOOD("Theatre of Blood", DiscordAreaType.RAIDS, Varbits.THEATRE_OF_BLOOD), + RAIDS_CHAMBERS_OF_XERIC("Chambers of Xeric", DiscordAreaType.RAIDS, 12889, 13136, 13137, 13138, 13139, 13140, 13141, 13145, 13393, 13394, 13395, 13396, 13397, 13401), + RAIDS_THEATRE_OF_BLOOD("Theatre of Blood", DiscordAreaType.RAIDS, 12611, 12612, 12613, 12867, 12869, 13122, 13123, 13125, 13379), // Other REGION_ABYSSAL_AREA("Abyssal Area", DiscordAreaType.REGIONS, 12108), @@ -367,6 +365,7 @@ enum DiscordGameEventType REGION_ICYENE_GRAVEYARD("Icyene Graveyard", DiscordAreaType.REGIONS, 14641, 14897, 14898), REGION_ISAFDAR("Isafdar", DiscordAreaType.REGIONS, 8497, 8753, 8754, 9009, 9010), REGION_ISLAND_OF_STONE("Island of Stone", DiscordAreaType.REGIONS, 9790), + REGION_ISLE_OF_SOULS("Isle of Souls", DiscordAreaType.REGIONS, 8236, 8237, 8238, 8491, 8492, 8494, 8747, 8750, 9003, 9004, 9006, 9260, 9261, 9262), REGION_JIGGIG("Jiggig" , DiscordAreaType.REGIONS, 9775), REGION_KANDARIN("Kandarin", DiscordAreaType.REGIONS, 9014, 9263, 9264, 9519, 9524, 9527, 9776, 9783, 10037, 10290, 10294, 10546, 10551, 10805), REGION_KARAMJA("Karamja" , DiscordAreaType.REGIONS, 10801, 10802, 11054, 11311, 11312, 11313, 11566, 11567, 11568, 11569, 11822), @@ -430,20 +429,12 @@ enum DiscordGameEventType REGION_WRATH_ALTAR("Wrath Altar", DiscordAreaType.REGIONS, 9291); private static final Map FROM_REGION; - private static final List FROM_VARBITS; static { ImmutableMap.Builder regionMapBuilder = new ImmutableMap.Builder<>(); - ImmutableList.Builder fromVarbitsBuilder = ImmutableList.builder(); for (DiscordGameEventType discordGameEventType : DiscordGameEventType.values()) { - if (discordGameEventType.getVarbits() != null) - { - fromVarbitsBuilder.add(discordGameEventType); - continue; - } - if (discordGameEventType.getRegionIds() == null) { continue; @@ -455,7 +446,6 @@ enum DiscordGameEventType } } FROM_REGION = regionMapBuilder.build(); - FROM_VARBITS = fromVarbitsBuilder.build(); } @Nullable @@ -497,9 +487,6 @@ enum DiscordGameEventType @Nullable private DiscordAreaType discordAreaType; - @Nullable - private Varbits varbits; - @Nullable private int[] regionIds; @@ -541,15 +528,6 @@ enum DiscordGameEventType this(state, priority, true, false, false, true, false); } - DiscordGameEventType(String areaName, DiscordAreaType areaType, Varbits varbits) - { - this.state = exploring(areaType, areaName); - this.priority = -2; - this.discordAreaType = areaType; - this.varbits = varbits; - this.shouldClear = true; - } - private static String training(final Skill skill) { return training(skill.getName()); @@ -609,17 +587,4 @@ enum DiscordGameEventType { return FROM_REGION.get(regionId); } - - public static DiscordGameEventType fromVarbit(final Client client) - { - for (DiscordGameEventType fromVarbit : FROM_VARBITS) - { - if (client.getVar(fromVarbit.getVarbits()) != 0) - { - return fromVarbit; - } - } - - return null; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java index 6f5ffbdcd1..cfc0aa80e9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java @@ -46,7 +46,6 @@ import net.runelite.api.WorldType; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.StatChanged; -import net.runelite.api.events.VarbitChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.discord.DiscordService; import net.runelite.client.discord.events.DiscordJoinGame; @@ -210,22 +209,6 @@ public class DiscordPlugin extends Plugin } } - @Subscribe - public void onVarbitChanged(VarbitChanged event) - { - if (!config.showRaidingActivity()) - { - return; - } - - final DiscordGameEventType discordGameEventType = DiscordGameEventType.fromVarbit(client); - - if (discordGameEventType != null) - { - discordState.triggerEvent(discordGameEventType); - } - } - @Subscribe public void onDiscordReady(DiscordReady event) { @@ -447,6 +430,7 @@ public class DiscordPlugin extends Plugin case DUNGEONS: return config.showDungeonActivity(); case MINIGAMES: return config.showMinigameActivity(); case REGIONS: return config.showRegionsActivity(); + case RAIDS: return config.showRaidingActivity(); } return false; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java index 37cc934ce2..8eb163ba53 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/Emoji.java @@ -91,6 +91,8 @@ enum Emoji GORILLA(":G"), PLEADING("(n_n)"), XD("Xd"), + SPOON("--o"), + WEARY_FACE("Dx"), ; private static final Map emojiMap; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java index e63a97c3cc..9fc33d8647 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/emojis/EmojiPlugin.java @@ -40,7 +40,6 @@ import net.runelite.api.events.ChatMessage; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.OverheadTextChanged; import net.runelite.client.callback.ClientThread; -import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -63,9 +62,6 @@ public class EmojiPlugin extends Plugin @Inject private ClientThread clientThread; - @Inject - private ChatMessageManager chatMessageManager; - private int modIconsStart = -1; @Override @@ -145,9 +141,7 @@ public class EmojiPlugin extends Plugin return; } - messageNode.setRuneLiteFormatMessage(updatedMessage); - chatMessageManager.update(messageNode); - client.refreshChat(); + messageNode.setValue(updatedMessage); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java index d3ee118b8c..a18c211da3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderConfig.java @@ -29,16 +29,18 @@ import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; -@ConfigGroup("entityhider") +@ConfigGroup(EntityHiderConfig.GROUP) public interface EntityHiderConfig extends Config { + String GROUP = "entityhider"; + @ConfigItem( position = 1, keyName = "hidePlayers", - name = "Hide Players", - description = "Configures whether or not players are hidden" + name = "Hide Others", + description = "Configures whether or not other players are hidden" ) - default boolean hidePlayers() + default boolean hideOthers() { return true; } @@ -46,10 +48,10 @@ public interface EntityHiderConfig extends Config @ConfigItem( position = 2, keyName = "hidePlayers2D", - name = "Hide Players 2D", - description = "Configures whether or not players 2D elements are hidden" + name = "Hide Others 2D", + description = "Configures whether or not other players 2D elements are hidden" ) - default boolean hidePlayers2D() + default boolean hideOthers2D() { return true; } @@ -78,6 +80,17 @@ public interface EntityHiderConfig extends Config @ConfigItem( position = 5, + keyName = "hideIgnores", + name = "Hide Ignores", + description = "Configures whether or not ignored players are hidden" + ) + default boolean hideIgnores() + { + return false; + } + + @ConfigItem( + position = 6, keyName = "hideLocalPlayer", name = "Hide Local Player", description = "Configures whether or not the local player is hidden" @@ -88,7 +101,7 @@ public interface EntityHiderConfig extends Config } @ConfigItem( - position = 6, + position = 7, keyName = "hideLocalPlayer2D", name = "Hide Local Player 2D", description = "Configures whether or not the local player's 2D elements are hidden" @@ -99,7 +112,7 @@ public interface EntityHiderConfig extends Config } @ConfigItem( - position = 7, + position = 8, keyName = "hideNPCs", name = "Hide NPCs", description = "Configures whether or not NPCs are hidden" @@ -110,7 +123,7 @@ public interface EntityHiderConfig extends Config } @ConfigItem( - position = 8, + position = 9, keyName = "hideNPCs2D", name = "Hide NPCs 2D", description = "Configures whether or not NPCs 2D elements are hidden" @@ -121,7 +134,7 @@ public interface EntityHiderConfig extends Config } @ConfigItem( - position = 9, + position = 10, keyName = "hidePets", name = "Hide Pets", description = "Configures whether or not other player pets are hidden" @@ -132,7 +145,7 @@ public interface EntityHiderConfig extends Config } @ConfigItem( - position = 10, + position = 11, keyName = "hideAttackers", name = "Hide Attackers", description = "Configures whether or not NPCs/players attacking you are hidden" @@ -143,7 +156,7 @@ public interface EntityHiderConfig extends Config } @ConfigItem( - position = 11, + position = 12, keyName = "hideProjectiles", name = "Hide Projectiles", description = "Configures whether or not projectiles are hidden" diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java index 155bc6cc38..79a9dc160f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/entityhider/EntityHiderPlugin.java @@ -28,13 +28,9 @@ package net.runelite.client.plugins.entityhider; import com.google.inject.Provides; import javax.inject.Inject; import net.runelite.api.Client; -import net.runelite.api.GameState; -import net.runelite.api.Player; -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.events.ConfigChanged; -import net.runelite.api.events.GameStateChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -67,27 +63,22 @@ public class EntityHiderPlugin extends Plugin @Subscribe public void onConfigChanged(ConfigChanged e) { - updateConfig(); - } - - @Subscribe - public void onGameStateChanged(GameStateChanged event) - { - if (event.getGameState() == GameState.LOGGED_IN) + if (e.getGroup().equals(EntityHiderConfig.GROUP)) { - client.setIsHidingEntities(isPlayerRegionAllowed()); + updateConfig(); } } private void updateConfig() { - client.setIsHidingEntities(isPlayerRegionAllowed()); + client.setIsHidingEntities(true); - client.setPlayersHidden(config.hidePlayers()); - client.setPlayersHidden2D(config.hidePlayers2D()); + client.setOthersHidden(config.hideOthers()); + client.setOthersHidden2D(config.hideOthers2D()); client.setFriendsHidden(config.hideFriends()); client.setFriendsChatMembersHidden(config.hideFriendsChatMembers()); + client.setIgnoresHidden(config.hideIgnores()); client.setLocalPlayerHidden(config.hideLocalPlayer()); client.setLocalPlayerHidden2D(config.hideLocalPlayer2D()); @@ -107,11 +98,12 @@ public class EntityHiderPlugin extends Plugin { client.setIsHidingEntities(false); - client.setPlayersHidden(false); - client.setPlayersHidden2D(false); + client.setOthersHidden(false); + client.setOthersHidden2D(false); client.setFriendsHidden(false); client.setFriendsChatMembersHidden(false); + client.setIgnoresHidden(false); client.setLocalPlayerHidden(false); client.setLocalPlayerHidden2D(false); @@ -125,19 +117,4 @@ public class EntityHiderPlugin extends Plugin client.setProjectilesHidden(false); } - - private boolean isPlayerRegionAllowed() - { - final Player localPlayer = client.getLocalPlayer(); - - if (localPlayer == null) - { - return true; - } - - final int playerRegionID = WorldPoint.fromLocalInstance(client, localPlayer.getLocalLocation()).getRegionID(); - - // 9520 = Castle Wars - return playerRegionID != 9520; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java index e48d80edbd..6e68c4270a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingConfig.java @@ -150,6 +150,17 @@ public interface FishingConfig extends Config @ConfigItem( position = 10, + keyName = "flyingFishNotification", + name = "Flying fish notification", + description = "Send a notification when a flying fish spawns on your fishing spot." + ) + default boolean flyingFishNotification() + { + return true; + } + + @ConfigItem( + position = 11, keyName = "trawlerNotification", name = "Trawler activity notification", description = "Send a notification when fishing trawler activity drops below 15%." @@ -160,7 +171,7 @@ public interface FishingConfig extends Config } @ConfigItem( - position = 11, + position = 12, keyName = "trawlerTimer", name = "Trawler timer in MM:SS", description = "Trawler Timer will display a more accurate timer in MM:SS format." diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java index 1311021c29..e782b81428 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java @@ -215,6 +215,11 @@ public class FishingPlugin extends Plugin spotOverlay.setHidden(false); fishingSpotMinimapOverlay.setHidden(false); } + + if (event.getMessage().equals("A flying fish jumps up and eats some of your minnows!") && config.flyingFishNotification()) + { + notifier.notify("A flying fish is eating your minnows!"); + } } @Subscribe @@ -372,7 +377,7 @@ public class FishingPlugin extends Plugin { if (!trawlerNotificationSent) { - notifier.notify("[" + client.getLocalPlayer().getName() + "] has low Fishing Trawler activity!"); + notifier.notify("You have low Fishing Trawler activity!"); trawlerNotificationSent = true; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java index 8413c225e6..6263518015 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/friendschat/FriendsChatPlugin.java @@ -411,11 +411,7 @@ public class FriendsChatPlugin extends Plugin .append(textColor, member.getName() + activityMessage); final String messageString = message.build(); - client.addChatMessage(ChatMessageType.FRIENDSCHATNOTIFICATION, "", messageString, ""); - - final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(ChatMessageType.FRIENDSCHATNOTIFICATION.getType()); - final MessageNode[] lines = chatLineBuffer.getLines(); - final MessageNode line = lines[0]; + final MessageNode line = client.addChatMessage(ChatMessageType.FRIENDSCHATNOTIFICATION, "", messageString, ""); MemberJoinMessage joinMessage = new MemberJoinMessage(line, line.getId(), client.getTickCount()); joinMessages.addLast(joinMessage); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLBuffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLBuffer.java new file mode 100644 index 0000000000..cd3b7288ed --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GLBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Adam + * 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.gpu; + +import org.jocl.Pointer; +import org.jocl.cl_mem; + +class GLBuffer +{ + int glBufferId = -1; + int size = -1; + cl_mem cl_mem; + + Pointer ptr() + { + return cl_mem != null ? Pointer.to(cl_mem) : null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index a6be4e5872..8ba9db5a67 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -29,6 +29,11 @@ import com.google.inject.Provides; import com.jogamp.nativewindow.awt.AWTGraphicsConfiguration; import com.jogamp.nativewindow.awt.JAWTWindow; import com.jogamp.opengl.GL; +import static com.jogamp.opengl.GL.GL_ARRAY_BUFFER; +import static com.jogamp.opengl.GL.GL_DYNAMIC_DRAW; +import static com.jogamp.opengl.GL2ES2.GL_STREAM_DRAW; +import static com.jogamp.opengl.GL2ES3.GL_STATIC_COPY; +import static com.jogamp.opengl.GL2ES3.GL_UNIFORM_BUFFER; import com.jogamp.opengl.GL4; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLContext; @@ -45,6 +50,7 @@ import java.awt.Image; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -93,6 +99,10 @@ import net.runelite.client.plugins.gpu.config.UIScalingMode; import net.runelite.client.plugins.gpu.template.Template; import net.runelite.client.ui.DrawManager; import net.runelite.client.util.OSType; +import org.jocl.CL; +import static org.jocl.CL.CL_MEM_READ_ONLY; +import static org.jocl.CL.CL_MEM_WRITE_ONLY; +import static org.jocl.CL.clCreateFromGLBuffer; @PluginDescriptor( name = "GPU", @@ -105,8 +115,8 @@ import net.runelite.client.util.OSType; public class GpuPlugin extends Plugin implements DrawCallbacks { // This is the maximum number of triangles the compute shaders support - private static final int MAX_TRIANGLE = 4096; - private static final int SMALL_TRIANGLE_COUNT = 512; + static final int MAX_TRIANGLE = 4096; + static final int SMALL_TRIANGLE_COUNT = 512; private static final int FLAG_SCENE_BUFFER = Integer.MIN_VALUE; private static final int DEFAULT_DISTANCE = 25; static final int MAX_DISTANCE = 90; @@ -115,6 +125,9 @@ public class GpuPlugin extends Plugin implements DrawCallbacks @Inject private Client client; + @Inject + private OpenCLManager openCLManager; + @Inject private ClientThread clientThread; @@ -133,7 +146,14 @@ public class GpuPlugin extends Plugin implements DrawCallbacks @Inject private PluginManager pluginManager; - private boolean useComputeShaders; + enum ComputeMode + { + NONE, + OPENGL, + OPENCL + } + + private ComputeMode computeMode = ComputeMode.NONE; private Canvas canvas; private JAWTWindow jawtWindow; @@ -182,23 +202,22 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private int texSceneHandle; private int rboSceneHandle; - // scene vertex buffer id - private int bufferId; - // scene uv buffer id - private int uvBufferId; + // scene vertex buffer + private final GLBuffer sceneVertexBuffer = new GLBuffer(); + // scene uv buffer + private final GLBuffer sceneUvBuffer = new GLBuffer(); - private int tmpBufferId; // temporary scene vertex buffer - private int tmpUvBufferId; // temporary scene uv buffer - private int tmpModelBufferId; // scene model buffer, large - private int tmpModelBufferSmallId; // scene model buffer, small - private int tmpModelBufferUnorderedId; - private int tmpOutBufferId; // target vertex buffer for compute shaders - private int tmpOutUvBufferId; // target uv buffer for compute shaders + private final GLBuffer tmpVertexBuffer = new GLBuffer(); // temporary scene vertex buffer + private final GLBuffer tmpUvBuffer = new GLBuffer(); // temporary scene uv buffer + private final GLBuffer tmpModelBufferLarge = new GLBuffer(); // scene model buffer, large + private final GLBuffer tmpModelBufferSmall = new GLBuffer(); // scene model buffer, small + private final GLBuffer tmpModelBufferUnordered = new GLBuffer(); // scene model buffer, unordered + private final GLBuffer tmpOutBuffer = new GLBuffer(); // target vertex buffer for compute shaders + private final GLBuffer tmpOutUvBuffer = new GLBuffer(); // target uv buffer for compute shaders private int textureArrayId; - private int uniformBufferId; - private final IntBuffer uniformBuffer = GpuIntBuffer.allocateDirect(5 + 3 + 2048 * 4); + private final GLBuffer uniformBuffer = new GLBuffer(); private final float[] textureOffsets = new float[128]; private GpuIntBuffer vertexBuffer; @@ -278,7 +297,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks { try { - bufferId = uvBufferId = uniformBufferId = tmpBufferId = tmpUvBufferId = tmpModelBufferId = tmpModelBufferSmallId = tmpModelBufferUnorderedId = tmpOutBufferId = tmpOutUvBufferId = -1; texSceneHandle = fboSceneHandle = rboSceneHandle = -1; // AA FBO unorderedModels = smallModels = largeModels = 0; drawingModel = false; @@ -290,8 +308,9 @@ public class GpuPlugin extends Plugin implements DrawCallbacks return false; } - // OSX supports up to OpenGL 4.1, however 4.3 is required for compute shaders - useComputeShaders = config.useComputeShaders() && OSType.getOSType() != OSType.MacOS; + computeMode = config.useComputeShaders() + ? (OSType.getOSType() == OSType.MacOS ? ComputeMode.OPENCL : ComputeMode.OPENGL) + : ComputeMode.NONE; canvas.setIgnoreRepaint(true); @@ -397,7 +416,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks if (client.getGameState() == GameState.LOGGED_IN) { - uploadScene(); + invokeOnMainThread(this::uploadScene); } } catch (Throwable e) @@ -433,6 +452,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks invokeOnMainThread(() -> { + openCLManager.cleanup(); + if (gl != null) { if (textureArrayId != -1) @@ -441,11 +462,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks textureArrayId = -1; } - if (uniformBufferId != -1) - { - glDeleteBuffer(gl, uniformBufferId); - uniformBufferId = -1; - } + destroyGlBuffer(uniformBuffer); shutdownBuffers(); shutdownInterfaceTexture(); @@ -519,12 +536,16 @@ public class GpuPlugin extends Plugin implements DrawCallbacks glProgram = PROGRAM.compile(gl, template); glUiProgram = UI_PROGRAM.compile(gl, template); - if (useComputeShaders) + if (computeMode == ComputeMode.OPENGL) { glComputeProgram = COMPUTE_PROGRAM.compile(gl, template); glSmallComputeProgram = SMALL_COMPUTE_PROGRAM.compile(gl, template); glUnorderedComputeProgram = UNORDERED_COMPUTE_PROGRAM.compile(gl, template); } + else if (computeMode == ComputeMode.OPENCL) + { + openCLManager.init(gl); + } initUniforms(); } @@ -593,8 +614,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks -1f, 1f, 0.0f, 0.0f, 0f // top left }); vboUiBuf.rewind(); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vboUiHandle); - gl.glBufferData(gl.GL_ARRAY_BUFFER, vboUiBuf.capacity() * Float.BYTES, vboUiBuf, gl.GL_STATIC_DRAW); + gl.glBindBuffer(GL_ARRAY_BUFFER, vboUiHandle); + gl.glBufferData(GL_ARRAY_BUFFER, vboUiBuf.capacity() * Float.BYTES, vboUiBuf, gl.GL_STATIC_DRAW); // position attribute gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, false, 5 * Float.BYTES, 0); @@ -605,7 +626,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks gl.glEnableVertexAttribArray(1); // unbind VBO - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0); + gl.glBindBuffer(GL_ARRAY_BUFFER, 0); } private void shutdownVao() @@ -622,71 +643,49 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private void initBuffers() { - bufferId = glGenBuffers(gl); - uvBufferId = glGenBuffers(gl); - tmpBufferId = glGenBuffers(gl); - tmpUvBufferId = glGenBuffers(gl); - tmpModelBufferId = glGenBuffers(gl); - tmpModelBufferSmallId = glGenBuffers(gl); - tmpModelBufferUnorderedId = glGenBuffers(gl); - tmpOutBufferId = glGenBuffers(gl); - tmpOutUvBufferId = glGenBuffers(gl); + initGlBuffer(sceneVertexBuffer); + initGlBuffer(sceneUvBuffer); + initGlBuffer(tmpVertexBuffer); + initGlBuffer(tmpUvBuffer); + initGlBuffer(tmpModelBufferLarge); + initGlBuffer(tmpModelBufferSmall); + initGlBuffer(tmpModelBufferUnordered); + initGlBuffer(tmpOutBuffer); + initGlBuffer(tmpOutUvBuffer); + } + + private void initGlBuffer(GLBuffer glBuffer) + { + glBuffer.glBufferId = glGenBuffers(gl); } private void shutdownBuffers() { - if (bufferId != -1) - { - glDeleteBuffer(gl, bufferId); - bufferId = -1; - } + destroyGlBuffer(sceneVertexBuffer); + destroyGlBuffer(sceneUvBuffer); - if (uvBufferId != -1) - { - glDeleteBuffer(gl, uvBufferId); - uvBufferId = -1; - } + destroyGlBuffer(tmpVertexBuffer); + destroyGlBuffer(tmpUvBuffer); + destroyGlBuffer(tmpModelBufferLarge); + destroyGlBuffer(tmpModelBufferSmall); + destroyGlBuffer(tmpModelBufferUnordered); + destroyGlBuffer(tmpOutBuffer); + destroyGlBuffer(tmpOutUvBuffer); + } - if (tmpBufferId != -1) + private void destroyGlBuffer(GLBuffer glBuffer) + { + if (glBuffer.glBufferId != -1) { - glDeleteBuffer(gl, tmpBufferId); - tmpBufferId = -1; + glDeleteBuffer(gl, glBuffer.glBufferId); + glBuffer.glBufferId = -1; } + glBuffer.size = -1; - if (tmpUvBufferId != -1) + if (glBuffer.cl_mem != null) { - glDeleteBuffer(gl, tmpUvBufferId); - tmpUvBufferId = -1; - } - - if (tmpModelBufferId != -1) - { - glDeleteBuffer(gl, tmpModelBufferId); - tmpModelBufferId = -1; - } - - if (tmpModelBufferSmallId != -1) - { - glDeleteBuffer(gl, tmpModelBufferSmallId); - tmpModelBufferSmallId = -1; - } - - if (tmpModelBufferUnorderedId != -1) - { - glDeleteBuffer(gl, tmpModelBufferUnorderedId); - tmpModelBufferUnorderedId = -1; - } - - if (tmpOutBufferId != -1) - { - glDeleteBuffer(gl, tmpOutBufferId); - tmpOutBufferId = -1; - } - - if (tmpOutUvBufferId != -1) - { - glDeleteBuffer(gl, tmpOutUvBufferId); - tmpOutUvBufferId = -1; + CL.clReleaseMemObject(glBuffer.cl_mem); + glBuffer.cl_mem = null; } } @@ -709,21 +708,21 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private void initUniformBuffer() { - uniformBufferId = glGenBuffers(gl); - gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, uniformBufferId); - uniformBuffer.clear(); - uniformBuffer.put(new int[8]); + initGlBuffer(uniformBuffer); + + IntBuffer uniformBuf = GpuIntBuffer.allocateDirect(8 + 2048 * 4); + uniformBuf.put(new int[8]); // uniform block final int[] pad = new int[2]; for (int i = 0; i < 2048; i++) { - uniformBuffer.put(Perspective.SINE[i]); - uniformBuffer.put(Perspective.COSINE[i]); - uniformBuffer.put(pad); + uniformBuf.put(Perspective.SINE[i]); + uniformBuf.put(Perspective.COSINE[i]); + uniformBuf.put(pad); // ivec2 alignment in std140 is 16 bytes } - uniformBuffer.flip(); + uniformBuf.flip(); - gl.glBufferData(gl.GL_UNIFORM_BUFFER, uniformBuffer.limit() * Integer.BYTES, uniformBuffer, gl.GL_DYNAMIC_DRAW); - gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, 0); + updateBuffer(uniformBuffer, GL_UNIFORM_BUFFER, uniformBuf.limit() * Integer.BYTES, uniformBuf, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); + gl.glBindBuffer(GL_UNIFORM_BUFFER, 0); } private void initAAFbo(int width, int height, int aaSamples) @@ -785,9 +784,11 @@ public class GpuPlugin extends Plugin implements DrawCallbacks invokeOnMainThread(() -> { // UBO. Only the first 32 bytes get modified here, the rest is the constant sin/cos table. - gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, uniformBufferId); - uniformBuffer.clear(); - uniformBuffer + // We can reuse the vertex buffer since it isn't used yet. + vertexBuffer.clear(); + vertexBuffer.ensureCapacity(32); + IntBuffer uniformBuf = vertexBuffer.getBuffer(); + uniformBuf .put(yaw) .put(pitch) .put(client.getCenterX()) @@ -796,12 +797,14 @@ public class GpuPlugin extends Plugin implements DrawCallbacks .put(cameraX) .put(cameraY) .put(cameraZ); - uniformBuffer.flip(); + uniformBuf.flip(); - gl.glBufferSubData(gl.GL_UNIFORM_BUFFER, 0, uniformBuffer.limit() * Integer.BYTES, uniformBuffer); - gl.glBindBuffer(gl.GL_UNIFORM_BUFFER, 0); + gl.glBindBuffer(GL_UNIFORM_BUFFER, uniformBuffer.glBufferId); + gl.glBufferSubData(GL_UNIFORM_BUFFER, 0, uniformBuf.limit() * Integer.BYTES, uniformBuf); + gl.glBindBuffer(GL_UNIFORM_BUFFER, 0); - gl.glBindBufferBase(gl.GL_UNIFORM_BUFFER, 0, uniformBufferId); + gl.glBindBufferBase(GL_UNIFORM_BUFFER, 0, uniformBuffer.glBufferId); + uniformBuf.clear(); }); } @@ -813,7 +816,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private void postDraw() { - if (!useComputeShaders) + if (computeMode == ComputeMode.NONE) { // Upload buffers vertexBuffer.flip(); @@ -822,12 +825,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks IntBuffer vertexBuffer = this.vertexBuffer.getBuffer(); FloatBuffer uvBuffer = this.uvBuffer.getBuffer(); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, gl.GL_DYNAMIC_DRAW); - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpUvBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, gl.GL_DYNAMIC_DRAW); - + updateBuffer(tmpVertexBuffer, GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, GL_DYNAMIC_DRAW, 0L); + updateBuffer(tmpUvBuffer, GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, GL_DYNAMIC_DRAW, 0L); return; } @@ -844,79 +843,91 @@ public class GpuPlugin extends Plugin implements DrawCallbacks IntBuffer modelBufferSmall = this.modelBufferSmall.getBuffer(); IntBuffer modelBufferUnordered = this.modelBufferUnordered.getBuffer(); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, gl.GL_DYNAMIC_DRAW); + // temp buffers + updateBuffer(tmpVertexBuffer, GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); + updateBuffer(tmpUvBuffer, GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpUvBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, gl.GL_DYNAMIC_DRAW); - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpModelBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, modelBuffer.limit() * Integer.BYTES, modelBuffer, gl.GL_DYNAMIC_DRAW); - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpModelBufferSmallId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, modelBufferSmall.limit() * Integer.BYTES, modelBufferSmall, gl.GL_DYNAMIC_DRAW); - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpModelBufferUnorderedId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, modelBufferUnordered.limit() * Integer.BYTES, modelBufferUnordered, gl.GL_DYNAMIC_DRAW); + // model buffers + updateBuffer(tmpModelBufferLarge, GL_ARRAY_BUFFER, modelBuffer.limit() * Integer.BYTES, modelBuffer, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); + updateBuffer(tmpModelBufferSmall, GL_ARRAY_BUFFER, modelBufferSmall.limit() * Integer.BYTES, modelBufferSmall, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); + updateBuffer(tmpModelBufferUnordered, GL_ARRAY_BUFFER, modelBufferUnordered.limit() * Integer.BYTES, modelBufferUnordered, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); // Output buffers - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpOutBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, + updateBuffer(tmpOutBuffer, + GL_ARRAY_BUFFER, targetBufferOffset * 16, // each vertex is an ivec4, which is 16 bytes null, - gl.GL_STREAM_DRAW); - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, tmpOutUvBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, - targetBufferOffset * 16, + GL_STREAM_DRAW, + CL_MEM_WRITE_ONLY); + updateBuffer(tmpOutUvBuffer, + GL_ARRAY_BUFFER, + targetBufferOffset * 16, // each vertex is an ivec4, which is 16 bytes null, - gl.GL_STREAM_DRAW); + GL_STREAM_DRAW, + CL_MEM_WRITE_ONLY); - // Bind UBO to compute programs - gl.glUniformBlockBinding(glSmallComputeProgram, uniBlockSmall, 0); - gl.glUniformBlockBinding(glComputeProgram, uniBlockLarge, 0); + if (computeMode == ComputeMode.OPENCL) + { + // The docs for clEnqueueAcquireGLObjects say all pending GL operations must be completed before calling + // clEnqueueAcquireGLObjects, and recommends calling glFinish() as the only portable way to do that. + // However no issues have been observed from not calling it, and so will leave disabled for now. + // gl.glFinish(); + + openCLManager.compute( + unorderedModels, smallModels, largeModels, + sceneVertexBuffer, sceneUvBuffer, + tmpVertexBuffer, tmpUvBuffer, + tmpModelBufferUnordered, tmpModelBufferSmall, tmpModelBufferLarge, + tmpOutBuffer, tmpOutUvBuffer, + uniformBuffer); + return; + } /* * Compute is split into three separate programs: 'unordered', 'small', and 'large' * to save on GPU resources. Small will sort <= 512 faces, large will do <= 4096. */ + // Bind UBO to compute programs + gl.glUniformBlockBinding(glSmallComputeProgram, uniBlockSmall, 0); + gl.glUniformBlockBinding(glComputeProgram, uniBlockLarge, 0); + // unordered gl.glUseProgram(glUnorderedComputeProgram); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferUnorderedId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, this.bufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, this.uvBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferUnordered.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, sceneVertexBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpVertexBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, sceneUvBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBuffer.glBufferId); gl.glDispatchCompute(unorderedModels, 1, 1); // small gl.glUseProgram(glSmallComputeProgram); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferSmallId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, this.bufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, this.uvBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferSmall.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, sceneVertexBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpVertexBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, sceneUvBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBuffer.glBufferId); gl.glDispatchCompute(smallModels, 1, 1); // large gl.glUseProgram(glComputeProgram); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, this.bufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, this.uvBufferId); - gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, tmpModelBufferLarge.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 1, sceneVertexBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 2, tmpVertexBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 3, tmpOutBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 4, tmpOutUvBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 5, sceneUvBuffer.glBufferId); + gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 6, tmpUvBuffer.glBufferId); gl.glDispatchCompute(largeModels, 1, 1); } @@ -926,7 +937,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks SceneTilePaint paint, int tileZ, int tileX, int tileY, int zoom, int centerX, int centerY) { - if (!useComputeShaders) + if (computeMode == ComputeMode.NONE) { targetBufferOffset += sceneUploader.upload(paint, tileZ, tileX, tileY, @@ -963,7 +974,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks SceneTileModel model, int tileZ, int tileX, int tileY, int zoom, int centerX, int centerY) { - if (!useComputeShaders) + if (computeMode == ComputeMode.NONE) { targetBufferOffset += sceneUploader.upload(model, tileX, tileY, @@ -1131,7 +1142,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks // Ceil the sizes because even if the size is 599.1 we want to treat it as size 600 (i.e. render to the x=599 pixel). renderViewportHeight = (int) Math.ceil(scaleFactorY * (renderViewportHeight)) + padding * 2; - renderViewportWidth = (int) Math.ceil(scaleFactorX * (renderViewportWidth )) + padding * 2; + renderViewportWidth = (int) Math.ceil(scaleFactorX * (renderViewportWidth )) + padding * 2; // Floor the offsets because even if the offset is 4.9, we want to render to the x=4 pixel anyway. renderHeightOff = (int) Math.floor(scaleFactorY * (renderHeightOff)) - padding; @@ -1195,27 +1206,36 @@ public class GpuPlugin extends Plugin implements DrawCallbacks gl.glBindVertexArray(vaoHandle); int vertexBuffer, uvBuffer; - if (useComputeShaders) + if (computeMode != ComputeMode.NONE) { - // Before reading the SSBOs written to from postDrawScene() we must insert a barrier - gl.glMemoryBarrier(gl.GL_SHADER_STORAGE_BARRIER_BIT); + if (computeMode == ComputeMode.OPENGL) + { + // Before reading the SSBOs written to from postDrawScene() we must insert a barrier + gl.glMemoryBarrier(gl.GL_SHADER_STORAGE_BARRIER_BIT); + } + else + { + // Wait for the command queue to finish, so that we know the compute is done + openCLManager.finish(); + } + // Draw using the output buffer of the compute - vertexBuffer = tmpOutBufferId; - uvBuffer = tmpOutUvBufferId; + vertexBuffer = tmpOutBuffer.glBufferId; + uvBuffer = tmpOutUvBuffer.glBufferId; } else { // Only use the temporary buffers, which will contain the full scene - vertexBuffer = tmpBufferId; - uvBuffer = tmpUvBufferId; + vertexBuffer = tmpVertexBuffer.glBufferId; + uvBuffer = tmpUvBuffer.glBufferId; } gl.glEnableVertexAttribArray(0); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vertexBuffer); + gl.glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); gl.glVertexAttribIPointer(0, 4, gl.GL_INT, 0, 0); gl.glEnableVertexAttribArray(1); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, uvBuffer); + gl.glBindBuffer(GL_ARRAY_BUFFER, uvBuffer); gl.glVertexAttribPointer(1, 4, gl.GL_FLOAT, false, 0, 0); gl.glDrawArrays(gl.GL_TRIANGLES, 0, targetBufferOffset); @@ -1400,12 +1420,12 @@ public class GpuPlugin extends Plugin implements DrawCallbacks @Subscribe public void onGameStateChanged(GameStateChanged gameStateChanged) { - if (!useComputeShaders || gameStateChanged.getGameState() != GameState.LOGGED_IN) + if (computeMode == ComputeMode.NONE || gameStateChanged.getGameState() != GameState.LOGGED_IN) { return; } - uploadScene(); + invokeOnMainThread(this::uploadScene); } private void uploadScene() @@ -1421,13 +1441,10 @@ public class GpuPlugin extends Plugin implements DrawCallbacks IntBuffer vertexBuffer = this.vertexBuffer.getBuffer(); FloatBuffer uvBuffer = this.uvBuffer.getBuffer(); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, bufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, gl.GL_STATIC_COPY); + updateBuffer(sceneVertexBuffer, GL_ARRAY_BUFFER, vertexBuffer.limit() * Integer.BYTES, vertexBuffer, GL_STATIC_COPY, CL_MEM_READ_ONLY); + updateBuffer(sceneUvBuffer, GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, GL_STATIC_COPY, CL_MEM_READ_ONLY); - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, uvBufferId); - gl.glBufferData(gl.GL_ARRAY_BUFFER, uvBuffer.limit() * Float.BYTES, uvBuffer, gl.GL_STATIC_COPY); - - gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0); + gl.glBindBuffer(GL_ARRAY_BUFFER, 0); vertexBuffer.clear(); uvBuffer.clear(); @@ -1492,7 +1509,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks @Override public void draw(Renderable renderable, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash) { - if (!useComputeShaders) + if (computeMode == ComputeMode.NONE) { Model model = renderable instanceof Model ? (Model) renderable : renderable.getModel(); if (model != null) @@ -1673,7 +1690,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private int getDrawDistance() { - final int limit = useComputeShaders ? MAX_DISTANCE : DEFAULT_DISTANCE; + final int limit = computeMode != ComputeMode.NONE ? MAX_DISTANCE : DEFAULT_DISTANCE; return Ints.constrainToRange(config.drawDistance(), 0, limit); } @@ -1688,4 +1705,36 @@ public class GpuPlugin extends Plugin implements DrawCallbacks runnable.run(); } } + + private void updateBuffer(GLBuffer glBuffer, int target, int size, Buffer data, int usage, long clFlags) + { + gl.glBindBuffer(target, glBuffer.glBufferId); + if (size > glBuffer.size) + { + log.trace("Buffer resize: {} {} -> {}", glBuffer, glBuffer.size, size); + + glBuffer.size = size; + gl.glBufferData(target, size, data, usage); + + if (computeMode == ComputeMode.OPENCL) + { + if (glBuffer.cl_mem != null) + { + CL.clReleaseMemObject(glBuffer.cl_mem); + } + if (size == 0) + { + glBuffer.cl_mem = null; + } + else + { + glBuffer.cl_mem = clCreateFromGLBuffer(openCLManager.context, clFlags, glBuffer.glBufferId, null); + } + } + } + else if (data != null) + { + gl.glBufferSubData(target, 0, size, data); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/OpenCLManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/OpenCLManager.java new file mode 100644 index 0000000000..77c2f9b575 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/OpenCLManager.java @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2021, Adam + * 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.gpu; + +import com.google.common.base.Charsets; +import com.jogamp.nativewindow.NativeSurface; +import com.jogamp.opengl.GL4; +import com.jogamp.opengl.GLContext; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; +import javax.inject.Singleton; +import jogamp.opengl.GLContextImpl; +import jogamp.opengl.GLDrawableImpl; +import jogamp.opengl.egl.EGLContext; +import jogamp.opengl.macosx.cgl.CGL; +import jogamp.opengl.windows.wgl.WindowsWGLContext; +import jogamp.opengl.x11.glx.X11GLXContext; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.gpu.template.Template; +import net.runelite.client.util.OSType; +import org.jocl.CL; +import static org.jocl.CL.*; +import org.jocl.CLException; +import org.jocl.Pointer; +import org.jocl.Sizeof; +import org.jocl.cl_command_queue; +import org.jocl.cl_context; +import org.jocl.cl_context_properties; +import org.jocl.cl_device_id; +import org.jocl.cl_event; +import org.jocl.cl_kernel; +import org.jocl.cl_mem; +import org.jocl.cl_platform_id; +import org.jocl.cl_program; + +@Singleton +@Slf4j +class OpenCLManager +{ + private static final String GL_SHARING_PLATFORM_EXT = "cl_khr_gl_sharing"; + + private static final String KERNEL_NAME_UNORDERED = "computeUnordered"; + private static final String KERNEL_NAME_LARGE = "computeLarge"; + + private static final int MIN_WORK_GROUP_SIZE = 256; + private static final int SMALL_SIZE = GpuPlugin.SMALL_TRIANGLE_COUNT; + private static final int LARGE_SIZE = GpuPlugin.MAX_TRIANGLE; + // struct shared_data { + // int totalNum[12]; + // int totalDistance[12]; + // int totalMappedNum[18]; + // int min10; + // int dfs[0]; + // }; + private static final int SHARED_SIZE = 12 + 12 + 18 + 1; // in ints + + // The number of faces each worker processes in the two kernels + private int largeFaceCount; + private int smallFaceCount; + + private cl_platform_id platform; + private cl_device_id device; + cl_context context; + private cl_command_queue commandQueue; + + private cl_program programUnordered; + private cl_program programSmall; + private cl_program programLarge; + + private cl_kernel kernelUnordered; + private cl_kernel kernelSmall; + private cl_kernel kernelLarge; + + void init(GL4 gl) + { + CL.setExceptionsEnabled(true); + + switch (OSType.getOSType()) + { + case Windows: + case Linux: + initPlatform(); + initDevice(); + initContext(gl); + break; + case MacOS: + initMacOS(gl); + break; + default: + throw new RuntimeException("Unsupported OS Type " + OSType.getOSType().name()); + } + ensureMinWorkGroupSize(); + initQueue(); + compilePrograms(); + } + + void cleanup() + { + if (programUnordered != null) + { + CL.clReleaseProgram(programUnordered); + programUnordered = null; + } + + if (programSmall != null) + { + CL.clReleaseProgram(programSmall); + programSmall = null; + } + + if (programLarge != null) + { + CL.clReleaseProgram(programLarge); + programLarge = null; + } + + if (kernelUnordered != null) + { + CL.clReleaseKernel(kernelUnordered); + kernelUnordered = null; + } + + if (kernelSmall != null) + { + CL.clReleaseKernel(kernelSmall); + kernelSmall = null; + } + + if (kernelLarge != null) + { + CL.clReleaseKernel(kernelLarge); + kernelLarge = null; + } + + if (commandQueue != null) + { + CL.clReleaseCommandQueue(commandQueue); + commandQueue = null; + } + + if (context != null) + { + CL.clReleaseContext(context); + context = null; + } + + if (device != null) + { + CL.clReleaseDevice(device); + device = null; + } + } + + private String logPlatformInfo(cl_platform_id platform, int param) + { + long[] size = new long[1]; + clGetPlatformInfo(platform, param, 0, null, size); + + byte[] buffer = new byte[(int) size[0]]; + clGetPlatformInfo(platform, param, buffer.length, Pointer.to(buffer), null); + String platformInfo = new String(buffer, Charsets.UTF_8); + log.debug("Platform: {}, {}", stringFor_cl_platform_info(param), platformInfo); + return platformInfo; + } + + private void logBuildInfo(cl_program program, int param) + { + long[] size = new long[1]; + clGetProgramBuildInfo(program, device, param, 0, null, size); + + ByteBuffer buffer = ByteBuffer.allocateDirect((int) size[0]); + clGetProgramBuildInfo(program, device, param, buffer.limit(), Pointer.toBuffer(buffer), null); + + switch (param) + { + case CL_PROGRAM_BUILD_STATUS: + log.debug("Build status: {}, {}", stringFor_cl_program_build_info(param), stringFor_cl_build_status(buffer.getInt())); + break; + case CL_PROGRAM_BINARY_TYPE: + log.debug("Binary type: {}, {}", stringFor_cl_program_build_info(param), stringFor_cl_program_binary_type(buffer.getInt())); + break; + case CL_PROGRAM_BUILD_LOG: + String buildLog = StandardCharsets.US_ASCII.decode(buffer).toString(); + log.trace("Build log: {}, {}", stringFor_cl_program_build_info(param), buildLog); + break; + case CL_PROGRAM_BUILD_OPTIONS: + String message = StandardCharsets.US_ASCII.decode(buffer).toString(); + log.debug("Build options: {}, {}", stringFor_cl_program_build_info(param), message); + break; + default: + throw new IllegalArgumentException(); + } + } + + private void initPlatform() + { + int[] platformCount = new int[1]; + clGetPlatformIDs(0, null, platformCount); + if (platformCount[0] == 0) + { + throw new RuntimeException("No compute platforms found"); + } + + cl_platform_id[] platforms = new cl_platform_id[platformCount[0]]; + clGetPlatformIDs(platforms.length, platforms, null); + + for (cl_platform_id platform : platforms) + { + log.debug("Found cl_platform_id {}", platform); + logPlatformInfo(platform, CL_PLATFORM_PROFILE); + logPlatformInfo(platform, CL_PLATFORM_VERSION); + logPlatformInfo(platform, CL_PLATFORM_NAME); + logPlatformInfo(platform, CL_PLATFORM_VENDOR); + String[] extensions = logPlatformInfo(platform, CL_PLATFORM_EXTENSIONS).split(" "); + if (Arrays.stream(extensions).noneMatch(s -> s.equals(GL_SHARING_PLATFORM_EXT))) + { + throw new RuntimeException("Platform does not support OpenGL buffer sharing"); + } + } + + platform = platforms[0]; + log.debug("Selected cl_platform_id {}", platform); + } + + private void initDevice() + { + int[] deviceCount = new int[1]; + clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, null, deviceCount); + if (deviceCount[0] == 0) + { + throw new RuntimeException("No compute devices found"); + } + + cl_device_id[] devices = new cl_device_id[(int) deviceCount[0]]; + clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, devices.length, devices, null); + + for (cl_device_id device : devices) + { + long[] size = new long[1]; + clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, 0, null, size); + + byte[] devInfoBuf = new byte[(int) size[0]]; + clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, devInfoBuf.length, Pointer.to(devInfoBuf), null); + + log.debug("Found cl_device_id: {}", device); + log.debug("Device extensions: {}", new String(devInfoBuf, Charsets.UTF_8)); + } + + device = devices[0]; + log.debug("Selected cl_device_id {}", device); + } + + private void initContext(GL4 gl) + { + // set computation platform + cl_context_properties contextProps = new cl_context_properties(); + contextProps.addProperty(CL_CONTEXT_PLATFORM, platform); + + // pull gl context + GLContext glContext = gl.getContext(); + log.debug("Got GLContext of type {}", glContext.getClass().getSimpleName()); + if (!glContext.isCurrent()) + { + throw new RuntimeException("Can't create OpenCL context from inactive GL Context"); + } + + // get correct props based on os + long glContextHandle = glContext.getHandle(); + GLContextImpl glContextImpl = (GLContextImpl) glContext; + GLDrawableImpl glDrawableImpl = glContextImpl.getDrawableImpl(); + NativeSurface nativeSurface = glDrawableImpl.getNativeSurface(); + + if (glContext instanceof X11GLXContext) + { + long displayHandle = nativeSurface.getDisplayHandle(); + contextProps.addProperty(CL_GL_CONTEXT_KHR, glContextHandle); + contextProps.addProperty(CL_GLX_DISPLAY_KHR, displayHandle); + } + else if (glContext instanceof WindowsWGLContext) + { + long surfaceHandle = nativeSurface.getSurfaceHandle(); + contextProps.addProperty(CL_GL_CONTEXT_KHR, glContextHandle); + contextProps.addProperty(CL_WGL_HDC_KHR, surfaceHandle); + } + else if (glContext instanceof EGLContext) + { + long displayHandle = nativeSurface.getDisplayHandle(); + contextProps.addProperty(CL_GL_CONTEXT_KHR, glContextHandle); + contextProps.addProperty(CL_EGL_DISPLAY_KHR, displayHandle); + } + + log.debug("Creating context with props: {}", contextProps); + context = clCreateContext(contextProps, 1, new cl_device_id[]{device}, null, null, null); + log.debug("Created compute context {}", context); + } + + private void initMacOS(GL4 gl) + { + // get sharegroup from gl context + GLContext glContext = gl.getContext(); + if (!glContext.isCurrent()) + { + throw new RuntimeException("Can't create context from inactive GL"); + } + long cglContext = CGL.CGLGetCurrentContext(); + long cglShareGroup = CGL.CGLGetShareGroup(cglContext); + + // build context props + cl_context_properties contextProps = new cl_context_properties(); + contextProps.addProperty(CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE, cglShareGroup); + + // ask macos to make the context for us + log.debug("Creating context with props: {}", contextProps); + context = clCreateContext(contextProps, 0, null, null, null, null); + + // pull the compute device out of the provided context + device = new cl_device_id(); + clGetGLContextInfoAPPLE(context, cglContext, CL_CGL_DEVICE_FOR_CURRENT_VIRTUAL_SCREEN_APPLE, Sizeof.cl_device_id, Pointer.to(device), null); + + log.debug("Got macOS CLGL compute device {}", device); + } + + private void ensureMinWorkGroupSize() + { + long[] maxWorkGroupSize = new long[1]; + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, Sizeof.size_t, Pointer.to(maxWorkGroupSize), null); + log.debug("Device CL_DEVICE_MAX_WORK_GROUP_SIZE: {}", maxWorkGroupSize[0]); + + if (maxWorkGroupSize[0] < MIN_WORK_GROUP_SIZE) + { + throw new RuntimeException("Compute device does not support min work group size " + MIN_WORK_GROUP_SIZE); + } + + // Largest power of 2 less than or equal to maxWorkGroupSize + int groupSize = 0x80000000 >>> Integer.numberOfLeadingZeros((int) maxWorkGroupSize[0]); + largeFaceCount = LARGE_SIZE / (Math.min(groupSize, LARGE_SIZE)); + smallFaceCount = SMALL_SIZE / (Math.min(groupSize, SMALL_SIZE)); + + log.debug("Face counts: small: {}, large: {}", smallFaceCount, largeFaceCount); + } + + private void initQueue() + { + long[] l = new long[1]; + clGetDeviceInfo(device, CL_DEVICE_QUEUE_PROPERTIES, Sizeof.cl_long, Pointer.to(l), null); + + commandQueue = clCreateCommandQueue(context, device, l[0] & CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, null); + log.debug("Created command_queue {}, properties {}", commandQueue, l[0] & CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE); + } + + private cl_program compileProgram(String programSource) + { + log.trace("Compiling program:\n {}", programSource); + cl_program program = clCreateProgramWithSource(context, 1, new String[]{programSource}, null, null); + + try + { + clBuildProgram(program, 0, null, null, null, null); + } + catch (CLException e) + { + logBuildInfo(program, CL_PROGRAM_BUILD_LOG); + throw e; + } + + logBuildInfo(program, CL_PROGRAM_BUILD_STATUS); + logBuildInfo(program, CL_PROGRAM_BINARY_TYPE); + logBuildInfo(program, CL_PROGRAM_BUILD_OPTIONS); + logBuildInfo(program, CL_PROGRAM_BUILD_LOG); + return program; + } + + private cl_kernel getKernel(cl_program program, String kernelName) + { + cl_kernel kernel = clCreateKernel(program, kernelName, null); + log.debug("Loaded kernel {} for program {}", kernelName, program); + return kernel; + } + + private void compilePrograms() + { + Template templateSmall = new Template() + .addInclude(OpenCLManager.class) + .add(key -> key.equals("FACE_COUNT") ? ("#define FACE_COUNT " + smallFaceCount) : null); + Template templateLarge = new Template() + .addInclude(OpenCLManager.class) + .add(key -> key.equals("FACE_COUNT") ? ("#define FACE_COUNT " + largeFaceCount) : null); + + String unordered = new Template() + .addInclude(OpenCLManager.class) + .load("comp_unordered.cl"); + String small = templateSmall.load("comp.cl"); + String large = templateLarge.load("comp.cl"); + + programUnordered = compileProgram(unordered); + programSmall = compileProgram(small); + programLarge = compileProgram(large); + + kernelUnordered = getKernel(programUnordered, KERNEL_NAME_UNORDERED); + kernelSmall = getKernel(programSmall, KERNEL_NAME_LARGE); + kernelLarge = getKernel(programLarge, KERNEL_NAME_LARGE); + } + + void compute(int unorderedModels, int smallModels, int largeModels, + GLBuffer sceneVertexBuffer, + GLBuffer sceneUvBuffer, + GLBuffer vertexBuffer, + GLBuffer uvBuffer, + GLBuffer unorderedBuffer, + GLBuffer smallBuffer, + GLBuffer largeBuffer, + GLBuffer outVertexBuffer, + GLBuffer outUvBuffer, + GLBuffer uniformBuffer + ) + { + cl_mem[] glBuffersAll = { + sceneVertexBuffer.cl_mem, + sceneUvBuffer.cl_mem, + unorderedBuffer.cl_mem, + smallBuffer.cl_mem, + largeBuffer.cl_mem, + vertexBuffer.cl_mem, + uvBuffer.cl_mem, + outVertexBuffer.cl_mem, + outUvBuffer.cl_mem, + uniformBuffer.cl_mem, + }; + cl_mem[] glBuffers = Arrays.stream(glBuffersAll) + .filter(Objects::nonNull) + .toArray(cl_mem[]::new); + + cl_event acquireGLBuffers = new cl_event(); + clEnqueueAcquireGLObjects(commandQueue, glBuffers.length, glBuffers, 0, null, acquireGLBuffers); + + cl_event[] computeEvents = { + new cl_event(), + new cl_event(), + new cl_event() + }; + int numComputeEvents = 0; + + if (unorderedModels > 0) + { + clSetKernelArg(kernelUnordered, 0, Sizeof.cl_mem, unorderedBuffer.ptr()); + clSetKernelArg(kernelUnordered, 1, Sizeof.cl_mem, sceneVertexBuffer.ptr()); + clSetKernelArg(kernelUnordered, 2, Sizeof.cl_mem, vertexBuffer.ptr()); + clSetKernelArg(kernelUnordered, 3, Sizeof.cl_mem, sceneUvBuffer.ptr()); + clSetKernelArg(kernelUnordered, 4, Sizeof.cl_mem, uvBuffer.ptr()); + clSetKernelArg(kernelUnordered, 5, Sizeof.cl_mem, outVertexBuffer.ptr()); + clSetKernelArg(kernelUnordered, 6, Sizeof.cl_mem, outUvBuffer.ptr()); + + // queue compute call after acquireGLBuffers + clEnqueueNDRangeKernel(commandQueue, kernelUnordered, 1, null, + new long[]{unorderedModels * 6L}, new long[]{6}, 1, new cl_event[]{acquireGLBuffers}, computeEvents[numComputeEvents++]); + } + + if (smallModels > 0) + { + clSetKernelArg(kernelSmall, 0, (SHARED_SIZE + SMALL_SIZE) * Integer.BYTES, null); + clSetKernelArg(kernelSmall, 1, Sizeof.cl_mem, smallBuffer.ptr()); + clSetKernelArg(kernelSmall, 2, Sizeof.cl_mem, sceneVertexBuffer.ptr()); + clSetKernelArg(kernelSmall, 3, Sizeof.cl_mem, vertexBuffer.ptr()); + clSetKernelArg(kernelSmall, 4, Sizeof.cl_mem, sceneUvBuffer.ptr()); + clSetKernelArg(kernelSmall, 5, Sizeof.cl_mem, uvBuffer.ptr()); + clSetKernelArg(kernelSmall, 6, Sizeof.cl_mem, outVertexBuffer.ptr()); + clSetKernelArg(kernelSmall, 7, Sizeof.cl_mem, outUvBuffer.ptr()); + clSetKernelArg(kernelSmall, 8, Sizeof.cl_mem, uniformBuffer.ptr()); + + clEnqueueNDRangeKernel(commandQueue, kernelSmall, 1, null, + new long[]{smallModels * (SMALL_SIZE / smallFaceCount)}, new long[]{SMALL_SIZE / smallFaceCount}, 1, new cl_event[]{acquireGLBuffers}, computeEvents[numComputeEvents++]); + } + + if (largeModels > 0) + { + clSetKernelArg(kernelLarge, 0, (SHARED_SIZE + LARGE_SIZE) * Integer.BYTES, null); + clSetKernelArg(kernelLarge, 1, Sizeof.cl_mem, largeBuffer.ptr()); + clSetKernelArg(kernelLarge, 2, Sizeof.cl_mem, sceneVertexBuffer.ptr()); + clSetKernelArg(kernelLarge, 3, Sizeof.cl_mem, vertexBuffer.ptr()); + clSetKernelArg(kernelLarge, 4, Sizeof.cl_mem, sceneUvBuffer.ptr()); + clSetKernelArg(kernelLarge, 5, Sizeof.cl_mem, uvBuffer.ptr()); + clSetKernelArg(kernelLarge, 6, Sizeof.cl_mem, outVertexBuffer.ptr()); + clSetKernelArg(kernelLarge, 7, Sizeof.cl_mem, outUvBuffer.ptr()); + clSetKernelArg(kernelLarge, 8, Sizeof.cl_mem, uniformBuffer.ptr()); + + clEnqueueNDRangeKernel(commandQueue, kernelLarge, 1, null, + new long[]{(long) largeModels * (LARGE_SIZE / largeFaceCount)}, new long[]{LARGE_SIZE / largeFaceCount}, 1, new cl_event[]{acquireGLBuffers}, computeEvents[numComputeEvents++]); + } + + clEnqueueReleaseGLObjects(commandQueue, glBuffers.length, glBuffers, numComputeEvents, computeEvents, null); + } + + void finish() + { + clFinish(commandQueue); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index d28f4b0323..f3d9b513b3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -128,7 +128,6 @@ public class GrandExchangePlugin extends Plugin private static final String BUY_LIMIT_GE_TEXT = "
Buy limit: "; private static final String BUY_LIMIT_KEY = "buylimit"; - private static final Gson GSON = new Gson(); private static final Duration BUY_LIMIT_RESET = Duration.ofHours(4); static final String SEARCH_GRAND_EXCHANGE = "Search Grand Exchange"; @@ -183,6 +182,9 @@ public class GrandExchangePlugin extends Plugin @Inject private ConfigManager configManager; + @Inject + private Gson gson; + private Widget grandExchangeText; private Widget grandExchangeItem; private String grandExchangeExamine; @@ -253,12 +255,12 @@ public class GrandExchangePlugin extends Plugin { return null; } - return GSON.fromJson(offer, SavedOffer.class); + return gson.fromJson(offer, SavedOffer.class); } private void setOffer(int slot, SavedOffer offer) { - configManager.setRSProfileConfiguration("geoffer", Integer.toString(slot), GSON.toJson(offer)); + configManager.setRSProfileConfiguration("geoffer", Integer.toString(slot), gson.toJson(offer)); } private void deleteOffer(int slot) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index e2a69bec33..f565652b11 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -75,12 +75,14 @@ public class GroundItemsOverlay extends Overlay private static final Duration DESPAWN_TIME_INSTANCE = Duration.ofMinutes(30); private static final Duration DESPAWN_TIME_LOOT = Duration.ofMinutes(2); private static final Duration DESPAWN_TIME_DROP = Duration.ofMinutes(3); + private static final Duration DESPAWN_TIME_TABLE = Duration.ofMinutes(10); private static final int KRAKEN_REGION = 9116; private static final int KBD_NMZ_REGION = 9033; private static final int ZILYANA_REGION = 11602; private static final int GRAARDOR_REGION = 11347; private static final int KRIL_TSUTSAROTH_REGION = 11603; private static final int KREEARRA_REGION = 11346; + private static final int NIGHTMARE_REGION = 15515; private final Client client; private final GroundItemsPlugin plugin; @@ -397,8 +399,11 @@ public class GroundItemsOverlay extends Overlay private Instant calculateDespawnTime(GroundItem groundItem) { - // We can only accurately guess despawn times for our own pvm loot and dropped items - if (groundItem.getLootType() != LootType.PVM && groundItem.getLootType() != LootType.DROPPED) + // We can only accurately guess despawn times for our own pvm loot, dropped items, + // and items we placed on tables + if (groundItem.getLootType() != LootType.PVM + && groundItem.getLootType() != LootType.DROPPED + && groundItem.getLootType() != LootType.TABLE) { return null; } @@ -447,9 +452,9 @@ public class GroundItemsOverlay extends Overlay } } else if (playerRegionID == ZILYANA_REGION || playerRegionID == GRAARDOR_REGION || - playerRegionID == KRIL_TSUTSAROTH_REGION || playerRegionID == KREEARRA_REGION) + playerRegionID == KRIL_TSUTSAROTH_REGION || playerRegionID == KREEARRA_REGION || playerRegionID == NIGHTMARE_REGION) { - // GWD instances use the normal despawn timers + // GWD and Nightmare instances use the normal despawn timers despawnTime = spawnTime.plus(groundItem.getLootType() == LootType.DROPPED ? DESPAWN_TIME_DROP : DESPAWN_TIME_LOOT); @@ -461,9 +466,18 @@ public class GroundItemsOverlay extends Overlay } else { - despawnTime = spawnTime.plus(groundItem.getLootType() == LootType.DROPPED - ? DESPAWN_TIME_DROP - : DESPAWN_TIME_LOOT); + switch (groundItem.getLootType()) + { + case DROPPED: + despawnTime = spawnTime.plus(DESPAWN_TIME_DROP); + break; + case TABLE: + despawnTime = spawnTime.plus(DESPAWN_TIME_TABLE); + break; + default: + despawnTime = spawnTime.plus(DESPAWN_TIME_LOOT); + break; + } } if (now.isBefore(spawnTime) || now.isAfter(despawnTime)) @@ -477,8 +491,11 @@ public class GroundItemsOverlay extends Overlay private Color getItemTimerColor(GroundItem groundItem) { - // We can only accurately guess despawn times for our own pvm loot and dropped items - if (groundItem.getLootType() != LootType.PVM && groundItem.getLootType() != LootType.DROPPED) + // We can only accurately guess despawn times for our own pvm loot, dropped items, + // and items we placed on tables + if (groundItem.getLootType() != LootType.PVM + && groundItem.getLootType() != LootType.DROPPED + && groundItem.getLootType() != LootType.TABLE) { return null; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index 0c8db3a0e1..b7a8fc6500 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -52,11 +52,13 @@ import lombok.Setter; import lombok.Value; import net.runelite.api.Client; import net.runelite.api.GameState; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; import net.runelite.api.ItemID; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; -import net.runelite.api.Player; import net.runelite.api.Tile; import net.runelite.api.TileItem; import net.runelite.api.coords.WorldPoint; @@ -180,6 +182,7 @@ public class GroundItemsPlugin extends Plugin private LoadingCache highlightedItems; private LoadingCache hiddenItems; private final Queue droppedItemQueue = EvictingQueue.create(16); // recently dropped items + private int lastUsedItem; @Provides GroundItemsConfig provideConfig(ConfigManager configManager) @@ -194,6 +197,7 @@ public class GroundItemsPlugin extends Plugin mouseManager.registerMouseListener(inputListener); keyManager.registerKeyListener(inputListener); executor.execute(this::reset); + lastUsedItem = -1; } @Override @@ -384,6 +388,7 @@ public class GroundItemsPlugin extends Plugin final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemId; final int alchPrice = itemComposition.getHaPrice(); final boolean dropped = tile.getWorldLocation().equals(client.getLocalPlayer().getWorldLocation()) && droppedItemQueue.remove(itemId); + final boolean table = itemId == lastUsedItem && tile.getItemLayer().getHeight() > 0; final GroundItem groundItem = GroundItem.builder() .id(itemId) @@ -394,12 +399,11 @@ public class GroundItemsPlugin extends Plugin .haPrice(alchPrice) .height(tile.getItemLayer().getHeight()) .tradeable(itemComposition.isTradeable()) - .lootType(dropped ? LootType.DROPPED : LootType.UNKNOWN) + .lootType(dropped ? LootType.DROPPED : (table ? LootType.TABLE : LootType.UNKNOWN)) .spawnTime(Instant.now()) .stackable(itemComposition.isStackable()) .build(); - // Update item price in case it is coins if (realItemId == COINS) { @@ -638,11 +642,8 @@ public class GroundItemsPlugin extends Plugin return; } - final Player local = client.getLocalPlayer(); final StringBuilder notificationStringBuilder = new StringBuilder() - .append('[') - .append(local.getName()) - .append("] received a ") + .append("You received a ") .append(dropType) .append(" drop: ") .append(item.getName()); @@ -687,5 +688,21 @@ public class GroundItemsPlugin extends Plugin // item spawns that are drops droppedItemQueue.add(itemId); } + else if (menuOptionClicked.getMenuAction() == MenuAction.ITEM_USE_ON_GAME_OBJECT) + { + final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); + if (inventory == null) + { + return; + } + + final Item clickedItem = inventory.getItem(menuOptionClicked.getSelectedItemIndex()); + if (clickedItem == null) + { + return; + } + + lastUsedItem = clickedItem.getId(); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java index b434298faf..604815732f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/LootType.java @@ -27,6 +27,7 @@ package net.runelite.client.plugins.grounditems; enum LootType { UNKNOWN, + TABLE, DROPPED, PVP, PVM; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java index c1e1774931..81ac210bfc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; import lombok.AccessLevel; @@ -73,8 +74,6 @@ public class GroundMarkerPlugin extends Plugin private static final String WALK_HERE = "Walk here"; private static final String REGION_PREFIX = "region_"; - private static final Gson GSON = new Gson(); - @Getter(AccessLevel.PACKAGE) private final List points = new ArrayList<>(); @@ -105,6 +104,9 @@ public class GroundMarkerPlugin extends Plugin @Inject private GroundMarkerSharingManager sharingManager; + @Inject + private Gson gson; + void savePoints(int regionId, Collection points) { if (points == null || points.isEmpty()) @@ -113,7 +115,7 @@ public class GroundMarkerPlugin extends Plugin return; } - String json = GSON.toJson(points); + String json = gson.toJson(points); configManager.setConfiguration(CONFIG_GROUP, REGION_PREFIX + regionId, json); } @@ -126,7 +128,7 @@ public class GroundMarkerPlugin extends Plugin } // CHECKSTYLE:OFF - return GSON.fromJson(json, new TypeToken>(){}.getType()); + return gson.fromJson(json, new TypeToken>(){}.getType()); // CHECKSTYLE:ON } @@ -312,21 +314,22 @@ public class GroundMarkerPlugin extends Plugin WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, localPoint); final int regionId = worldPoint.getRegionID(); + GroundMarkerPoint searchPoint = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), null, null); + Collection points = getPoints(regionId); + GroundMarkerPoint existing = points.stream() + .filter(p -> p.equals(searchPoint)) + .findFirst().orElse(null); + if (existing == null) + { + return; + } + chatboxPanelManager.openTextInput("Tile label") + .value(Optional.ofNullable(existing.getLabel()).orElse("")) .onDone((input) -> { input = Strings.emptyToNull(input); - GroundMarkerPoint searchPoint = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), null, null); - Collection points = getPoints(regionId); - GroundMarkerPoint existing = points.stream() - .filter(p -> p.equals(searchPoint)) - .findFirst().orElse(null); - if (existing == null) - { - return; - } - GroundMarkerPoint newPoint = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), client.getPlane(), existing.getColor(), input); points.remove(searchPoint); points.add(newPoint); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java index 8899a81f0d..91c0e800ac 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerSharingManager.java @@ -53,7 +53,6 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.menus.MenuManager; import net.runelite.client.menus.WidgetMenuOption; -import net.runelite.http.api.RuneLiteAPI; @Slf4j class GroundMarkerSharingManager @@ -61,22 +60,23 @@ class GroundMarkerSharingManager private static final WidgetMenuOption EXPORT_MARKERS_OPTION = new WidgetMenuOption("Export", "Ground Markers", WORLD_MAP_OPTION); private static final WidgetMenuOption IMPORT_MARKERS_OPTION = new WidgetMenuOption("Import", "Ground Markers", WORLD_MAP_OPTION); - private static final Gson GSON = RuneLiteAPI.GSON; - private final GroundMarkerPlugin plugin; private final Client client; private final MenuManager menuManager; private final ChatMessageManager chatMessageManager; private final ChatboxPanelManager chatboxPanelManager; + private final Gson gson; @Inject - private GroundMarkerSharingManager(GroundMarkerPlugin plugin, Client client, MenuManager menuManager, ChatMessageManager chatMessageManager, ChatboxPanelManager chatboxPanelManager) + private GroundMarkerSharingManager(GroundMarkerPlugin plugin, Client client, MenuManager menuManager, + ChatMessageManager chatMessageManager, ChatboxPanelManager chatboxPanelManager, Gson gson) { this.plugin = plugin; this.client = client; this.menuManager = menuManager; this.chatMessageManager = chatMessageManager; this.chatboxPanelManager = chatboxPanelManager; + this.gson = gson; } void addMenuOptions() @@ -135,7 +135,7 @@ class GroundMarkerSharingManager return; } - final String exportDump = GSON.toJson(activePoints); + final String exportDump = gson.toJson(activePoints); log.debug("Exported ground markers: {}", exportDump); @@ -173,7 +173,7 @@ class GroundMarkerSharingManager try { // CHECKSTYLE:OFF - importPoints = GSON.fromJson(clipboardText, new TypeToken>(){}.getType()); + importPoints = gson.fromJson(clipboardText, new TypeToken>(){}.getType()); // CHECKSTYLE:ON } catch (JsonSyntaxException e) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java index e9d2d01896..6d7dcec442 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java @@ -147,7 +147,7 @@ public class IdleNotifierPlugin extends Plugin case COOKING_FIRE: case COOKING_RANGE: case COOKING_WINE: - /* Crafting(Gem Cutting, Glassblowing, Spinning, Battlestaves, Pottery) */ + /* Crafting(Gem Cutting, Glassblowing, Spinning, Weaving, Battlestaves, Pottery) */ case GEM_CUTTING_OPAL: case GEM_CUTTING_JADE: case GEM_CUTTING_REDTOPAZ: @@ -158,6 +158,7 @@ public class IdleNotifierPlugin extends Plugin case GEM_CUTTING_AMETHYST: case CRAFTING_GLASSBLOWING: case CRAFTING_SPINNING: + case CRAFTING_LOOM: case CRAFTING_BATTLESTAVES: case CRAFTING_LEATHER: case CRAFTING_POTTERS_WHEEL: @@ -268,6 +269,10 @@ public class IdleNotifierPlugin extends Plugin case MAGIC_ENCHANTING_BOLTS: /* Prayer */ case USING_GILDED_ALTAR: + case ECTOFUNTUS_FILL_SLIME_BUCKET: + case ECTOFUNTUS_INSERT_BONES: + case ECTOFUNTUS_GRIND_BONES: + case ECTOFUNTUS_EMPTY_BIN: /* Farming */ case FARMING_MIX_ULTRACOMPOST: case FARMING_HARVEST_BUSH: @@ -433,64 +438,64 @@ public class IdleNotifierPlugin extends Plugin if (config.logoutIdle() && checkIdleLogout()) { - notifier.notify("[" + local.getName() + "] is about to log out from idling too long!"); + notifier.notify("You are about to log out from idling too long!"); } if (check6hrLogout()) { - notifier.notify("[" + local.getName() + "] is about to log out from being online for 6 hours!"); + notifier.notify("You are about to log out from being online for 6 hours!"); } if (config.animationIdle() && checkAnimationIdle(waitDuration, local)) { - notifier.notify("[" + local.getName() + "] is now idle!"); + notifier.notify("You are now idle!"); } if (config.movementIdle() && checkMovementIdle(waitDuration, local)) { - notifier.notify("[" + local.getName() + "] has stopped moving!"); + notifier.notify("You have stopped moving!"); } if (config.interactionIdle() && checkInteractionIdle(waitDuration, local)) { if (lastInteractWasCombat) { - notifier.notify("[" + local.getName() + "] is now out of combat!"); + notifier.notify("You are now out of combat!"); } else { - notifier.notify("[" + local.getName() + "] is now idle!"); + notifier.notify("You are now idle!"); } } if (checkLowHitpoints()) { - notifier.notify("[" + local.getName() + "] has low hitpoints!"); + notifier.notify("You have low hitpoints!"); } if (checkLowPrayer()) { - notifier.notify("[" + local.getName() + "] has low prayer!"); + notifier.notify("You have low prayer!"); } if (checkLowEnergy()) { - notifier.notify("[" + local.getName() + "] has low run energy!"); + notifier.notify("You have low run energy!"); } if (checkHighEnergy()) { - notifier.notify("[" + local.getName() + "] has restored run energy!"); + notifier.notify("You have restored run energy!"); } if (checkLowOxygen()) { - notifier.notify("[" + local.getName() + "] has low oxygen!"); + notifier.notify("You have low oxygen!"); } if (checkFullSpecEnergy()) { - notifier.notify("[" + local.getName() + "] has restored spec energy!"); + notifier.notify("You have restored spec energy!"); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java index c59fa78798..2386bb8c45 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -27,6 +27,7 @@ package net.runelite.client.plugins.info; import com.google.common.base.MoreObjects; import com.google.inject.Inject; +import com.openosrs.client.OpenOSRS; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; @@ -135,8 +136,11 @@ public class InfoPanel extends PluginPanel final Font smallFont = FontManager.getRunescapeSmallFont(); - JLabel version = new JLabel(htmlLabel("RuneLite version: ", runeliteVersion)); - version.setFont(smallFont); + JLabel rlVersion = new JLabel(htmlLabel("RuneLite version: ", runeliteVersion)); + rlVersion.setFont(smallFont); + + JLabel oprsVersion = new JLabel(htmlLabel("OpenOSRS version: ", OpenOSRS.SYSTEM_VERSION)); + oprsVersion.setFont(smallFont); JLabel revision = new JLabel(); revision.setFont(smallFont); @@ -147,7 +151,7 @@ public class InfoPanel extends PluginPanel engineVer = String.format("Rev %d", client.getRevision()); } - revision.setText(htmlLabel("Oldschool revision: ", engineVer)); + revision.setText(htmlLabel("OldSchool revision: ", engineVer)); JLabel launcher = new JLabel(htmlLabel("Launcher version: ", MoreObjects .firstNonNull(RuneLiteProperties.getLauncherVersion(), "Unknown"))); @@ -170,7 +174,8 @@ public class InfoPanel extends PluginPanel } }); - versionPanel.add(version); + versionPanel.add(rlVersion); + versionPanel.add(oprsVersion); versionPanel.add(revision); versionPanel.add(launcher); versionPanel.add(Box.createGlue()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 7bf83abca3..f3802e230a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -145,7 +145,8 @@ public class LootTrackerPlugin extends Plugin // Chest loot handling private static final String CHEST_LOOTED_MESSAGE = "You find some treasure in the chest!"; private static final Pattern LARRAN_LOOTED_PATTERN = Pattern.compile("You have opened Larran's (big|small) chest .*"); - private static final String STONE_CHEST_LOOTED_MESSAGE = "You steal some loot from the chest."; + // Used by Stone Chest, Isle of Souls chest, Dark Chest + private static final String OTHER_CHEST_LOOTED_MESSAGE = "You steal some loot from the chest."; private static final String DORGESH_KAAN_CHEST_LOOTED_MESSAGE = "You find treasure inside!"; private static final String GRUBBY_CHEST_LOOTED_MESSAGE = "You have opened the Grubby Chest"; private static final Pattern HAM_CHEST_LOOTED_PATTERN = Pattern.compile("Your (?[a-z]+) key breaks in the lock.*"); @@ -161,6 +162,8 @@ public class LootTrackerPlugin extends Plugin put(10835, "Dorgesh-Kaan Chest"). put(10834, "Dorgesh-Kaan Chest"). put(7323, "Grubby Chest"). + put(8593, "Isle of Souls Chest"). + put(7827, "Dark Chest"). build(); // Shade chest loot handling @@ -186,6 +189,11 @@ public class LootTrackerPlugin extends Plugin put(ObjectID.SILVER_CHEST_4128, "Silver key crimson"). put(ObjectID.SILVER_CHEST_4129, "Silver key black"). put(ObjectID.SILVER_CHEST_4130, "Silver key purple"). + put(ObjectID.GOLD_CHEST, "Gold key red"). + put(ObjectID.GOLD_CHEST_41213, "Gold key brown"). + put(ObjectID.GOLD_CHEST_41214, "Gold key crimson"). + put(ObjectID.GOLD_CHEST_41215, "Gold key black"). + put(ObjectID.GOLD_CHEST_41216, "Gold key purple"). build(); // Hallow Sepulchre Coffin handling @@ -625,7 +633,7 @@ public class LootTrackerPlugin extends Plugin final String message = event.getMessage(); - if (message.equals(CHEST_LOOTED_MESSAGE) || message.equals(STONE_CHEST_LOOTED_MESSAGE) + if (message.equals(CHEST_LOOTED_MESSAGE) || message.equals(OTHER_CHEST_LOOTED_MESSAGE) || message.equals(DORGESH_KAAN_CHEST_LOOTED_MESSAGE) || message.startsWith(GRUBBY_CHEST_LOOTED_MESSAGE) || LARRAN_LOOTED_PATTERN.matcher(message).matches()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java index 603aedf9e3..785c812d85 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java @@ -144,6 +144,17 @@ public interface MenuEntrySwapperConfig extends Config return false; } + @ConfigItem( + keyName = "swapBattlestaves", + name = "Battlestaff", + description = "Swap Wield with Use on Battlestaves without orbs", + section = itemSection + ) + default boolean swapBattlestaves() + { + return false; + } + @ConfigItem( keyName = "swapPrayerBook", name = "Recite-Prayer", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java index b72c138391..4ef686a34c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java @@ -334,6 +334,8 @@ public class MenuEntrySwapperPlugin extends Plugin swap("bury", "use", config::swapBones); + swap("wield", "battlestaff", "use", config::swapBattlestaves); + swap("clean", "use", config::swapHerbs); swap("read", "recite-prayer", config::swapPrayerBook); @@ -361,22 +363,22 @@ public class MenuEntrySwapperPlugin extends Plugin swap("eat", "guzzle", config::swapRockCake); } - private void swap(String option, String swappedOption, Supplier enabled) + public void swap(String option, String swappedOption, Supplier enabled) { swap(option, alwaysTrue(), swappedOption, enabled); } - private void swap(String option, String target, String swappedOption, Supplier enabled) + public void swap(String option, String target, String swappedOption, Supplier enabled) { swap(option, equalTo(target), swappedOption, enabled); } - private void swap(String option, Predicate targetPredicate, String swappedOption, Supplier enabled) + public void swap(String option, Predicate targetPredicate, String swappedOption, Supplier enabled) { swaps.put(option, new Swap(alwaysTrue(), targetPredicate, swappedOption, enabled, true)); } - private void swapContains(String option, Predicate targetPredicate, String swappedOption, Supplier enabled) + public void swapContains(String option, Predicate targetPredicate, String swappedOption, Supplier enabled) { swaps.put(option, new Swap(alwaysTrue(), targetPredicate, swappedOption, enabled, false)); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java index 6f3421b08a..b557cc18b3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/music/MusicPlugin.java @@ -466,6 +466,7 @@ public class MusicPlugin extends Plugin public void update() { + handle.setNoClickThrough(false); handle.setOnDragListener((JavaScriptCallback) this::drag); handle.setOnDragCompleteListener((JavaScriptCallback) this::drag); handle.setHasListener(true); @@ -511,6 +512,9 @@ public class MusicPlugin extends Plugin int level = (x * channel.max) / getWidth(); level = Ints.constrainToRange(level, 0, channel.max); channel.setLevel(level); + + int percent = (int) Math.round((level * 100.0 / channel.getMax())); + sliderTooltip = new Tooltip(channel.getName() + ": " + percent + "%"); } protected int getWidth() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index 46a4a0924d..cfa99092a8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -88,7 +88,6 @@ public class ObjectIndicatorsPlugin extends Plugin private static final String MARK = "Mark object"; private static final String UNMARK = "Unmark object"; - private final Gson GSON = new Gson(); @Getter(AccessLevel.PACKAGE) private final List objects = new ArrayList<>(); private final Map> points = new HashMap<>(); @@ -108,6 +107,9 @@ public class ObjectIndicatorsPlugin extends Plugin @Inject private ObjectIndicatorsConfig config; + @Inject + private Gson gson; + @Provides ObjectIndicatorsConfig provideConfig(ConfigManager configManager) { @@ -428,7 +430,7 @@ public class ObjectIndicatorsPlugin extends Plugin } else { - final String json = GSON.toJson(points); + final String json = gson.toJson(points); configManager.setConfiguration(CONFIG_GROUP, "region_" + id, json); } } @@ -442,7 +444,7 @@ public class ObjectIndicatorsPlugin extends Plugin return null; } - Set points = GSON.fromJson(json, new TypeToken>() + Set points = gson.fromJson(json, new TypeToken>() { }.getType()); // Prior to multiloc support the plugin would mark objects named "null", which breaks diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/OpenOSRSPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/OpenOSRSPlugin.java similarity index 92% rename from runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/OpenOSRSPlugin.java rename to runelite-client/src/main/java/net/runelite/client/plugins/openosrs/OpenOSRSPlugin.java index 4ad7f21184..fcca577c35 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/OpenOSRSPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/OpenOSRSPlugin.java @@ -24,17 +24,16 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -package com.openosrs.client.plugins.openosrs; +package net.runelite.client.plugins.openosrs; import ch.qos.logback.classic.Logger; -import com.openosrs.client.plugins.openosrs.externals.ExternalPluginManagerPanel; import com.openosrs.client.config.OpenOSRSConfig; +import net.runelite.client.plugins.openosrs.externals.ExternalPluginManagerPanel; import java.awt.image.BufferedImage; import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.client.callback.ClientThread; import net.runelite.client.config.Keybind; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; @@ -65,9 +64,6 @@ public class OpenOSRSPlugin extends Plugin @Inject private Client client; - @Inject - private ClientThread clientThread; - @Inject private ClientToolbar clientToolbar; @@ -78,6 +74,10 @@ public class OpenOSRSPlugin extends Plugin @Override public void hotkeyPressed() { + if (client == null) + { + return; + } detach = !detach; client.setOculusOrbState(detach ? 1 : 0); client.setOculusOrbNormalSpeed(detach ? 36 : 12); @@ -91,7 +91,7 @@ public class OpenOSRSPlugin extends Plugin { ExternalPluginManagerPanel panel = injector.getInstance(ExternalPluginManagerPanel.class); - final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "externalmanager_icon.png"); + final BufferedImage icon = ImageUtil.loadImageResource(getClass(), "externalmanager_icon.png"); navButton = NavigationButton.builder() .tooltip("External Plugin Manager") diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/ExternalBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/ExternalBox.java similarity index 98% rename from runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/ExternalBox.java rename to runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/ExternalBox.java index cdaef1c8cd..51c9e94b90 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/ExternalBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/ExternalBox.java @@ -1,4 +1,4 @@ -package com.openosrs.client.plugins.openosrs.externals; +package net.runelite.client.plugins.openosrs.externals; import java.awt.BorderLayout; import java.awt.Color; diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/ExternalPluginManagerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/ExternalPluginManagerPanel.java similarity index 90% rename from runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/ExternalPluginManagerPanel.java rename to runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/ExternalPluginManagerPanel.java index 43211002e5..67212c8cfc 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/ExternalPluginManagerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/ExternalPluginManagerPanel.java @@ -1,6 +1,6 @@ -package com.openosrs.client.plugins.openosrs.externals; +package net.runelite.client.plugins.openosrs.externals; -import com.openosrs.client.plugins.ExternalPluginManager; +import net.runelite.client.plugins.OPRSExternalPluginManager; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -9,7 +9,6 @@ import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.net.MalformedURLException; import java.net.URL; -import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; import javax.swing.ImageIcon; import javax.swing.JCheckBox; @@ -37,26 +36,24 @@ public class ExternalPluginManagerPanel extends PluginPanel static { final BufferedImage addIconRaw = - ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "add_raw_icon.png"); + ImageUtil.loadImageResource(ExternalPluginManagerPanel.class, "add_raw_icon.png"); final BufferedImage addIconGh = ImageUtil - .resizeImage(ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "gh_icon.png"), 14, 14); + .resizeImage(ImageUtil.loadImageResource(ExternalPluginManagerPanel.class, "gh_icon.png"), 14, 14); ADD_ICON_RAW = new ImageIcon(addIconRaw); ADD_HOVER_ICON_RAW = new ImageIcon(ImageUtil.alphaOffset(addIconRaw, 0.53f)); ADD_ICON_GH = new ImageIcon(addIconGh); ADD_HOVER_ICON_GH = new ImageIcon(ImageUtil.alphaOffset(addIconGh, 0.53f)); } - private final ExternalPluginManager externalPluginManager; - private final ScheduledExecutorService executor; + private final OPRSExternalPluginManager externalPluginManager; private final EventBus eventBus; @Inject - private ExternalPluginManagerPanel(ExternalPluginManager externalPluginManager, ScheduledExecutorService executor, EventBus eventBus) + private ExternalPluginManagerPanel(OPRSExternalPluginManager externalPluginManager, EventBus eventBus) { super(false); this.externalPluginManager = externalPluginManager; - this.executor = executor; this.eventBus = eventBus; buildPanel(); @@ -134,7 +131,7 @@ public class ExternalPluginManagerPanel extends PluginPanel return; } - if (ExternalPluginManager.testGHRepository(owner.getText(), name.getText())) + if (OPRSExternalPluginManager.testGHRepository(owner.getText(), name.getText())) { JOptionPane.showMessageDialog(ClientUI.getFrame(), "This doesn't appear to be a valid repository.", "Error!", JOptionPane.ERROR_MESSAGE); @@ -222,7 +219,7 @@ public class ExternalPluginManagerPanel extends PluginPanel return; } - if (ExternalPluginManager.testRepository(urlActual)) + if (OPRSExternalPluginManager.testRepository(urlActual)) { JOptionPane.showMessageDialog(ClientUI.getFrame(), "This doesn't appear to be a valid repository.", "Error!", JOptionPane.ERROR_MESSAGE); @@ -258,7 +255,7 @@ public class ExternalPluginManagerPanel extends PluginPanel { JTabbedPane mainTabPane = new JTabbedPane(); - PluginsPanel pluginPanel = new PluginsPanel(this.externalPluginManager, this.executor, this.eventBus); + PluginsPanel pluginPanel = new PluginsPanel(this.externalPluginManager, this.eventBus); JScrollPane repositoryPanel = wrapContainer(new RepositoryPanel(this.externalPluginManager, this.eventBus)); mainTabPane.add("Plugins", pluginPanel); diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/PluginsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/PluginsPanel.java similarity index 92% rename from runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/PluginsPanel.java rename to runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/PluginsPanel.java index fa3af31ca2..2d27d08173 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/PluginsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/PluginsPanel.java @@ -1,12 +1,10 @@ -package com.openosrs.client.plugins.openosrs.externals; +package net.runelite.client.plugins.openosrs.externals; -import com.openosrs.client.plugins.ExternalPluginManager; +import net.runelite.client.plugins.OPRSExternalPluginManager; import com.google.gson.JsonSyntaxException; -import com.openosrs.client.events.ExternalPluginChanged; -import com.openosrs.client.events.ExternalRepositoryChanged; -import com.openosrs.client.util.DeferredDocumentChangedListener; -import com.openosrs.client.util.ImageUtil; -import com.openosrs.client.util.SwingUtil; +import com.openosrs.client.events.OPRSPluginChanged; +import com.openosrs.client.events.OPRSRepositoryChanged; +import net.runelite.client.util.DeferredDocumentChangedListener; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -25,7 +23,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ScheduledExecutorService; import javax.swing.ImageIcon; import javax.swing.JComboBox; import javax.swing.JLabel; @@ -45,6 +42,9 @@ import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; import net.runelite.client.ui.components.IconTextField; import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.SwingUtil; +import org.pf4j.VersionManager; import org.pf4j.update.PluginInfo; import org.pf4j.update.UpdateManager; import org.pf4j.update.UpdateRepository; @@ -64,7 +64,7 @@ public class PluginsPanel extends JPanel { final BufferedImage addIcon = ImageUtil.recolorImage( - ImageUtil.getResourceStreamFromClass(PluginsPanel.class, "add_icon.png"), ColorScheme.BRAND_BLUE + ImageUtil.loadImageResource(PluginsPanel.class, "add_icon.png"), ColorScheme.BRAND_BLUE ); ADD_ICON = new ImageIcon(addIcon); ADD_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f)); @@ -72,7 +72,7 @@ public class PluginsPanel extends JPanel final BufferedImage deleteImg = ImageUtil.recolorImage( ImageUtil.resizeCanvas( - ImageUtil.getResourceStreamFromClass(PluginsPanel.class, "delete_icon.png"), 14, 14 + ImageUtil.loadImageResource(PluginsPanel.class, "delete_icon.png"), 14, 14 ), ColorScheme.BRAND_BLUE ); DELETE_ICON = new ImageIcon(deleteImg); @@ -82,10 +82,9 @@ public class PluginsPanel extends JPanel DELETE_HOVER_ICON_GRAY = new ImageIcon(ImageUtil.alphaOffset(ImageUtil.grayscaleImage(deleteImg), 0.53f)); } - private final ExternalPluginManager externalPluginManager; + private final OPRSExternalPluginManager externalPluginManager; + private final VersionManager versionManager; private final UpdateManager updateManager; - private final ScheduledExecutorService executor; - private final EventBus eventBus; private final IconTextField searchBar = new IconTextField(); private final JPanel filterwrapper = new JPanel(new BorderLayout(0, 10)); @@ -97,12 +96,11 @@ public class PluginsPanel extends JPanel private JComboBox filterComboBox; private Set deps; - PluginsPanel(ExternalPluginManager externalPluginManager, ScheduledExecutorService executor, EventBus eventBus) + PluginsPanel(OPRSExternalPluginManager externalPluginManager, EventBus eventBus) { this.externalPluginManager = externalPluginManager; + this.versionManager = externalPluginManager.getExternalPluginManager().getVersionManager(); this.updateManager = externalPluginManager.getUpdateManager(); - this.executor = executor; - this.eventBus = eventBus; setLayout(new BorderLayout(0, 10)); setBackground(ColorScheme.DARK_GRAY_COLOR); @@ -123,7 +121,7 @@ public class PluginsPanel extends JPanel } @Subscribe - public void onExternalRepositoryChanged(ExternalRepositoryChanged event) + public void onExternalRepositoryChanged(OPRSRepositoryChanged event) { buildFilter(); reloadPlugins(); @@ -283,7 +281,7 @@ public class PluginsPanel extends JPanel } @Subscribe - private void onExternalPluginChanged(ExternalPluginChanged externalPluginChanged) + private void onExternalPluginChanged(OPRSPluginChanged externalPluginChanged) { String pluginId = externalPluginChanged.getPluginId(); Optional externalBox; @@ -410,6 +408,13 @@ public class PluginsPanel extends JPanel for (PluginInfo pluginInfo : availablePluginsList) { + if (pluginInfo.releases + .stream() + .noneMatch((pluginRelease) -> versionManager.checkVersionConstraint(externalPluginManager.getExternalPluginManager().getSystemVersion(), pluginRelease.requires))) + { + continue; + } + if (!search.equals("") && mismatchesSearchTerms(search, pluginInfo)) { continue; diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/RepositoryBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/RepositoryBox.java similarity index 91% rename from runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/RepositoryBox.java rename to runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/RepositoryBox.java index 9a1171d726..dba7a23832 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/RepositoryBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/RepositoryBox.java @@ -1,8 +1,7 @@ -package com.openosrs.client.plugins.openosrs.externals; +package net.runelite.client.plugins.openosrs.externals; -import com.openosrs.client.plugins.ExternalPluginManager; +import net.runelite.client.plugins.OPRSExternalPluginManager; import com.openosrs.client.ui.JMultilineLabel; -import com.openosrs.client.util.ImageUtil; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -19,6 +18,7 @@ import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; +import net.runelite.client.util.ImageUtil; import net.runelite.client.util.LinkBrowser; import org.pf4j.update.PluginInfo; import org.pf4j.update.UpdateRepository; @@ -37,7 +37,7 @@ public class RepositoryBox extends JPanel final BufferedImage deleteImg = ImageUtil.recolorImage( ImageUtil.resizeCanvas( - ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "delete_icon.png"), 14, 14 + ImageUtil.loadImageResource(ExternalPluginManagerPanel.class, "delete_icon.png"), 14, 14 ), ColorScheme.BRAND_BLUE ); DELETE_ICON = new ImageIcon(deleteImg); @@ -46,14 +46,14 @@ public class RepositoryBox extends JPanel final BufferedImage discordImg = ImageUtil.recolorImage( ImageUtil.resizeCanvas( - ImageUtil.getResourceStreamFromClass(ExternalPluginManagerPanel.class, "discord_icon.png"), 14, 14 + ImageUtil.loadImageResource(ExternalPluginManagerPanel.class, "discord_icon.png"), 14, 14 ), Color.WHITE ); DISCORD_ICON = new ImageIcon(discordImg); DISCORD_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(discordImg, 0.53f)); } - RepositoryBox(ExternalPluginManager externalPluginManager, UpdateRepository updateRepository) + RepositoryBox(OPRSExternalPluginManager externalPluginManager, UpdateRepository updateRepository) { setLayout(new BorderLayout()); setBackground(ColorScheme.DARKER_GRAY_COLOR); diff --git a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/RepositoryPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/RepositoryPanel.java similarity index 73% rename from runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/RepositoryPanel.java rename to runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/RepositoryPanel.java index 1f8973aa88..03a46a751a 100644 --- a/runelite-client/src/main/java/com/openosrs/client/plugins/openosrs/externals/RepositoryPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/openosrs/externals/RepositoryPanel.java @@ -1,7 +1,7 @@ -package com.openosrs.client.plugins.openosrs.externals; +package net.runelite.client.plugins.openosrs.externals; -import com.openosrs.client.plugins.ExternalPluginManager; -import com.openosrs.client.events.ExternalRepositoryChanged; +import net.runelite.client.plugins.OPRSExternalPluginManager; +import com.openosrs.client.events.OPRSRepositoryChanged; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -18,11 +18,11 @@ public class RepositoryPanel extends JPanel @Inject public EventBus eventBus; - private final ExternalPluginManager externalPluginManager; + private final OPRSExternalPluginManager externalPluginManager; private final GridBagConstraints c = new GridBagConstraints(); - RepositoryPanel(ExternalPluginManager externalPluginManager, EventBus eventBus) + RepositoryPanel(OPRSExternalPluginManager externalPluginManager, EventBus eventBus) { this.externalPluginManager = externalPluginManager; @@ -36,7 +36,7 @@ public class RepositoryPanel extends JPanel } @Subscribe - private void onExternalRepositoryChanged(ExternalRepositoryChanged event) + private void onExternalRepositoryChanged(OPRSRepositoryChanged event) { removeAll(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java index 6339ce5486..2160148401 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/regenmeter/RegenMeterPlugin.java @@ -166,7 +166,7 @@ public class RegenMeterPlugin extends Plugin if (config.getNotifyBeforeHpRegenSeconds() > 0 && currentHP < maxHP && shouldNotifyHpRegenThisTick(ticksPerHPRegen)) { - notifier.notify("[" + client.getLocalPlayer().getName() + "] regenerates their next hitpoint soon!"); + notifier.notify("Your next hitpoint will regenerate soon!"); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerPlugin.java index 88dca9d214..5ec747e3f5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerPlugin.java @@ -88,6 +88,9 @@ public class ScreenMarkerPlugin extends Plugin @Inject private ScreenMarkerCreationOverlay overlay; + @Inject + private Gson gson; + @Getter @Inject private ColorPickerManager colorPickerManager; @@ -266,7 +269,6 @@ public class ScreenMarkerPlugin extends Plugin return; } - final Gson gson = new Gson(); final String json = gson .toJson(screenMarkers.stream().map(ScreenMarkerOverlay::getMarker).collect(Collectors.toList())); configManager.setConfiguration(CONFIG_GROUP, CONFIG_KEY, json); @@ -279,7 +281,6 @@ public class ScreenMarkerPlugin extends Plugin return Stream.empty(); } - final Gson gson = new Gson(); final List screenMarkerData = gson.fromJson(json, new TypeToken>() { }.getType()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java index 269eb8554a..44d9d58b51 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java @@ -205,11 +205,23 @@ public interface ScreenshotConfig extends Config return false; } + @ConfigItem( + keyName = "valuableDropThreshold", + name = "Valuable Threshold", + description = "The minimum value to save screenshots of valuable drops.", + position = 14, + section = whatSection + ) + default int valuableDropThreshold() + { + return 0; + } + @ConfigItem( keyName = "untradeableDrop", name = "Screenshot Untradeable drops", description = "Configures whether or not screenshots are automatically taken when you receive an untradeable drop.", - position = 14, + position = 15, section = whatSection ) default boolean screenshotUntradeableDrop() @@ -221,7 +233,7 @@ public interface ScreenshotConfig extends Config keyName = "ccKick", name = "Screenshot Kicks from FC", description = "Take a screenshot when you kick a user from a friends chat.", - position = 15, + position = 16, section = whatSection ) default boolean screenshotKick() @@ -233,7 +245,7 @@ public interface ScreenshotConfig extends Config keyName = "baHighGamble", name = "Screenshot BA high gambles", description = "Take a screenshot of your reward from a high gamble at Barbarian Assault.", - position = 16, + position = 17, section = whatSection ) default boolean screenshotHighGamble() @@ -245,7 +257,7 @@ public interface ScreenshotConfig extends Config keyName = "hotkey", name = "Screenshot hotkey", description = "When you press this key a screenshot will be taken", - position = 17 + position = 18 ) default Keybind hotkey() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java index b199248c39..0dada91ae6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java @@ -99,7 +99,7 @@ public class ScreenshotPlugin extends Plugin private static final Pattern NUMBER_PATTERN = Pattern.compile("([0-9]+)"); private static final Pattern LEVEL_UP_PATTERN = Pattern.compile(".*Your ([a-zA-Z]+) (?:level is|are)? now (\\d+)\\."); private static final Pattern BOSSKILL_MESSAGE_PATTERN = Pattern.compile("Your (.+) kill count is: (\\d+)."); - private static final Pattern VALUABLE_DROP_PATTERN = Pattern.compile(".*Valuable drop: ([^<>]+)(?:)?"); + private static final Pattern VALUABLE_DROP_PATTERN = Pattern.compile(".*Valuable drop: ([^<>]+?\\(((?:\\d+,?)+) coins\\))(?:)?"); private static final Pattern UNTRADEABLE_DROP_PATTERN = Pattern.compile(".*Untradeable drop: ([^<>]+)(?:)?"); private static final Pattern DUEL_END_PATTERN = Pattern.compile("You have now (won|lost) ([0-9]+) duels?\\."); private static final Pattern QUEST_PATTERN_1 = Pattern.compile(".+?ve\\.*? (?been|rebuilt|.+?ed)? ?(?:the )?'?(?.+?)'?(?: [Qq]uest)?[!.]?$"); @@ -417,9 +417,13 @@ public class ScreenshotPlugin extends Plugin Matcher m = VALUABLE_DROP_PATTERN.matcher(chatMessage); if (m.matches()) { - String valuableDropName = m.group(1); - String fileName = "Valuable drop " + valuableDropName; - takeScreenshot(fileName, "Valuable Drops"); + int valuableDropValue = Integer.parseInt(m.group(2).replaceAll(",", "")); + if (valuableDropValue >= config.valuableDropThreshold()) + { + String valuableDropName = m.group(1); + String fileName = "Valuable drop " + valuableDropName; + takeScreenshot(fileName, "Valuable Drops"); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculator.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculator.java index 64422ee34c..bd688d3788 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculator.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/SkillCalculator.java @@ -30,6 +30,8 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.text.DecimalFormat; @@ -95,7 +97,14 @@ class SkillCalculator extends JPanel searchBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); searchBar.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR); searchBar.addClearListener(this::onSearch); - searchBar.addKeyListener(e -> onSearch()); + searchBar.addKeyListener(new KeyAdapter() + { + @Override + public void keyTyped(KeyEvent e) + { + onSearch(); + } + }); setLayout(new DynamicGridLayout(0, 1, 0, 5)); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java index 8e02556440..29867dc042 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java @@ -202,6 +202,7 @@ enum Task "Fremennik Slayer Dungeon", "God Wars Dungeon", "Iorwerth Dungeon", + "Isle of Souls", "Jormungand's Prison", "Kalphite Lair", "Karuulm Slayer Dungeon", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java index fb8572a853..341c1c35a0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java @@ -89,6 +89,7 @@ public class TimersPlugin extends Plugin private static final String CANNON_FURNACE_MESSAGE = "You add the furnace."; private static final String CANNON_PICKUP_MESSAGE = "You pick up the cannon. It's really heavy."; private static final String CANNON_REPAIR_MESSAGE = "You repair your cannon, restoring it to working order."; + private static final String CANNON_DESTROYED_MESSAGE = "Your cannon has been destroyed!"; private static final String CHARGE_EXPIRED_MESSAGE = "Your magical charge fades away."; private static final String CHARGE_MESSAGE = "You feel charged with magic power."; private static final String EXTENDED_ANTIFIRE_DRINK_MESSAGE = "You drink some of your extended antifire potion."; @@ -517,7 +518,7 @@ public class TimersPlugin extends Plugin cannonTimer.setTooltip(cannonTimer.getTooltip() + " - World " + client.getWorld()); } - if (config.showCannon() && message.equals(CANNON_PICKUP_MESSAGE)) + if (config.showCannon() && (message.equals(CANNON_PICKUP_MESSAGE) || message.equals(CANNON_DESTROYED_MESSAGE))) { removeGameTimer(CANNON); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java index dd3a2bba5b..88b6362e26 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java @@ -77,7 +77,7 @@ public abstract class TabContentPanel extends JPanel LocalDateTime currentTime = LocalDateTime.now(); if (endTime.getDayOfWeek() != currentTime.getDayOfWeek()) { - sb.append(endTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" "); + sb.append(endTime.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.getDefault())).append(" "); } sb.append("at "); sb.append(formatter.format(endTime)); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java index 28704ff3ce..aa284ee86c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java @@ -41,6 +41,7 @@ public interface TimeTrackingConfig extends Config String TIMERS = "timers"; String STOPWATCHES = "stopwatches"; String PREFER_SOONEST = "preferSoonest"; + String NOTIFY = "notify"; @ConfigItem( keyName = "timeFormatMode", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 310c2e4e4a..7eee98df8c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -29,7 +29,6 @@ import com.google.inject.Inject; import com.google.inject.Provides; import java.awt.image.BufferedImage; import java.time.Instant; -import java.time.temporal.ChronoUnit; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -58,7 +57,6 @@ import net.runelite.client.plugins.timetracking.clocks.ClockManager; import net.runelite.client.plugins.timetracking.farming.FarmingContractManager; import net.runelite.client.plugins.timetracking.farming.FarmingTracker; import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker; -import net.runelite.client.task.Schedule; import net.runelite.client.ui.ClientToolbar; import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; @@ -102,6 +100,8 @@ public class TimeTrackingPlugin extends Plugin private ScheduledFuture panelUpdateFuture; + private ScheduledFuture notifierFuture; + private TimeTrackingPanel panel; private NavigationButton navButton; @@ -139,6 +139,7 @@ public class TimeTrackingPlugin extends Plugin clientToolbar.addNavigation(navButton); panelUpdateFuture = executorService.scheduleAtFixedRate(this::updatePanel, 200, 200, TimeUnit.MILLISECONDS); + notifierFuture = executorService.scheduleAtFixedRate(this::checkCompletion, 10, 10, TimeUnit.SECONDS); } @Override @@ -153,6 +154,7 @@ public class TimeTrackingPlugin extends Plugin panelUpdateFuture = null; } + notifierFuture.cancel(true); clientToolbar.removeNavigation(navButton); infoBoxManager.removeInfoBox(farmingContractManager.getInfoBox()); farmingContractManager.setInfoBox(null); @@ -260,8 +262,7 @@ public class TimeTrackingPlugin extends Plugin } } - @Schedule(period = 10, unit = ChronoUnit.SECONDS) - public void checkCompletion() + private void checkCompletion() { boolean birdHouseDataChanged = birdHouseTracker.checkCompletion(); @@ -269,6 +270,8 @@ public class TimeTrackingPlugin extends Plugin { panel.update(); } + + farmingTracker.checkCompletion(); } private void updatePanel() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java index e7584a6e27..9796146500 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java @@ -29,22 +29,29 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.GridLayout; +import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JToggleButton; import javax.swing.border.EmptyBorder; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Constants; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.components.ThinProgressBar; import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; +import net.runelite.client.util.ImageUtil; +import net.runelite.client.util.SwingUtil; +@Slf4j @Getter public class TimeablePanel extends JPanel { private final T timeable; private final JLabel icon = new JLabel(); private final JLabel farmingContractIcon = new JLabel(); + private final JToggleButton notifyButton = new JToggleButton(); private final JLabel estimate = new JLabel(); private final ThinProgressBar progress = new ThinProgressBar(); private final JLabel text; @@ -79,8 +86,29 @@ public class TimeablePanel extends JPanel infoPanel.add(text); infoPanel.add(estimate); + ImageIcon notifyIcon = new ImageIcon(ImageUtil.loadImageResource(TimeTrackingPlugin.class, "notify_icon.png")); + ImageIcon notifySelectedIcon = new ImageIcon(ImageUtil.loadImageResource(TimeTrackingPlugin.class, "notify_selected_icon.png")); + + notifyButton.setPreferredSize(new Dimension(30, 16)); + notifyButton.setBorder(new EmptyBorder(0, 0, 0, 10)); + notifyButton.setIcon(notifyIcon); + notifyButton.setSelectedIcon(notifySelectedIcon); + SwingUtil.removeButtonDecorations(notifyButton); + SwingUtil.addModalTooltip(notifyButton, "Disable notifications", "Enable notifications"); + + JPanel notifyPanel = new JPanel(); + notifyPanel.setLayout(new BorderLayout()); + notifyPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + notifyPanel.add(notifyButton, BorderLayout.CENTER); + + JPanel iconPanel = new JPanel(); + iconPanel.setLayout(new BorderLayout()); + iconPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); + iconPanel.add(notifyPanel, BorderLayout.EAST); + iconPanel.add(farmingContractIcon, BorderLayout.WEST); + + topContainer.add(iconPanel, BorderLayout.EAST); topContainer.add(icon, BorderLayout.WEST); - topContainer.add(farmingContractIcon, BorderLayout.EAST); topContainer.add(infoPanel, BorderLayout.CENTER); progress.setValue(0); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java index 720c5633e9..30d16ff573 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java @@ -53,6 +53,9 @@ public class ClockManager @Inject private Notifier notifier; + @Inject + private Gson gson; + @Getter private final List timers = new CopyOnWriteArrayList<>(); @@ -183,7 +186,6 @@ public class ClockManager if (!Strings.isNullOrEmpty(timersJson)) { - final Gson gson = new Gson(); final List timers = gson.fromJson(timersJson, new TypeToken>() { }.getType()); @@ -200,7 +202,6 @@ public class ClockManager if (!Strings.isNullOrEmpty(stopwatchesJson)) { - final Gson gson = new Gson(); final List stopwatches = gson.fromJson(stopwatchesJson, new TypeToken>() { }.getType()); @@ -227,14 +228,12 @@ public class ClockManager void saveTimers() { - final Gson gson = new Gson(); final String json = gson.toJson(timers); configManager.setConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.TIMERS, json); } void saveStopwatches() { - final Gson gson = new Gson(); final String json = gson.toJson(stopwatches); configManager.setConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.STOPWATCHES, json); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java index 8c24588917..25b29ba01f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java @@ -29,6 +29,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import net.runelite.api.Varbits; +import net.runelite.client.plugins.timetracking.TimeTrackingConfig; @RequiredArgsConstructor( access = AccessLevel.PACKAGE @@ -41,4 +42,14 @@ class FarmingPatch private final String name; private final Varbits varbit; private final PatchImplementation implementation; -} + + String configKey() + { + return region.getRegionID() + "." + varbit.getId(); + } + + String notifyConfigKey() + { + return TimeTrackingConfig.NOTIFY + "." + region.getRegionID() + "." + varbit.getId(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java index a759d2c31b..bb149af373 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java @@ -33,13 +33,15 @@ public class FarmingRegion { private final String name; private final int regionID; + private final boolean definite; private final FarmingPatch[] patches; private final Varbits[] varbits; - FarmingRegion(String name, int regionID, FarmingPatch... patches) + FarmingRegion(String name, int regionID, boolean definite, FarmingPatch... patches) { this.name = name; this.regionID = regionID; + this.definite = definite; this.patches = patches; this.varbits = new Varbits[patches.length]; for (int i = 0; i < patches.length; i++) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index 1c068790c8..18d525c1e0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -33,8 +33,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.swing.JLabel; +import javax.swing.JToggleButton; import javax.swing.border.EmptyBorder; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.ItemID; +import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.timetracking.TabContentPanel; import net.runelite.client.plugins.timetracking.TimeTrackingConfig; @@ -42,10 +45,12 @@ import net.runelite.client.plugins.timetracking.TimeablePanel; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; +@Slf4j public class FarmingTabPanel extends TabContentPanel { private final FarmingTracker farmingTracker; private final ItemManager itemManager; + private final ConfigManager configManager; private final TimeTrackingConfig config; private final List> patchPanels; private final FarmingContractManager farmingContractManager; @@ -53,6 +58,7 @@ public class FarmingTabPanel extends TabContentPanel FarmingTabPanel( FarmingTracker farmingTracker, ItemManager itemManager, + ConfigManager configManager, TimeTrackingConfig config, Set patches, FarmingContractManager farmingContractManager @@ -60,6 +66,7 @@ public class FarmingTabPanel extends TabContentPanel { this.farmingTracker = farmingTracker; this.itemManager = itemManager; + this.configManager = configManager; this.config = config; this.patchPanels = new ArrayList<>(); this.farmingContractManager = farmingContractManager; @@ -103,6 +110,18 @@ public class FarmingTabPanel extends TabContentPanel lastImpl = patch.getImplementation(); } + // Set toggle state of notification menu on icon click; + JToggleButton toggleNotify = p.getNotifyButton(); + String configKey = patch.notifyConfigKey(); + + toggleNotify.addActionListener(e -> + { + if (configManager.getRSProfileKey() != null) + { + configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, configKey, toggleNotify.isSelected()); + } + }); + patchPanels.add(p); add(p, c); c.gridy++; @@ -197,18 +216,26 @@ public class FarmingTabPanel extends TabContentPanel { panel.getProgress().setVisible(false); } - JLabel farmingContractIcon = panel.getFarmingContractIcon(); - if (farmingContractManager.shouldHighlightFarmingTabPanel(patch)) - { - itemManager.getImage(ItemID.SEED_PACK).addTo(farmingContractIcon); - farmingContractIcon.setToolTipText(farmingContractManager.getContract().getName()); - } - else - { - farmingContractIcon.setIcon(null); - farmingContractIcon.setToolTipText(""); - } } + + JLabel farmingContractIcon = panel.getFarmingContractIcon(); + if (farmingContractManager.shouldHighlightFarmingTabPanel(patch)) + { + itemManager.getImage(ItemID.SEED_PACK).addTo(farmingContractIcon); + farmingContractIcon.setToolTipText(farmingContractManager.getContract().getName()); + } + else + { + farmingContractIcon.setIcon(null); + farmingContractIcon.setToolTipText(""); + } + + String configKey = patch.notifyConfigKey(); + JToggleButton toggleNotify = panel.getNotifyButton(); + boolean notifyEnabled = Boolean.TRUE + .equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, configKey, Boolean.class)); + + toggleNotify.setSelected(notifyEnabled); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java index a5c953f324..4a381220b2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java @@ -24,25 +24,34 @@ */ package net.runelite.client.plugins.timetracking.farming; +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Singleton; import java.time.Instant; import java.util.Collection; import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.GameState; import net.runelite.api.Varbits; import net.runelite.api.coords.WorldPoint; import net.runelite.api.vars.Autoweed; import net.runelite.api.widgets.WidgetModalMode; +import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.RuneScapeProfile; +import net.runelite.client.config.RuneScapeProfileType; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.timetracking.SummaryState; import net.runelite.client.plugins.timetracking.Tab; import net.runelite.client.plugins.timetracking.TimeTrackingConfig; +import net.runelite.client.util.Text; @Slf4j @Singleton @@ -53,6 +62,7 @@ public class FarmingTracker private final ConfigManager configManager; private final TimeTrackingConfig config; private final FarmingWorld farmingWorld; + private final Notifier notifier; private final Map summaries = new EnumMap<>(Tab.class); @@ -61,23 +71,26 @@ public class FarmingTracker * or {@code -1} if we have no data about any patch of the given type. */ private final Map completionTimes = new EnumMap<>(Tab.class); + Map wasNotified = new HashMap<>(); private boolean newRegionLoaded; private Collection lastRegions; + private boolean firstNotifyCheck = true; @Inject - private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, FarmingWorld farmingWorld) + private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, FarmingWorld farmingWorld, Notifier notifier) { this.client = client; this.itemManager = itemManager; this.configManager = configManager; this.config = config; this.farmingWorld = farmingWorld; + this.notifier = notifier; } public FarmingTabPanel createTabPanel(Tab tab, FarmingContractManager farmingContractManager) { - return new FarmingTabPanel(this, itemManager, config, farmingWorld.getTabs().get(tab), farmingContractManager); + return new FarmingTabPanel(this, itemManager, configManager, config, farmingWorld.getTabs().get(tab), farmingContractManager); } /** @@ -130,7 +143,7 @@ public class FarmingTracker { // Write the config value if it doesn't match what is current, or it is more than 5 minutes old Varbits varbit = patch.getVarbit(); - String key = region.getRegionID() + "." + varbit.getId(); + String key = patch.configKey(); String strVarbit = Integer.toString(client.getVar(varbit)); String storedValue = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key); @@ -189,6 +202,12 @@ public class FarmingTracker configManager.setRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET, offsetMins); } } + if (currentPatchState.getTickRate() != 0 + // Don't set wasNotified to false if witnessing a check-health action + && !(previousPatchState.getCropState() == CropState.GROWING && currentPatchState.getCropState() == CropState.HARVESTABLE && currentPatchState.getProduce().getPatchImplementation().isHealthCheckRequired())) + { + wasNotified.put(new ProfilePatch(patch, configManager.getRSProfileKey()), false); + } } else { @@ -258,17 +277,23 @@ public class FarmingTracker @Nullable public PatchPrediction predictPatch(FarmingPatch patch) + { + return predictPatch(patch, configManager.getRSProfileKey()); + } + + @Nullable + public PatchPrediction predictPatch(FarmingPatch patch, String profile) { long unixNow = Instant.now().getEpochSecond(); boolean autoweed = Integer.toString(Autoweed.ON.ordinal()) - .equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.AUTOWEED)); + .equals(configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile, TimeTrackingConfig.AUTOWEED)); boolean botanist = Boolean.TRUE - .equals(configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.BOTANIST, Boolean.class)); + .equals(configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile, TimeTrackingConfig.BOTANIST, Boolean.class)); - String key = patch.getRegion().getRegionID() + "." + patch.getVarbit().getId(); - String storedValue = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, key); + String key = patch.configKey(); + String storedValue = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile, key); if (storedValue == null) { @@ -323,11 +348,11 @@ public class FarmingTracker long doneEstimate = 0; if (tickrate > 0) { - long tickNow = getTickTime(tickrate, 0, unixNow); - long tickTime = getTickTime(tickrate, 0, unixTime); + long tickNow = getTickTime(tickrate, 0, unixNow, profile); + long tickTime = getTickTime(tickrate, 0, unixTime, profile); int delta = (int) (tickNow - tickTime) / (tickrate * 60); - doneEstimate = getTickTime(tickrate, stages - 1 - stage, tickTime); + doneEstimate = getTickTime(tickrate, stages - 1 - stage, tickTime, profile); stage += delta; if (stage >= stages) @@ -347,13 +372,13 @@ public class FarmingTracker public long getTickTime(int tickRate, int ticks) { - return getTickTime(tickRate, ticks, Instant.now().getEpochSecond()); + return getTickTime(tickRate, ticks, Instant.now().getEpochSecond(), configManager.getRSProfileKey()); } - public long getTickTime(int tickRate, int ticks, long requestedTime) + public long getTickTime(int tickRate, int ticks, long requestedTime, String profile) { - Integer offsetPrecisionMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION, int.class); - Integer offsetTimeMins = configManager.getRSProfileConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET, int.class); + Integer offsetPrecisionMins = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION, int.class); + Integer offsetTimeMins = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile, TimeTrackingConfig.FARM_TICK_OFFSET, int.class); //All offsets are negative but are stored as positive long calculatedOffsetTime = 0L; @@ -465,4 +490,133 @@ public class FarmingTracker completionTimes.put(tab.getKey(), completionTime); } } + + public void checkCompletion() + { + List rsProfiles = configManager.getRSProfiles(); + long unixNow = Instant.now().getEpochSecond(); + + for (RuneScapeProfile profile : rsProfiles) + { + Integer offsetPrecisionMins = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile.getKey(), TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION, int.class); + Integer offsetTimeMins = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile.getKey(), TimeTrackingConfig.FARM_TICK_OFFSET, int.class); + + for (Map.Entry> tab : farmingWorld.getTabs().entrySet()) + { + for (FarmingPatch patch : tab.getValue()) + { + ProfilePatch profilePatch = new ProfilePatch(patch, profile.getKey()); + boolean patchNotified = wasNotified.getOrDefault(profilePatch, false); + String configKey = patch.notifyConfigKey(); + boolean shouldNotify = Boolean.TRUE + .equals(configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, profile.getKey(), configKey, Boolean.class)); + PatchPrediction prediction = predictPatch(patch, profile.getKey()); + + if (prediction == null) + { + continue; + } + + int tickRate = prediction.getProduce().getTickrate(); + + if (offsetPrecisionMins == null || offsetTimeMins == null || (offsetPrecisionMins < tickRate && offsetPrecisionMins < 40) || prediction.getProduce() == Produce.WEEDS + || unixNow <= prediction.getDoneEstimate() || patchNotified || prediction.getCropState() == CropState.FILLING || prediction.getCropState() == CropState.EMPTY) + { + continue; + } + + wasNotified.put(profilePatch, true); + + if (!firstNotifyCheck && shouldNotify) + { + sendNotification(profile, prediction, patch); + } + } + } + } + firstNotifyCheck = false; + } + + @VisibleForTesting + void sendNotification(RuneScapeProfile profile, PatchPrediction prediction, FarmingPatch patch) + { + final RuneScapeProfileType profileType = profile.getType(); + + final StringBuilder stringBuilder = new StringBuilder(); + // Same RS account + if (client.getGameState() == GameState.LOGGED_IN && profile.getDisplayName().equals(client.getLocalPlayer().getName())) + { + // Same RS account but different profile type + if (profileType != RuneScapeProfileType.getCurrent(client)) + { + stringBuilder.append("(") + .append(Text.titleCase(profile.getType())) + .append(") "); + } + // Same RS account AND profile falls through here so no bracketed prefix is added + } + else + { + // Different RS account AND profile type + if (profileType != RuneScapeProfileType.getCurrent(client) || client.getGameState() == GameState.LOGIN_SCREEN) + { + //Don't print profile type when logged out if is STANDARD + if (client.getGameState() == GameState.LOGIN_SCREEN && profileType == RuneScapeProfileType.STANDARD) + { + stringBuilder.append("(") + .append(profile.getDisplayName()) + .append(") "); + } + else + { + stringBuilder.append("(") + .append(profile.getDisplayName()) + .append(" - ") + .append(Text.titleCase(profile.getType())) + .append(") "); + } + } + // Different RS account but same profile type + else + { + stringBuilder.append("(") + .append(profile.getDisplayName()) + .append(") "); + } + } + + stringBuilder + .append("Your ") + .append(prediction.getProduce().getName()); + + switch (prediction.getCropState()) + { + case HARVESTABLE: + case GROWING: + if (prediction.getProduce().getName().toLowerCase(Locale.ENGLISH).contains("compost")) + { + stringBuilder.append(" is ready to collect in "); + } + else + { + stringBuilder.append(" is ready to harvest in "); + } + break; + case DISEASED: + stringBuilder.append(" has become diseased in "); + break; + case DEAD: + stringBuilder.append(" has died in "); + break; + default: + // EMPTY and FILLING are caught above + throw new IllegalStateException(); + } + + stringBuilder.append(patch.getRegion().isDefinite() ? "the " : "") + .append(patch.getRegion().getName()) + .append("."); + + notifier.notify(stringBuilder.toString()); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java index 7ca46e130c..5429240775 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java @@ -64,14 +64,14 @@ class FarmingWorld { // Some of these patches get updated in multiple regions. // It may be worth it to add a specialization for these patches - add(new FarmingRegion("Al Kharid", 13106, + add(new FarmingRegion("Al Kharid", 13106, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.CACTUS) ), 13362, 13105); - add(new FarmingRegion("Ardougne", 10290, + add(new FarmingRegion("Ardougne", 10290, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) ), 10546); - add(new FarmingRegion("Ardougne", 10548, + add(new FarmingRegion("Ardougne", 10548, false, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), @@ -79,12 +79,12 @@ class FarmingWorld new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST) )); - add(new FarmingRegion("Brimhaven", 11058, + add(new FarmingRegion("Brimhaven", 11058, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) ), 11057); - add(new FarmingRegion("Catherby", 11062, + add(new FarmingRegion("Catherby", 11062, false, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), @@ -103,7 +103,7 @@ class FarmingWorld return true; } }, 11061, 11318, 11317); - add(new FarmingRegion("Catherby", 11317, + add(new FarmingRegion("Catherby", 11317, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE) ) { @@ -115,27 +115,27 @@ class FarmingWorld } }); - add(new FarmingRegion("Champions' Guild", 12596, + add(new FarmingRegion("Champions' Guild", 12596, true, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) )); - add(new FarmingRegion("Draynor Manor", 12340, + add(new FarmingRegion("Draynor Manor", 12340, false, new FarmingPatch("Belladonna", Varbits.FARMING_4771, PatchImplementation.BELLADONNA) )); - add(new FarmingRegion("Entrana", 11060, + add(new FarmingRegion("Entrana", 11060, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) ), 11316); - add(new FarmingRegion("Etceteria", 10300, + add(new FarmingRegion("Etceteria", 10300, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.SPIRIT_TREE) )); - add(new FarmingRegion("Falador", 11828, + add(new FarmingRegion("Falador", 11828, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) ), 12084); - add(new FarmingRegion("Falador", 12083, + add(new FarmingRegion("Falador", 12083, false, new FarmingPatch("North West", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South East", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), @@ -151,7 +151,7 @@ class FarmingWorld } }); - add(new FarmingRegion("Fossil Island", 14651, + add(new FarmingRegion("Fossil Island", 14651, false, new FarmingPatch("East", Varbits.FARMING_4771, PatchImplementation.HARDWOOD_TREE), new FarmingPatch("Middle", Varbits.FARMING_4772, PatchImplementation.HARDWOOD_TREE), new FarmingPatch("West", Varbits.FARMING_4773, PatchImplementation.HARDWOOD_TREE) @@ -179,22 +179,22 @@ class FarmingWorld return loc.getPlane() == 0; } }, 14907, 14908, 15164, 14652, 14906, 14650, 15162, 15163); - add(new FarmingRegion("Seaweed", 15008, + add(new FarmingRegion("Seaweed", 15008, false, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.SEAWEED), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.SEAWEED) )); - add(new FarmingRegion("Gnome Stronghold", 9781, + add(new FarmingRegion("Gnome Stronghold", 9781, true, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.FRUIT_TREE) ), 9782, 9526, 9525); - add(new FarmingRegion("Harmony", 15148, + add(new FarmingRegion("Harmony", 15148, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.HERB) )); - add(new FarmingRegion("Kourend", 6967, + add(new FarmingRegion("Kourend", 6967, false, new FarmingPatch("North East", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South West", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), @@ -202,7 +202,7 @@ class FarmingWorld new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST), new FarmingPatch("", Varbits.FARMING_7904, PatchImplementation.SPIRIT_TREE) ), 6711); - add(new FarmingRegion("Kourend", 7223, + add(new FarmingRegion("Kourend", 7223, false, new FarmingPatch("East 1", Varbits.GRAPES_4953, PatchImplementation.GRAPES), new FarmingPatch("East 2", Varbits.GRAPES_4954, PatchImplementation.GRAPES), new FarmingPatch("East 3", Varbits.GRAPES_4955, PatchImplementation.GRAPES), @@ -217,21 +217,21 @@ class FarmingWorld new FarmingPatch("West 6", Varbits.GRAPES_4964, PatchImplementation.GRAPES) )); - add(new FarmingRegion("Lletya", 9265, + add(new FarmingRegion("Lletya", 9265, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE) ), 11103); - add(new FarmingRegion("Lumbridge", 12851, + add(new FarmingRegion("Lumbridge", 12851, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) )); - add(new FarmingRegion("Lumbridge", 12594, + add(new FarmingRegion("Lumbridge", 12594, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) ), 12850); - add(new FarmingRegion("Morytania", 13622, + add(new FarmingRegion("Morytania", 13622, false, new FarmingPatch("Mushroom", Varbits.FARMING_4771, PatchImplementation.MUSHROOM) ), 13878); - add(new FarmingRegion("Morytania", 14391, + add(new FarmingRegion("Morytania", 14391, false, new FarmingPatch("North West", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South East", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), @@ -239,7 +239,7 @@ class FarmingWorld new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST) ), 14390); - add(new FarmingRegion("Port Sarim", 12082, + add(new FarmingRegion("Port Sarim", 12082, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.SPIRIT_TREE) ) { @@ -250,48 +250,48 @@ class FarmingWorld } }, 12083); - add(new FarmingRegion("Rimmington", 11570, + add(new FarmingRegion("Rimmington", 11570, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.BUSH) ), 11826); - add(new FarmingRegion("Seers' Village", 10551, + add(new FarmingRegion("Seers' Village", 10551, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) ), 10550); - add(new FarmingRegion("Tai Bwo Wannai", 11056, + add(new FarmingRegion("Tai Bwo Wannai", 11056, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.CALQUAT) )); - add(new FarmingRegion("Taverley", 11573, + add(new FarmingRegion("Taverley", 11573, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) ), 11829); - add(new FarmingRegion("Tree Gnome Village", 9777, + add(new FarmingRegion("Tree Gnome Village", 9777, true, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.FRUIT_TREE) ), 10033); - add(new FarmingRegion("Troll Stronghold", 11321, + add(new FarmingRegion("Troll Stronghold", 11321, true, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HERB) )); - add(new FarmingRegion("Varrock", 12854, + add(new FarmingRegion("Varrock", 12854, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.TREE) ), 12853); - add(new FarmingRegion("Yanille", 10288, + add(new FarmingRegion("Yanille", 10288, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HOPS) )); - add(new FarmingRegion("Weiss", 11325, + add(new FarmingRegion("Weiss", 11325, false, new FarmingPatch("", Varbits.FARMING_4771, PatchImplementation.HERB) )); - add(new FarmingRegion("Farming Guild", 5021, + add(new FarmingRegion("Farming Guild", 5021, true, new FarmingPatch("Hespori", Varbits.FARMING_7908, PatchImplementation.HESPORI) )); //Full 3x3 region area centered on farming guild - add(farmingGuildRegion = new FarmingRegion("Farming Guild", 4922, + add(farmingGuildRegion = new FarmingRegion("Farming Guild", 4922, true, new FarmingPatch("", Varbits.FARMING_7905, PatchImplementation.TREE), new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.HERB), new FarmingPatch("", Varbits.FARMING_4772, PatchImplementation.BUSH), @@ -308,7 +308,7 @@ class FarmingWorld ), 5177, 5178, 5179, 4921, 4923, 4665, 4666, 4667); //All of Prifddinas, and all of Prifddinas Underground - add(new FarmingRegion("Prifddinas", 13151, + add(new FarmingRegion("Prifddinas", 13151, false, new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/ProfilePatch.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/ProfilePatch.java new file mode 100644 index 0000000000..d928c1f8e0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/ProfilePatch.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 Hannah Ryan + * 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.timetracking.farming; + +import lombok.Value; + +@Value +class ProfilePatch +{ + FarmingPatch patch; + String rsProfileKey; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java index 8552d90eef..622e4db98b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java @@ -67,7 +67,6 @@ public class WikiSearchChatboxTextInput extends ChatboxTextInput private static final int PREDICTION_DEBOUNCE_DELAY_MS = 200; private final ChatboxPanelManager chatboxPanelManager; - private final Gson gson = new Gson(); private Future runningRequest = null; private List predictions = ImmutableList.of(); @@ -78,7 +77,7 @@ public class WikiSearchChatboxTextInput extends ChatboxTextInput @Inject public WikiSearchChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread, ScheduledExecutorService scheduledExecutorService, @Named("developerMode") final boolean developerMode, - OkHttpClient okHttpClient) + OkHttpClient okHttpClient, Gson gson) { super(chatboxPanelManager, clientThread); this.chatboxPanelManager = chatboxPanelManager; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/DungeonLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/DungeonLocation.java index 0df27cf60c..544cefd6c8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/DungeonLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/DungeonLocation.java @@ -58,6 +58,7 @@ enum DungeonLocation CRABCLAW_CAVES_TUNNEL("Crabclaw Caves Tunnel (quest)", new WorldPoint(1671, 9800, 0)), CRANDOR("Crandor Dungeon", new WorldPoint(2833, 3256, 0)), CRASH_ISLAND("Crash Island Dungeon", new WorldPoint(2920, 2721, 0)), + CRUMBLING_TOWER("Crumbling Tower basement", new WorldPoint(2130, 2994, 0)), DEEP_WILDERNESS("Deep Wilderness Dungeon", new WorldPoint(3044, 3924, 0)), DRAYNOR_MANOR_E("Draynor Manor basement", new WorldPoint(3114, 3357, 0)), DRAYNOR_MANOR_W("Draynor Manor basement", new WorldPoint(3091, 3362, 0)), @@ -89,6 +90,7 @@ enum DungeonLocation ICE_QUEEN_W("Ice Queen's Lair", new WorldPoint(2822, 3510, 0)), ICE_TROLL_E("Ice Troll Caves", new WorldPoint(2400, 3889, 0)), ICE_TROLL_W("Ice Troll Caves", new WorldPoint(2315, 3894, 0)), + ISLE_OF_SOULS_DUNGEON("Isle of Souls Dungeon", new WorldPoint(2308, 2919, 0)), IORWERTH("Iorwerth Dungeon", new WorldPoint(3224, 6044, 0)), IORWERTH_CAMP_CAVE("Iorwerth Camp cave", new WorldPoint(2200, 3262, 0)), IORWERTH_CAMP_CAVE_PRIF("Iorwerth Camp cave", new WorldPoint(3224, 6014, 0)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FishingSpotLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FishingSpotLocation.java index 3180ca0973..4b04a4beea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FishingSpotLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FishingSpotLocation.java @@ -95,6 +95,9 @@ enum FishingSpotLocation IORWERTH_CAMP_OUTSIDE(FishingSpot.SALMON, new WorldPoint(2215, 3245, 0)), ISAFDAR_NORTH_EAST_INSIDE(FishingSpot.SALMON, new WorldPoint(3293, 6005, 0)), ISAFDAR_NORTH_EAST_OUTSIDE(FishingSpot.SALMON, new WorldPoint(2269, 3253, 0)), + ISLE_OF_SOULS_EAST(FishingSpot.SHARK, new WorldPoint(2281, 2841, 0)), + ISLE_OF_SOULS_NORTH(FishingSpot.LOBSTER, new WorldPoint(2280, 2975, 0)), + ISLE_OF_SOULS_SOUTH_WEST(FishingSpot.SHRIMP, new WorldPoint(2162, 2782, 0)), JATISZO(new FishingSpot[]{FishingSpot.SHARK, FishingSpot.LOBSTER}, new WorldPoint(2400, 3780, 0), new WorldPoint(2412, 3780, 0), new WorldPoint(2419, 3789, 0)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/HunterAreaLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/HunterAreaLocation.java index 3f9ee919b8..5607170461 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/HunterAreaLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/HunterAreaLocation.java @@ -49,6 +49,9 @@ enum HunterAreaLocation FOSSIL_ISLAND_UNDERWATER(new WorldPoint(3743, 10295, 0), HunterCreature.FISH_SHOAL), GWENITH_HUNTER_AREA_OUTSIDE(new WorldPoint(2269, 3408, 0), HunterCreature.CARNIVOROUS_CHINCHOMPA), GWENITH_HUNTER_AREA_INSIDE(new WorldPoint(3293, 6160, 0), HunterCreature.CARNIVOROUS_CHINCHOMPA), + ISLE_OF_SOULS_NORTH(new WorldPoint(2207, 2964, 0), HunterCreature.COPPER_LONGTAIL), + ISLE_OF_SOULS_NORTH_WEST(new WorldPoint(2127, 2950, 0), HunterCreature.CHINCHOMPA), + ISLE_OF_SOULS_SOUTH_WEST(new WorldPoint(2158, 2822, 0), HunterCreature.CRIMSON_SWIFT), KARAMJA_HUNTER_AREA(new WorldPoint(2786, 3001, 0), HunterCreature.HORNED_GRAAHK), KEBOS_SWAMP(new WorldPoint(1184, 3595, 0), HunterCreature.CRIMSON_SWIFT), KOUREND_WOODLAND_CENTER(new WorldPoint(1512, 3478, 0), HunterCreature.RUBY_HARVEST), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MinigameLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MinigameLocation.java index a5f0b55ca0..cc387c4872 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MinigameLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MinigameLocation.java @@ -55,6 +55,7 @@ enum MinigameLocation PYRAMID_PLUNDER("Pyramid Plunder", new WorldPoint(3288, 2787, 0)), RANGING_GUILD("Ranging Guild", new WorldPoint(2671, 3419, 0)), ROGUES_DEN("Rogues' Den", new WorldPoint(2905, 3537, 0)), + SHADES_OF_MORTTON("Shades of Mort'ton", new WorldPoint(3505, 3315, 0)), SORCERESSS_GARDEN("Sorceress's Garden", new WorldPoint(3285, 3180, 0)), TROUBLE_BREWING("Trouble Brewing", new WorldPoint(3811, 3021, 0)), VOLCANIC_MINE("Volcanic Mine", new WorldPoint(3812, 3810, 0)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MiningSiteLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MiningSiteLocation.java index bd647d2b1d..ef4db23372 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MiningSiteLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/MiningSiteLocation.java @@ -117,6 +117,11 @@ enum MiningSiteLocation new Rock(10, Ore.CLAY), new Rock(11, Ore.COPPER), new Rock(4, Ore.TIN), new Rock(9, Ore.IRON), new Rock(2, Ore.SILVER)), ISAFDAR(new WorldPoint(2277, 3159, 0), new Rock(4, Ore.ADAMANTITE), new Rock(2, Ore.RUNITE)), + ISLE_OF_SOULS_DUNGEON_EAST(new WorldPoint(1831, 9109, 0), new Rock(1, Ore.RUNITE)), + ISLE_OF_SOULS_DUNGEON_WEST(new WorldPoint(1814, 9116, 0), new Rock(2, Ore.ADAMANTITE)), + ISLE_OF_SOULS_SOUTH(new WorldPoint(2195, 2793, 0), + new Rock(3, Ore.CLAY), new Rock(3, Ore.TIN), new Rock(3, Ore.COPPER), new Rock(10, Ore.IRON), + new Rock(3, Ore.SILVER), new Rock(6, Ore.COAL), new Rock(4, Ore.GOLD), new Rock(2, Ore.MITHRIL)), JATIZSO(new WorldPoint(2396, 3812, 0), new Rock(11, Ore.TIN), new Rock(7, Ore.IRON), new Rock(8, Ore.COAL), new Rock(15, Ore.MITHRIL), new Rock(11, Ore.ADAMANTITE)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java index addcda5af7..f7751bf5dd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java @@ -83,6 +83,9 @@ enum RareTreeLocation new WorldPoint(2748, 3466, 0), new WorldPoint(2710, 3570, 0), + // Isle of Souls + new WorldPoint(2254, 2808, 0), + // Prifddinas new WorldPoint(2209, 3427, 0), new WorldPoint(3233, 6179, 0)), @@ -111,6 +114,9 @@ enum RareTreeLocation // Mos Le'Harmless new WorldPoint(3810, 3058, 0), + // Isle of Souls + new WorldPoint(2194, 2991, 0), + // Karamja new WorldPoint(2821, 3084, 0)), @@ -180,6 +186,10 @@ enum RareTreeLocation new WorldPoint(3674, 3447, 0), new WorldPoint(3684, 3385, 0), + // Isle of Souls + new WorldPoint(2147, 2972, 0), + new WorldPoint(2165, 2863, 0), + // Zanaris new WorldPoint(2412, 4464, 0), new WorldPoint(2465, 4427, 0), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java index 1114d56720..dcac0c700f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java @@ -186,7 +186,7 @@ public class WorldMapPlugin extends Plugin { case AGILITY: { - int newAgilityLevel = statChanged.getLevel(); + int newAgilityLevel = statChanged.getBoostedLevel(); if (newAgilityLevel != agilityLevel) { agilityLevel = newAgilityLevel; @@ -196,7 +196,7 @@ public class WorldMapPlugin extends Plugin } case WOODCUTTING: { - int newWoodcutLevel = statChanged.getLevel(); + int newWoodcutLevel = statChanged.getBoostedLevel(); if (newWoodcutLevel != woodcuttingLevel) { woodcuttingLevel = newWoodcutLevel; diff --git a/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java b/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java index 8ecf6ab665..67ba137e63 100644 --- a/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java +++ b/runelite-client/src/main/java/net/runelite/client/task/Scheduler.java @@ -24,10 +24,16 @@ */ package net.runelite.client.task; +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -60,6 +66,63 @@ public class Scheduler return Collections.unmodifiableList(scheduledMethods); } + public void registerObject(Object obj) + { + for (Method method : obj.getClass().getMethods()) + { + Schedule schedule = method.getAnnotation(Schedule.class); + + if (schedule == null) + { + continue; + } + + Runnable runnable = null; + try + { + final Class clazz = method.getDeclaringClass(); + final MethodHandles.Lookup caller = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); + final MethodType subscription = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); + final MethodHandle target = caller.findVirtual(clazz, method.getName(), subscription); + final CallSite site = LambdaMetafactory.metafactory( + caller, + "run", + MethodType.methodType(Runnable.class, clazz), + subscription, + target, + subscription); + + final MethodHandle factory = site.getTarget(); + runnable = (Runnable) factory.bindTo(obj).invokeExact(); + } + catch (Throwable e) + { + log.warn("Unable to create lambda for method {}", method, e); + } + + ScheduledMethod scheduledMethod = new ScheduledMethod(schedule, method, obj, runnable); + log.debug("Scheduled task {}", scheduledMethod); + + addScheduledMethod(scheduledMethod); + } + } + + public void unregisterObject(Object obj) + { + List methods = new ArrayList<>(getScheduledMethods()); + + for (ScheduledMethod method : methods) + { + if (method.getObject() != obj) + { + continue; + } + + log.debug("Removing scheduled task {}", method); + removeScheduledMethod(method); + } + } + public void tick() { Instant now = Instant.now(); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java b/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java index b66c62d7fd..538a59b1c5 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ColorScheme.java @@ -38,10 +38,10 @@ public class ColorScheme public static final Color BRAND_BLUE_TRANSPARENT = new Color(25, 194, 255, 120); /* The orange color used for the branding's accents */ - public static final Color BRAND_ORANGE = new Color(220, 138, 0); + public static final Color BRAND_ORANGE = BRAND_BLUE; /* The orange color used for the branding's accents, with lowered opacity */ - public static final Color BRAND_ORANGE_TRANSPARENT = new Color(220, 138, 0, 120); + public static final Color BRAND_ORANGE_TRANSPARENT = BRAND_BLUE_TRANSPARENT; public static final Color DARKER_GRAY_COLOR = new Color(30, 30, 30); public static final Color DARK_GRAY_COLOR = new Color(40, 40, 40); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/FontManager.java b/runelite-client/src/main/java/net/runelite/client/ui/FontManager.java index 564ebcba62..5305506a41 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/FontManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/FontManager.java @@ -24,17 +24,25 @@ */ package net.runelite.client.ui; -import javax.swing.text.StyleContext; import java.awt.Font; import java.awt.FontFormatException; import java.awt.GraphicsEnvironment; import java.io.IOException; +import javax.swing.text.StyleContext; +import lombok.Getter; public class FontManager { + @Getter private static final Font runescapeFont; + @Getter private static final Font runescapeSmallFont; + @Getter private static final Font runescapeBoldFont; + @Getter + private static final Font defaultFont; + @Getter + private static final Font defaultBoldFont; static { @@ -48,7 +56,7 @@ public class FontManager ge.registerFont(font); runescapeFont = StyleContext.getDefaultStyleContext() - .getFont(font.getName(), Font.PLAIN, 16); + .getFont(font.getName(), Font.PLAIN, 16); ge.registerFont(runescapeFont); Font smallFont = Font.createFont(Font.TRUETYPE_FONT, @@ -57,16 +65,16 @@ public class FontManager ge.registerFont(smallFont); runescapeSmallFont = StyleContext.getDefaultStyleContext() - .getFont(smallFont.getName(), Font.PLAIN, 16); + .getFont(smallFont.getName(), Font.PLAIN, 16); ge.registerFont(runescapeSmallFont); Font boldFont = Font.createFont(Font.TRUETYPE_FONT, - FontManager.class.getResourceAsStream("runescape_bold.ttf")) - .deriveFont(Font.BOLD, 16); + FontManager.class.getResourceAsStream("runescape_bold.ttf")) + .deriveFont(Font.BOLD, 16); ge.registerFont(boldFont); runescapeBoldFont = StyleContext.getDefaultStyleContext() - .getFont(boldFont.getName(), Font.BOLD, 16); + .getFont(boldFont.getName(), Font.BOLD, 16); ge.registerFont(runescapeBoldFont); } catch (FontFormatException ex) @@ -77,20 +85,8 @@ public class FontManager { throw new RuntimeException("Font file not found.", ex); } - } - public static Font getRunescapeFont() - { - return runescapeFont; - } - - public static Font getRunescapeSmallFont() - { - return runescapeSmallFont; - } - - public static Font getRunescapeBoldFont() - { - return runescapeBoldFont; + defaultFont = new Font(Font.DIALOG, Font.PLAIN, 16); + defaultBoldFont = new Font(Font.DIALOG, Font.BOLD, 16); } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java index cb317b8afd..05806ada73 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/components/IconTextField.java @@ -29,17 +29,16 @@ package net.runelite.client.ui.components; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; -import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; import javax.swing.DefaultListModel; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -125,7 +124,7 @@ public class IconTextField extends JPanel textField.addMouseListener(hoverEffect); innerTxt.addMouseListener(hoverEffect); - clearButton = createRHSButton(ColorScheme.PROGRESS_ERROR_COLOR, Color.PINK); + clearButton = createRHSButton(ColorScheme.PROGRESS_ERROR_COLOR, Color.PINK, FontManager.getRunescapeBoldFont()); clearButton.setText("×"); clearButton.addActionListener(evt -> { @@ -192,7 +191,7 @@ public class IconTextField extends JPanel } }); - suggestionButton = createRHSButton(ColorScheme.LIGHT_GRAY_COLOR, ColorScheme.MEDIUM_GRAY_COLOR); + suggestionButton = createRHSButton(ColorScheme.LIGHT_GRAY_COLOR, ColorScheme.MEDIUM_GRAY_COLOR, FontManager.getDefaultBoldFont()); suggestionButton.setText("▾"); suggestionButton.addActionListener(e -> { @@ -237,11 +236,11 @@ public class IconTextField extends JPanel add(rhsButtons, BorderLayout.EAST); } - private JButton createRHSButton(Color fg, Color rollover) + private JButton createRHSButton(Color fg, Color rollover, Font font) { JButton b = new JButton(); b.setPreferredSize(new Dimension(30, 0)); - b.setFont(FontManager.getRunescapeBoldFont()); + b.setFont(font); b.setBorder(null); b.setRolloverEnabled(true); SwingUtil.removeButtonDecorations(b); @@ -334,30 +333,6 @@ public class IconTextField extends JPanel clearListeners.add(clearListener); } - public void addKeyListener(Consumer keyEventConsumer) - { - addKeyListener(new net.runelite.client.input.KeyListener() - { - @Override - public void keyTyped(KeyEvent e) - { - keyEventConsumer.accept(e); - } - - @Override - public void keyPressed(KeyEvent e) - { - keyEventConsumer.accept(e); - } - - @Override - public void keyReleased(KeyEvent e) - { - keyEventConsumer.accept(e); - } - }); - } - @Override public void removeKeyListener(KeyListener keyListener) { diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java index 20638e7874..83c67c09e0 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java @@ -28,6 +28,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; @@ -50,6 +51,10 @@ public class LineComponent implements LayoutableRenderableEntity @Builder.Default private Color rightColor = Color.WHITE; + private Font leftFont; + + private Font rightFont; + @Builder.Default private Point preferredLocation = new Point(); @@ -67,13 +72,16 @@ public class LineComponent implements LayoutableRenderableEntity final String left = MoreObjects.firstNonNull(this.left, ""); final String right = MoreObjects.firstNonNull(this.right, ""); - final FontMetrics metrics = graphics.getFontMetrics(); + final Font leftFont = MoreObjects.firstNonNull(this.leftFont, graphics.getFont()); + final Font rightFont = MoreObjects.firstNonNull(this.rightFont, graphics.getFont()); + final FontMetrics lfm = graphics.getFontMetrics(leftFont), rfm = graphics.getFontMetrics(rightFont); + final int fmHeight = Math.max(lfm.getHeight(), rfm.getHeight()); final int baseX = preferredLocation.x; - final int baseY = preferredLocation.y + metrics.getHeight(); + final int baseY = preferredLocation.y + fmHeight; int x = baseX; int y = baseY; - final int leftFullWidth = getLineWidth(left, metrics); - final int rightFullWidth = getLineWidth(right, metrics); + final int leftFullWidth = getLineWidth(left, lfm); + final int rightFullWidth = getLineWidth(right, rfm); final TextComponent textComponent = new TextComponent(); if (preferredSize.width < leftFullWidth + rightFullWidth) @@ -87,8 +95,8 @@ public class LineComponent implements LayoutableRenderableEntity leftSmallWidth -= rightSmallWidth; } - final String[] leftSplitLines = lineBreakText(left, leftSmallWidth, metrics); - final String[] rightSplitLines = lineBreakText(right, rightSmallWidth, metrics); + final String[] leftSplitLines = lineBreakText(left, leftSmallWidth, lfm); + final String[] rightSplitLines = lineBreakText(right, rightSmallWidth, rfm); int lineCount = Math.max(leftSplitLines.length, rightSplitLines.length); @@ -100,19 +108,21 @@ public class LineComponent implements LayoutableRenderableEntity textComponent.setPosition(new Point(x, y)); textComponent.setText(leftText); textComponent.setColor(leftColor); + textComponent.setFont(leftFont); textComponent.render(graphics); } if (i < rightSplitLines.length) { final String rightText = rightSplitLines[i]; - textComponent.setPosition(new Point(x + preferredSize.width - getLineWidth(rightText, metrics), y)); + textComponent.setPosition(new Point(x + preferredSize.width - getLineWidth(rightText, rfm), y)); textComponent.setText(rightText); textComponent.setColor(rightColor); + textComponent.setFont(rightFont); textComponent.render(graphics); } - y += metrics.getHeight(); + y += fmHeight; } final Dimension dimension = new Dimension(preferredSize.width, y - baseY); @@ -126,6 +136,7 @@ public class LineComponent implements LayoutableRenderableEntity textComponent.setPosition(new Point(x, y)); textComponent.setText(left); textComponent.setColor(leftColor); + textComponent.setFont(leftFont); textComponent.render(graphics); } @@ -134,10 +145,11 @@ public class LineComponent implements LayoutableRenderableEntity textComponent.setPosition(new Point(x + preferredSize.width - rightFullWidth, y)); textComponent.setText(right); textComponent.setColor(rightColor); + textComponent.setFont(rightFont); textComponent.render(graphics); } - y += metrics.getHeight(); + y += fmHeight; final Dimension dimension = new Dimension(preferredSize.width, y - baseY); bounds.setLocation(preferredLocation); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TextComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TextComponent.java index 45326fea28..0b6054a466 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TextComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TextComponent.java @@ -26,10 +26,12 @@ package net.runelite.client.ui.overlay.components; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.util.regex.Pattern; +import javax.annotation.Nullable; import lombok.Setter; import net.runelite.client.ui.overlay.RenderableEntity; import net.runelite.client.util.ColorUtil; @@ -45,10 +47,22 @@ public class TextComponent implements RenderableEntity private Point position = new Point(); private Color color = Color.WHITE; private boolean outline; + /** + * The text font. + */ + @Nullable + private Font font; @Override public Dimension render(Graphics2D graphics) { + Font originalFont = null; + if (font != null) + { + originalFont = graphics.getFont(); + graphics.setFont(font); + } + final FontMetrics fontMetrics = graphics.getFontMetrics(); if (COL_TAG_PATTERN_W_LOOKAHEAD.matcher(text).find()) @@ -105,6 +119,14 @@ public class TextComponent implements RenderableEntity graphics.drawString(text, position.x, position.y); } - return new Dimension(fontMetrics.stringWidth(text), fontMetrics.getHeight()); + int width = fontMetrics.stringWidth(text); + int height = fontMetrics.getHeight(); + + if (originalFont != null) + { + graphics.setFont(originalFont); + } + + return new Dimension(width, height); } } diff --git a/runelite-client/src/main/java/net/runelite/client/util/DeferredDocumentChangedListener.java b/runelite-client/src/main/java/net/runelite/client/util/DeferredDocumentChangedListener.java new file mode 100644 index 0000000000..a0cddbf5f4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/util/DeferredDocumentChangedListener.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019, Owain van Brakel + * 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.util; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.Timer; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +public class DeferredDocumentChangedListener implements DocumentListener +{ + private final Timer timer; + private final List listeners; + + public DeferredDocumentChangedListener() + { + listeners = new ArrayList<>(25); + timer = new Timer(200, e -> fireStateChanged()); + timer.setRepeats(false); + } + + public void addChangeListener(ChangeListener listener) + { + listeners.add(listener); + } + + private void fireStateChanged() + { + if (!listeners.isEmpty()) + { + ChangeEvent evt = new ChangeEvent(this); + for (ChangeListener listener : listeners) + { + listener.stateChanged(evt); + } + } + } + + @Override + public void insertUpdate(DocumentEvent e) + { + timer.restart(); + } + + @Override + public void removeUpdate(DocumentEvent e) + { + timer.restart(); + } + + @Override + public void changedUpdate(DocumentEvent e) + { + timer.restart(); + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/util/ImageCapture.java b/runelite-client/src/main/java/net/runelite/client/util/ImageCapture.java index dfee412c03..e842005dba 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/ImageCapture.java +++ b/runelite-client/src/main/java/net/runelite/client/util/ImageCapture.java @@ -26,6 +26,7 @@ package net.runelite.client.util; import com.google.common.base.Strings; +import com.google.gson.Gson; import java.awt.Toolkit; import java.awt.TrayIcon; import java.awt.datatransfer.Clipboard; @@ -54,7 +55,6 @@ import net.runelite.api.GameState; import net.runelite.api.WorldType; import net.runelite.client.Notifier; import static net.runelite.client.RuneLite.SCREENSHOT_DIR; -import net.runelite.http.api.RuneLiteAPI; import okhttp3.Call; import okhttp3.Callback; import okhttp3.HttpUrl; @@ -75,6 +75,7 @@ public class ImageCapture private final Client client; private final Notifier notifier; private final OkHttpClient okHttpClient; + private final Gson gson; private final String imgurClientId; @Inject @@ -82,12 +83,14 @@ public class ImageCapture final Client client, final Notifier notifier, final OkHttpClient okHttpClient, + final Gson gson, @Named("runelite.imgur.client.id") final String imgurClientId ) { this.client = client; this.notifier = notifier; this.okHttpClient = okHttpClient; + this.gson = gson; this.imgurClientId = imgurClientId; } @@ -204,7 +207,7 @@ public class ImageCapture */ private void uploadScreenshot(File screenshotFile, boolean notify) throws IOException { - String json = RuneLiteAPI.GSON.toJson(new ImageUploadRequest(screenshotFile)); + String json = gson.toJson(new ImageUploadRequest(screenshotFile)); Request request = new Request.Builder() .url(IMGUR_IMAGE_UPLOAD_URL) @@ -225,8 +228,8 @@ public class ImageCapture { try (InputStream in = response.body().byteStream()) { - ImageUploadResponse imageUploadResponse = RuneLiteAPI.GSON - .fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), ImageUploadResponse.class); + ImageUploadResponse imageUploadResponse = + gson.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), ImageUploadResponse.class); if (imageUploadResponse.isSuccess()) { diff --git a/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java b/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java index 3fb1da78d8..219e0e2e48 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java @@ -34,10 +34,12 @@ import java.awt.image.BufferedImage; import java.awt.image.DirectColorModel; import java.awt.image.PixelGrabber; import java.awt.image.RescaleOp; +import java.awt.image.WritableRaster; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Predicate; import javax.imageio.ImageIO; import javax.swing.GrayFilter; import lombok.extern.slf4j.Slf4j; @@ -525,4 +527,184 @@ public class ImageUtil return sprite; } + + /** + * Recolors pixels of the given image with the given color based on a given recolor condition + * predicate. + * + * @param image The image which should have its non-transparent pixels recolored. + * @param color The color with which to recolor pixels. + * @param recolorCondition The condition on which to recolor pixels with the given color. + * @return The given image with all pixels fulfilling the recolor condition predicate + * set to the given color. + */ + public static BufferedImage recolorImage(final BufferedImage image, final Color color, final Predicate recolorCondition) + { + final BufferedImage recoloredImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < recoloredImage.getWidth(); x++) + { + for (int y = 0; y < recoloredImage.getHeight(); y++) + { + final Color pixelColor = new Color(image.getRGB(x, y), true); + if (!recolorCondition.test(pixelColor)) + { + recoloredImage.setRGB(x, y, image.getRGB(x, y)); + continue; + } + + recoloredImage.setRGB(x, y, color.getRGB()); + } + } + return recoloredImage; + } + + public static BufferedImage recolorImage(BufferedImage image, final Color color) + { + int width = image.getWidth(); + int height = image.getHeight(); + WritableRaster raster = image.getRaster(); + + for (int xx = 0; xx < width; xx++) + { + for (int yy = 0; yy < height; yy++) + { + int[] pixels = raster.getPixel(xx, yy, (int[]) null); + pixels[0] = color.getRed(); + pixels[1] = color.getGreen(); + pixels[2] = color.getBlue(); + raster.setPixel(xx, yy, pixels); + } + } + return image; + } + + /** + * Draw fg centered on top of bg + */ + public static SpritePixels mergeSprites(final Client client, final SpritePixels bg, final SpritePixels fg) + { + assert fg.getHeight() <= bg.getHeight() && fg.getWidth() <= bg.getWidth() : "Background has to be larger than foreground"; + + final int[] canvas = Arrays.copyOf(bg.getPixels(), bg.getWidth() * bg.getHeight()); + final SpritePixels result = client.createSpritePixels(canvas, bg.getWidth(), bg.getHeight()); + + final int bgWid = bg.getWidth(); + final int fgHgt = fg.getHeight(); + final int fgWid = fg.getWidth(); + + final int xOffset = (bgWid - fgWid) / 2; + final int yOffset = (bg.getHeight() - fgHgt) / 2; + + final int[] fgPixels = fg.getPixels(); + + for (int y1 = yOffset, y2 = 0; y2 < fgHgt; y1++, y2++) + { + int i1 = y1 * bgWid + xOffset; + int i2 = y2 * fgWid; + + for (int x = 0; x < fgWid; x++, i1++, i2++) + { + if (fgPixels[i2] > 0) + { + canvas[i1] = fgPixels[i2]; + } + } + } + + return result; + } + + /** + * Resize Sprite sprite to given width (newW) and height (newH) + */ + public static SpritePixels resizeSprite(final Client client, final SpritePixels sprite, int newW, int newH) + { + assert newW > 0 && newH > 0; + + final int oldW = sprite.getWidth(); + final int oldH = sprite.getHeight(); + + if (oldW == newW && oldH == newH) + { + return sprite; + } + + final int[] canvas = new int[newW * newH]; + final int[] pixels = sprite.getPixels(); + + final SpritePixels result = client.createSpritePixels(canvas, newW, newH); + + int pixelX = 0; + int pixelY = 0; + + final int oldMaxW = sprite.getMaxWidth(); + final int oldMaxH = sprite.getMaxHeight(); + + final int pixelW = (oldMaxW << 16) / newW; + final int pixelH = (oldMaxH << 16) / newH; + + int xOffset = 0; + int yOffset = 0; + + int canvasIdx; + if (sprite.getOffsetX() > 0) + { + canvasIdx = (pixelW + (sprite.getOffsetX() << 16) - 1) / pixelW; + xOffset += canvasIdx; + pixelX += canvasIdx * pixelW - (sprite.getOffsetX() << 16); + } + + if (sprite.getOffsetY() > 0) + { + canvasIdx = (pixelH + (sprite.getOffsetY() << 16) - 1) / pixelH; + yOffset += canvasIdx; + pixelY += canvasIdx * pixelH - (sprite.getOffsetY() << 16); + } + + if (oldW < oldMaxW) + { + newW = (pixelW + ((oldW << 16) - pixelX) - 1) / pixelW; + } + + if (oldH < oldMaxH) + { + newH = (pixelH + ((oldH << 16) - pixelY) - 1) / pixelH; + } + + canvasIdx = xOffset + yOffset * newW; + int canvasOffset = 0; + if (yOffset + newH > newH) + { + newH -= yOffset + newH - newH; + } + + int tmp; + if (yOffset < 0) + { + tmp = -yOffset; + newH -= tmp; + canvasIdx += tmp * newW; + pixelY += pixelH * tmp; + } + + if (newW + xOffset > newW) + { + tmp = newW + xOffset - newW; + newW -= tmp; + canvasOffset += tmp; + } + + if (xOffset < 0) + { + tmp = -xOffset; + newW -= tmp; + canvasIdx += tmp; + pixelX += pixelW * tmp; + canvasOffset += tmp; + } + + client.scaleSprite(canvas, pixels, 0, pixelX, pixelY, canvasIdx, canvasOffset, newW, newH, pixelW, pixelH, oldW); + + return result; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java index e8965029c1..24828baa51 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java +++ b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java @@ -207,4 +207,54 @@ public class LinkBrowser } }); } + + /** + * Tries to open the specified {@code File} with the systems default text editor. If operation fails + * an error message is displayed with the option to copy the absolute file path to clipboard. + * + * @param file the File instance of the log file + * @return did the file open successfully? + */ + public static boolean openLocalFile(final File file) + { + if (file == null || !file.exists()) + { + return false; + } + + if (attemptOpenLocalFile(file)) + { + log.debug("Opened log file through Desktop#open to {}", file); + return true; + } + + showMessageBox("Unable to open file. Press 'OK' and the file path will be copied to your clipboard", file.getAbsolutePath()); + return false; + } + + private static boolean attemptOpenLocalFile(final File file) + { + if (!Desktop.isDesktopSupported()) + { + return false; + } + + final Desktop desktop = Desktop.getDesktop(); + + if (!desktop.isSupported(Desktop.Action.OPEN)) + { + return false; + } + + try + { + desktop.open(file); + return true; + } + catch (IOException ex) + { + log.warn("Failed to open Desktop#open {}", file, ex); + return false; + } + } } diff --git a/runelite-client/src/main/java/com/openosrs/client/util/PvPUtil.java b/runelite-client/src/main/java/net/runelite/client/util/PvPUtil.java similarity index 59% rename from runelite-client/src/main/java/com/openosrs/client/util/PvPUtil.java rename to runelite-client/src/main/java/net/runelite/client/util/PvPUtil.java index 1f0120c75a..3380cf5a8f 100644 --- a/runelite-client/src/main/java/com/openosrs/client/util/PvPUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/PvPUtil.java @@ -1,29 +1,37 @@ /* - * Copyright (c) 2019. PKLite - All Rights Reserved - * Unauthorized modification, distribution, or possession of this source file, via any medium is strictly prohibited. - * Proprietary and confidential. Refer to PKLite License file for more information on - * full terms of this copyright and to determine what constitutes authorized use. - * Written by PKLite(ST0NEWALL, others) , 2019 + * Copyright (c) 2019, PKLite + * Copyright (c) 2020, ThatGamerBlue + * 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 com.openosrs.client.util; +package net.runelite.client.util; +import java.awt.Polygon; import net.runelite.api.Client; -import net.runelite.api.InventoryID; -import net.runelite.api.Item; -import net.runelite.api.ItemComposition; import net.runelite.api.Player; import net.runelite.api.Varbits; import net.runelite.api.WorldType; import net.runelite.api.coords.WorldPoint; import net.runelite.api.geometry.Cuboid; -import net.runelite.client.game.ItemManager; -import net.runelite.client.util.QuantityFormatter; -import org.apache.commons.lang3.ArrayUtils; -import java.awt.Polygon; -import java.util.Comparator; -import java.util.Objects; -import java.util.TreeMap; public class PvPUtil { @@ -94,42 +102,4 @@ public class PvPUtil } return wildernessLevel != 0 && Math.abs(client.getLocalPlayer().getCombatLevel() - player.getCombatLevel()) <= wildernessLevel; } - - public static int calculateRisk(Client client, ItemManager itemManager) - { - if (client.getItemContainer(InventoryID.EQUIPMENT) == null) - { - return 0; - } - if (client.getItemContainer(InventoryID.INVENTORY).getItems() == null) - { - return 0; - } - Item[] items = ArrayUtils.addAll(Objects.requireNonNull(client.getItemContainer(InventoryID.EQUIPMENT)).getItems(), - Objects.requireNonNull(client.getItemContainer(InventoryID.INVENTORY)).getItems()); - TreeMap priceMap = new TreeMap<>(Comparator.comparingInt(Integer::intValue)); - int wealth = 0; - for (Item i : items) - { - int value = (itemManager.getItemPrice(i.getId()) * i.getQuantity()); - - final ItemComposition itemComposition = itemManager.getItemComposition(i.getId()); - if (!itemComposition.isTradeable() && value == 0) - { - value = itemComposition.getPrice() * i.getQuantity(); - priceMap.put(value, i); - } - else - { - value = itemManager.getItemPrice(i.getId()) * i.getQuantity(); - if (i.getId() > 0 && value > 0) - { - priceMap.put(value, i); - } - } - wealth += value; - } - return Integer.parseInt(QuantityFormatter.quantityToRSDecimalStack(priceMap.keySet().stream().mapToInt(Integer::intValue).sum())); - - } -} \ No newline at end of file +} diff --git a/runelite-client/src/main/java/net/runelite/client/util/ReflectUtil.java b/runelite-client/src/main/java/net/runelite/client/util/ReflectUtil.java index 60b3b305f1..fbcc28d28d 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/ReflectUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/ReflectUtil.java @@ -25,6 +25,8 @@ */ package net.runelite.client.util; +import com.google.common.io.ByteStreams; +import java.io.IOException; import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -36,7 +38,7 @@ public class ReflectUtil { } - public static MethodHandles.Lookup privateLookupIn(Class clazz) + public static MethodHandles.Lookup privateLookupIn(Class clazz) { try { @@ -44,7 +46,16 @@ public class ReflectUtil // we need to access it via reflection. This is preferred way because it's Java 9+ public api and is // likely to not change final Method privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class); - return (MethodHandles.Lookup) privateLookupIn.invoke(null, clazz, MethodHandles.lookup()); + MethodHandles.Lookup caller; + if (clazz.getClassLoader() instanceof PrivateLookupableClassLoader) + { + caller = ((PrivateLookupableClassLoader) clazz.getClassLoader()).getLookup(); + } + else + { + caller = MethodHandles.lookup(); + } + return (MethodHandles.Lookup) privateLookupIn.invoke(null, clazz, caller); } catch (InvocationTargetException | IllegalAccessException e) { @@ -69,4 +80,51 @@ public class ReflectUtil } } } + + public interface PrivateLookupableClassLoader + { + // define class is protected final so this needs a different name to become public + Class defineClass0(String name, byte[] b, int off, int len) throws ClassFormatError; + + MethodHandles.Lookup getLookup(); + void setLookup(MethodHandles.Lookup lookup); + } + + /** + * Allows private Lookups to be created for classes in this ClassLoader + *

+ * Due to JDK-8173978 it is impossible to create get a lookup with module scoped permissions when teleporting + * between modules. Since external plugins are loaded in a separate classloader to us they are contained in unique + * unnamed modules. Since we (via LambdaMetafactory) are creating a hidden class in that module, we require module + * scoped access to it, and since the methods can be private, we also require private access. The only way to get + * MODULE|PRIVATE is to either 1) invokedynamic in that class, 2) call MethodHandles.lookup() from that class, or + * 3) call privateLookupIn with an existing lookup with PRIVATE|MODULE created from a class in the same module. + * Our solution is to make classloaders call this method which will define a class in the classloader's unnamed + * module that calls MethodHandles.lookup() and stores it in the classloader for later use. + */ + public static void installLookupHelper(PrivateLookupableClassLoader cl) + { + try + { + String name = PrivateLookupHelper.class.getName(); + byte[] classData = ByteStreams.toByteArray(ReflectUtil.class.getResourceAsStream("/" + name.replace('.', '/') + ".class")); + Class clazz = cl.defineClass0(name, classData, 0, classData.length); + + // force to run + clazz.getConstructor().newInstance(); + } + catch (IOException | ReflectiveOperationException e) + { + throw new RuntimeException("unable to install lookup helper", e); + } + } + + public static class PrivateLookupHelper + { + static + { + PrivateLookupableClassLoader pcl = (PrivateLookupableClassLoader) PrivateLookupHelper.class.getClassLoader(); + pcl.setLookup(MethodHandles.lookup()); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java index 5f226a40c9..1f33ce9638 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/SwingUtil.java @@ -40,6 +40,7 @@ import java.awt.TrayIcon; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; +import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.function.BiConsumer; import javax.annotation.Nonnull; @@ -302,4 +303,19 @@ public class SwingUtil l.enter(); } } + + /** + * Executes a runnable on the EDT, blocking until it finishes. + */ + public static void syncExec(final Runnable r) throws InvocationTargetException, InterruptedException + { + if (EventQueue.isDispatchThread()) + { + r.run(); + } + else + { + EventQueue.invokeAndWait(r); + } + } } diff --git a/runelite-client/src/main/resources/com/openosrs/client/game/npc_stats.json b/runelite-client/src/main/resources/com/openosrs/client/game/npc_stats.json deleted file mode 100644 index 7838d269b1..0000000000 --- a/runelite-client/src/main/resources/com/openosrs/client/game/npc_stats.json +++ /dev/null @@ -1 +0,0 @@ -{"1":{"name":"Molanisk","hitpoints":52,"combatLevel":51,"slayerLevel":39,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":50,"rangeLevel":1,"stabDef":45,"slashDef":45,"crushDef":35,"rangeDef":55,"magicDef":30},"2":{"name":"Aberrant spectre","hitpoints":90,"combatLevel":96,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":105,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":15},"3":{"name":"Aberrant spectre","hitpoints":90,"combatLevel":96,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":105,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":15},"4":{"name":"Aberrant spectre","hitpoints":90,"combatLevel":96,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":105,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":15},"5":{"name":"Aberrant spectre","hitpoints":90,"combatLevel":96,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":105,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":15},"6":{"name":"Aberrant spectre","hitpoints":90,"combatLevel":96,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":105,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":15},"7":{"name":"Aberrant spectre","hitpoints":90,"combatLevel":96,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":105,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":15},"8":{"name":"Nechryael","hitpoints":105,"combatLevel":115,"slayerLevel":80,"attackSpeed":4,"attackLevel":97,"strengthLevel":97,"defenceLevel":105,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"10":{"name":"Death spawn","hitpoints":60,"combatLevel":46,"attackSpeed":4,"attackLevel":67,"strengthLevel":7,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"11":{"name":"Nechryael","hitpoints":105,"combatLevel":115,"slayerLevel":80,"attackSpeed":4,"attackLevel":97,"strengthLevel":97,"defenceLevel":105,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"26":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"27":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"28":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"29":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"30":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"31":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"32":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"33":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"34":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"35":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"36":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"37":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"38":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"39":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"40":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"41":{"name":"Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"42":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"43":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"44":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"45":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"46":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"47":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"48":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"49":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"50":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"51":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"52":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"53":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"54":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"55":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"56":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"57":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"58":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"59":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"60":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"61":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"62":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"63":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"64":{"name":"Zombie","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":8,"crushDef":12,"rangeDef":10,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"65":{"name":"Zombie","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":8,"crushDef":12,"rangeDef":10,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"66":{"name":"Zombie","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":8,"crushDef":12,"rangeDef":10,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"67":{"name":"Zombie","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":8,"crushDef":12,"rangeDef":10,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"68":{"name":"Zombie","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":19,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":8,"crushDef":12,"rangeDef":10,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"69":{"name":"Summoned Zombie","hitpoints":22,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":9,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"70":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"71":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"72":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"73":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"74":{"name":"Skeleton","hitpoints":24,"combatLevel":21,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"75":{"name":"Skeleton","hitpoints":24,"combatLevel":21,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"76":{"name":"Skeleton","hitpoints":24,"combatLevel":21,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"77":{"name":"Skeleton","hitpoints":17,"combatLevel":25,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"78":{"name":"Skeleton","hitpoints":17,"combatLevel":25,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"79":{"name":"Skeleton","hitpoints":17,"combatLevel":25,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"80":{"name":"Skeleton","hitpoints":17,"combatLevel":25,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"81":{"name":"Skeleton","hitpoints":17,"combatLevel":25,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"82":{"name":"Skeleton","hitpoints":59,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":35,"defenceLevel":36,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"83":{"name":"Skeleton","hitpoints":59,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":35,"defenceLevel":36,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"84":{"name":"Skeleton Mage","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":14,"defenceLevel":14,"rangeLevel":1,"magicLevel":18},"85":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"86":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"87":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"88":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"89":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"90":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"91":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"92":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"93":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"94":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"95":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"96":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"97":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"98":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"99":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"100":{"name":"Rock Crab","hitpoints":50,"combatLevel":13,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"101":{"name":"Rocks","hitpoints":50,"combatLevel":13,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"102":{"name":"Rock Crab","hitpoints":50,"combatLevel":13,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"103":{"name":"Rocks","hitpoints":50,"combatLevel":13,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"104":{"name":"Hellhound","hitpoints":116,"combatLevel":122,"attackSpeed":4,"attackLevel":105,"strengthLevel":104,"defenceLevel":102,"rangeLevel":1,"magicLevel":1},"105":{"name":"Hellhound","hitpoints":116,"combatLevel":122,"attackSpeed":4,"attackLevel":105,"strengthLevel":104,"defenceLevel":102,"rangeLevel":1,"magicLevel":1},"106":{"name":"Wolf","hitpoints":69,"combatLevel":64,"attackSpeed":4,"attackLevel":50,"strengthLevel":55,"defenceLevel":52,"rangeLevel":1,"magicLevel":1},"107":{"name":"White wolf","hitpoints":34,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"108":{"name":"White wolf","hitpoints":44,"combatLevel":38,"attackSpeed":4,"attackLevel":30,"strengthLevel":31,"defenceLevel":32,"rangeLevel":1,"magicLevel":1},"109":{"name":"Big Wolf","hitpoints":74,"combatLevel":73,"attackSpeed":4,"attackLevel":60,"strengthLevel":61,"defenceLevel":62,"rangeLevel":1,"magicLevel":1},"110":{"name":"Wolf","hitpoints":34,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"112":{"name":"Wild dog","hitpoints":62,"combatLevel":63,"attackSpeed":4,"attackLevel":53,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1},"113":{"name":"Wild dog","hitpoints":62,"combatLevel":63,"attackSpeed":4,"attackLevel":53,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1},"114":{"name":"Guard dog","hitpoints":49,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":36,"defenceLevel":37,"rangeLevel":1,"magicLevel":1},"115":{"name":"Big Wolf","hitpoints":74,"combatLevel":73,"attackSpeed":4,"attackLevel":60,"strengthLevel":61,"defenceLevel":62,"rangeLevel":1,"magicLevel":1},"116":{"name":"Wolf","hitpoints":34,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"117":{"name":"Wolf","hitpoints":34,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"130":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1},"131":{"name":"Guard dog","hitpoints":49,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":36,"defenceLevel":37,"rangeLevel":1,"magicLevel":1},"132":{"name":"Hobgoblin","hitpoints":62,"combatLevel":54,"attackSpeed":6,"attackLevel":45,"strengthLevel":43,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"bonusAttack":8,"bonusStrength":10},"133":{"name":"Troll","hitpoints":120,"combatLevel":91,"attackSpeed":4,"attackLevel":50,"strengthLevel":100,"defenceLevel":50,"stabDef":25,"slashDef":25,"crushDef":40,"rangeDef":200,"magicDef":200,"bonusAttack":40,"bonusStrength":70},"134":{"name":"Huge spider","hitpoints":90,"combatLevel":81,"attackSpeed":4,"attackLevel":59,"strengthLevel":70,"defenceLevel":69,"rangeLevel":1,"magicLevel":1},"135":{"name":"Hellhound","hitpoints":116,"combatLevel":122,"attackSpeed":4,"attackLevel":105,"strengthLevel":104,"defenceLevel":102,"rangeLevel":1,"magicLevel":1},"136":{"name":"Ogre","hitpoints":60,"combatLevel":53,"attackSpeed":6,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20},"137":{"name":"Baby red dragon","hitpoints":65,"combatLevel":65,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":55,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"138":{"name":"Kalphite Soldier","hitpoints":90,"combatLevel":85,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"139":{"name":"Steel dragon","hitpoints":210,"combatLevel":246,"attackSpeed":4,"attackLevel":215,"strengthLevel":215,"defenceLevel":215,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"140":{"name":"Dagannoth","hitpoints":142,"combatLevel":135,"attackSpeed":4,"attackLevel":114,"strengthLevel":117,"defenceLevel":98,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50,"bonusAttack":50,"bonusRangeStrength":50},"141":{"name":"Tok-Xil","hitpoints":60,"combatLevel":135,"attackSpeed":4,"attackLevel":120,"strengthLevel":180,"defenceLevel":90,"rangeLevel":180,"magicLevel":90},"142":{"name":"Demon","hitpoints":79,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"143":{"name":"Rocnar","hitpoints":100,"combatLevel":97,"attackSpeed":6,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"bonusAttack":22,"bonusStrength":20},"231":{"name":"Wolf","hitpoints":34,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"232":{"name":"Jungle Wolf","hitpoints":69,"combatLevel":64,"attackSpeed":4,"attackLevel":50,"strengthLevel":55,"defenceLevel":52,"rangeLevel":1,"magicLevel":1},"239":{"name":"King Black Dragon","hitpoints":240,"combatLevel":276,"attackSpeed":4,"attackLevel":240,"strengthLevel":240,"defenceLevel":240,"rangeLevel":1,"magicLevel":240,"stabDef":70,"slashDef":90,"crushDef":90,"rangeDef":70,"magicDef":80},"240":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"241":{"name":"Baby blue dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"242":{"name":"Baby blue dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"243":{"name":"Baby blue dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"244":{"name":"Baby red dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"245":{"name":"Baby red dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"246":{"name":"Baby red dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"247":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"248":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"249":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"250":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"251":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"252":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"253":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"254":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"255":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"256":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"257":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"258":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"259":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"260":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"261":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"262":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"263":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"264":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"265":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"266":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"267":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"268":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"269":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"270":{"name":"Bronze dragon","hitpoints":122,"combatLevel":131,"attackSpeed":4,"attackLevel":112,"strengthLevel":112,"defenceLevel":112,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"271":{"name":"Bronze dragon","hitpoints":122,"combatLevel":131,"attackSpeed":4,"attackLevel":112,"strengthLevel":112,"defenceLevel":112,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"272":{"name":"Iron dragon","hitpoints":165,"combatLevel":189,"attackSpeed":4,"attackLevel":165,"strengthLevel":165,"defenceLevel":165,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"273":{"name":"Iron dragon","hitpoints":165,"combatLevel":189,"attackSpeed":4,"attackLevel":165,"strengthLevel":165,"defenceLevel":165,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"274":{"name":"Steel dragon","hitpoints":210,"combatLevel":246,"attackSpeed":4,"attackLevel":215,"strengthLevel":215,"defenceLevel":215,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"275":{"name":"Steel dragon","hitpoints":210,"combatLevel":246,"attackSpeed":4,"attackLevel":215,"strengthLevel":215,"defenceLevel":215,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"289":{"name":"Ghoul","hitpoints":50,"combatLevel":42,"attackSpeed":6,"attackLevel":30,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"290":{"name":"Dwarf","hitpoints":16,"combatLevel":10,"attackSpeed":5,"attackLevel":8,"strengthLevel":8,"defenceLevel":6,"rangeLevel":1,"magicLevel":1,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"291":{"name":"Chaos dwarf","hitpoints":61,"combatLevel":48,"attackSpeed":4,"attackLevel":38,"strengthLevel":42,"defenceLevel":28,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":34,"crushDef":25,"rangeDef":35,"magicDef":10,"bonusAttack":13,"bonusStrength":9},"292":{"name":"Dwarf","hitpoints":26,"combatLevel":20,"attackSpeed":6,"attackLevel":15,"strengthLevel":16,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"magicDef":5,"bonusAttack":11,"bonusStrength":9},"294":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"295":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"296":{"name":"Dwarf","hitpoints":16,"combatLevel":10,"attackSpeed":5,"attackLevel":8,"strengthLevel":8,"defenceLevel":6,"rangeLevel":1,"magicLevel":1,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"299":{"name":"Gunthor the brave","hitpoints":35,"combatLevel":29,"attackSpeed":6,"attackLevel":22,"strengthLevel":22,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":10,"rangeDef":11,"bonusAttack":8,"bonusStrength":13},"300":{"name":"Jailer","hitpoints":47,"combatLevel":47,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":79,"slashDef":63,"crushDef":47},"301":{"name":"Black Heather","hitpoints":37,"combatLevel":34,"attackSpeed":4,"attackLevel":32,"strengthLevel":26,"defenceLevel":27,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":9,"bonusStrength":9},"302":{"name":"Donny the lad","hitpoints":37,"combatLevel":34,"attackSpeed":4,"attackLevel":32,"strengthLevel":26,"defenceLevel":27,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":9,"bonusStrength":9},"303":{"name":"Speedy Keith","hitpoints":37,"combatLevel":34,"attackSpeed":4,"attackLevel":32,"strengthLevel":26,"defenceLevel":27,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":9,"bonusStrength":9},"304":{"name":"Salarin the twisted","hitpoints":70,"combatLevel":70,"attackSpeed":4,"attackLevel":58,"strengthLevel":58,"defenceLevel":62,"rangeLevel":1,"magicLevel":1},"319":{"name":"Corporeal Beast","hitpoints":2000,"combatLevel":785,"attackSpeed":4,"attackLevel":320,"strengthLevel":320,"defenceLevel":310,"rangeLevel":150,"magicLevel":350,"stabDef":25,"slashDef":200,"crushDef":100,"rangeDef":230,"magicDef":150,"bonusAttack":50,"poisonImmune":true,"venomImmune":true},"320":{"name":"Dark energy core","hitpoints":25,"combatLevel":75,"attackSpeed":2,"attackLevel":1,"strengthLevel":1,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"397":{"name":"Town Guard","hitpoints":22,"combatLevel":22,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":26,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"bonusAttack":6,"bonusStrength":10},"398":{"name":"Town Guard","hitpoints":22,"combatLevel":22,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":26,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"bonusAttack":6,"bonusStrength":10},"399":{"name":"Town Guard","hitpoints":22,"combatLevel":19,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"bonusAttack":6,"bonusStrength":10},"400":{"name":"Town Guard","hitpoints":22,"combatLevel":19,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"bonusAttack":6,"bonusStrength":10},"406":{"name":"Cave crawler","hitpoints":22,"combatLevel":23,"slayerLevel":10,"attackSpeed":4,"attackLevel":22,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"407":{"name":"Cave crawler","hitpoints":22,"combatLevel":23,"slayerLevel":10,"attackSpeed":4,"attackLevel":22,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"408":{"name":"Cave crawler","hitpoints":22,"combatLevel":23,"slayerLevel":10,"attackSpeed":4,"attackLevel":22,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"409":{"name":"Cave crawler","hitpoints":22,"combatLevel":23,"slayerLevel":10,"attackSpeed":4,"attackLevel":22,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"410":{"name":"Kurask","hitpoints":97,"combatLevel":106,"slayerLevel":70,"attackSpeed":4,"attackLevel":67,"strengthLevel":105,"defenceLevel":105,"rangeLevel":1,"magicLevel":1,"slashDef":20,"crushDef":20,"poisonImmune":true,"venomImmune":true},"411":{"name":"Kurask","hitpoints":97,"combatLevel":106,"slayerLevel":70,"attackSpeed":4,"attackLevel":67,"strengthLevel":105,"defenceLevel":105,"rangeLevel":1,"magicLevel":1,"slashDef":20,"crushDef":20,"poisonImmune":true,"venomImmune":true},"412":{"name":"Gargoyle","hitpoints":105,"combatLevel":111,"slayerLevel":75,"attackSpeed":4,"attackLevel":75,"strengthLevel":105,"defenceLevel":107,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":20,"magicDef":20},"413":{"name":"Gargoyle","hitpoints":105,"combatLevel":111,"slayerLevel":75,"attackSpeed":4,"attackLevel":75,"strengthLevel":105,"defenceLevel":107,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":20,"magicDef":20},"414":{"name":"Banshee","hitpoints":22,"combatLevel":23,"slayerLevel":15,"attackSpeed":4,"attackLevel":22,"strengthLevel":15,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"415":{"name":"Abyssal demon","hitpoints":150,"combatLevel":124,"slayerLevel":85,"attackSpeed":4,"attackLevel":97,"strengthLevel":67,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"416":{"name":"Abyssal demon","hitpoints":150,"combatLevel":124,"slayerLevel":85,"attackSpeed":4,"attackLevel":97,"strengthLevel":67,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"417":{"name":"Basilisk","hitpoints":75,"combatLevel":61,"slayerLevel":40,"attackSpeed":4,"attackLevel":30,"strengthLevel":45,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"magicDef":20},"418":{"name":"Basilisk","hitpoints":75,"combatLevel":61,"slayerLevel":40,"attackSpeed":4,"attackLevel":30,"strengthLevel":45,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"magicDef":20},"419":{"name":"Cockatrice","hitpoints":37,"combatLevel":37,"slayerLevel":25,"attackSpeed":4,"attackLevel":22,"strengthLevel":37,"defenceLevel":37,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"magicDef":10},"420":{"name":"Cockatrice","hitpoints":37,"combatLevel":37,"slayerLevel":25,"attackSpeed":4,"attackLevel":22,"strengthLevel":37,"defenceLevel":37,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"magicDef":10},"421":{"name":"Rockslug","hitpoints":27,"combatLevel":29,"slayerLevel":20,"attackSpeed":4,"attackLevel":22,"strengthLevel":27,"defenceLevel":27,"rangeLevel":1,"magicLevel":1},"422":{"name":"Rockslug","hitpoints":27,"combatLevel":29,"slayerLevel":20,"attackSpeed":4,"attackLevel":22,"strengthLevel":27,"defenceLevel":27,"rangeLevel":1,"magicLevel":1},"423":{"name":"Dust devil","hitpoints":105,"combatLevel":93,"slayerLevel":65,"attackSpeed":4,"attackLevel":105,"strengthLevel":70,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"427":{"name":"Turoth","hitpoints":81,"combatLevel":89,"slayerLevel":55,"attackSpeed":4,"attackLevel":58,"strengthLevel":88,"defenceLevel":88,"rangeLevel":1,"magicLevel":1,"slashDef":20,"crushDef":20},"428":{"name":"Turoth","hitpoints":79,"combatLevel":87,"slayerLevel":55,"attackSpeed":4,"attackLevel":56,"strengthLevel":86,"defenceLevel":86,"rangeLevel":1,"magicLevel":1,"slashDef":20,"crushDef":20},"429":{"name":"Turoth","hitpoints":77,"combatLevel":85,"slayerLevel":55,"attackSpeed":4,"attackLevel":54,"strengthLevel":84,"defenceLevel":84,"rangeLevel":1,"magicLevel":1,"slashDef":20,"crushDef":20},"430":{"name":"Turoth","hitpoints":76,"combatLevel":83,"slayerLevel":55,"attackSpeed":4,"attackLevel":53,"strengthLevel":83,"defenceLevel":83,"rangeLevel":1,"magicLevel":1,"slashDef":20,"crushDef":20},"433":{"name":"Pyrefiend","hitpoints":45,"combatLevel":43,"slayerLevel":30,"attackSpeed":4,"attackLevel":52,"strengthLevel":30,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"434":{"name":"Pyrefiend","hitpoints":45,"combatLevel":43,"slayerLevel":30,"attackSpeed":4,"attackLevel":52,"strengthLevel":30,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"435":{"name":"Pyrefiend","hitpoints":45,"combatLevel":43,"slayerLevel":30,"attackSpeed":4,"attackLevel":52,"strengthLevel":30,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"436":{"name":"Pyrefiend","hitpoints":45,"combatLevel":43,"slayerLevel":30,"attackSpeed":4,"attackLevel":52,"strengthLevel":30,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"437":{"name":"Jelly","hitpoints":75,"combatLevel":78,"slayerLevel":52,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":120,"rangeLevel":1,"magicLevel":45},"438":{"name":"Jelly","hitpoints":75,"combatLevel":78,"slayerLevel":52,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":120,"rangeLevel":1,"magicLevel":45},"439":{"name":"Jelly","hitpoints":75,"combatLevel":78,"slayerLevel":52,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":120,"rangeLevel":1,"magicLevel":45},"440":{"name":"Jelly","hitpoints":75,"combatLevel":78,"slayerLevel":52,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":120,"rangeLevel":1,"magicLevel":45},"441":{"name":"Jelly","hitpoints":75,"combatLevel":78,"slayerLevel":52,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":120,"rangeLevel":1,"magicLevel":45},"442":{"name":"Jelly","hitpoints":75,"combatLevel":78,"slayerLevel":52,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":120,"rangeLevel":1,"magicLevel":45},"443":{"name":"Infernal Mage","hitpoints":60,"combatLevel":66,"slayerLevel":45,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":1,"magicLevel":75,"magicDef":40},"444":{"name":"Infernal Mage","hitpoints":60,"combatLevel":66,"slayerLevel":45,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":1,"magicLevel":75,"magicDef":40},"445":{"name":"Infernal Mage","hitpoints":60,"combatLevel":66,"slayerLevel":45,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":1,"magicLevel":75,"magicDef":40},"446":{"name":"Infernal Mage","hitpoints":60,"combatLevel":66,"slayerLevel":45,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":1,"magicLevel":75,"magicDef":40},"447":{"name":"Infernal Mage","hitpoints":60,"combatLevel":66,"slayerLevel":45,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":1,"magicLevel":75,"magicDef":40},"448":{"name":"Crawling Hand","hitpoints":16,"combatLevel":8,"slayerLevel":5,"attackSpeed":4,"attackLevel":8,"strengthLevel":4,"defenceLevel":4,"rangeLevel":1,"magicLevel":1},"449":{"name":"Crawling Hand","hitpoints":15,"combatLevel":7,"slayerLevel":5,"attackSpeed":4,"attackLevel":7,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"450":{"name":"Crawling Hand","hitpoints":15,"combatLevel":7,"slayerLevel":5,"attackSpeed":4,"attackLevel":7,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"451":{"name":"Crawling Hand","hitpoints":15,"combatLevel":7,"slayerLevel":5,"attackSpeed":4,"attackLevel":7,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"452":{"name":"Crawling Hand","hitpoints":15,"combatLevel":7,"slayerLevel":5,"attackSpeed":4,"attackLevel":7,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"453":{"name":"Crawling Hand","hitpoints":19,"combatLevel":12,"slayerLevel":5,"attackSpeed":4,"attackLevel":11,"strengthLevel":7,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"454":{"name":"Crawling Hand","hitpoints":19,"combatLevel":12,"slayerLevel":5,"attackSpeed":4,"attackLevel":11,"strengthLevel":7,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"458":{"name":"Lizard","hitpoints":40,"combatLevel":42,"slayerLevel":22,"attackSpeed":4,"attackLevel":38,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"459":{"name":"Desert Lizard","hitpoints":25,"combatLevel":24,"slayerLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":22,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"460":{"name":"Desert Lizard","hitpoints":25,"combatLevel":24,"slayerLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":22,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"461":{"name":"Desert Lizard","hitpoints":25,"combatLevel":24,"slayerLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":22,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"462":{"name":"Small Lizard","hitpoints":15,"combatLevel":12,"slayerLevel":22,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":5,"crushDef":5},"463":{"name":"Small Lizard","hitpoints":15,"combatLevel":12,"slayerLevel":22,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":5,"crushDef":5},"464":{"name":"Harpie Bug Swarm","hitpoints":25,"combatLevel":46,"slayerLevel":33,"attackSpeed":4,"attackLevel":54,"strengthLevel":46,"defenceLevel":32,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":5,"crushDef":10,"rangeDef":10,"magicDef":5},"465":{"name":"Skeletal Wyvern","hitpoints":200,"combatLevel":140,"slayerLevel":72,"attackSpeed":6,"attackLevel":125,"strengthLevel":116,"defenceLevel":120,"rangeLevel":120,"magicLevel":125,"stabDef":140,"slashDef":90,"crushDef":90,"rangeDef":140,"magicDef":80},"466":{"name":"Skeletal Wyvern","hitpoints":200,"combatLevel":140,"slayerLevel":72,"attackSpeed":6,"attackLevel":125,"strengthLevel":116,"defenceLevel":120,"rangeLevel":120,"magicLevel":125,"stabDef":140,"slashDef":90,"crushDef":90,"rangeDef":140,"magicDef":80},"467":{"name":"Skeletal Wyvern","hitpoints":200,"combatLevel":140,"slayerLevel":72,"attackSpeed":6,"attackLevel":125,"strengthLevel":116,"defenceLevel":120,"rangeLevel":120,"magicLevel":125,"stabDef":140,"slashDef":90,"crushDef":90,"rangeDef":140,"magicDef":80},"468":{"name":"Skeletal Wyvern","hitpoints":200,"combatLevel":140,"slayerLevel":72,"attackSpeed":6,"attackLevel":125,"strengthLevel":116,"defenceLevel":120,"rangeLevel":120,"magicLevel":125,"stabDef":140,"slashDef":90,"crushDef":90,"rangeDef":140,"magicDef":80},"469":{"name":"Killerwatt","hitpoints":51,"combatLevel":55,"slayerLevel":37,"attackSpeed":2,"attackLevel":50,"strengthLevel":50,"defenceLevel":40,"rangeLevel":67,"magicLevel":67,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"magicDef":20,"bonusAttack":30},"470":{"name":"Killerwatt","hitpoints":51,"combatLevel":55,"slayerLevel":37,"attackSpeed":2,"attackLevel":50,"strengthLevel":50,"defenceLevel":40,"rangeLevel":67,"magicLevel":67,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"magicDef":20,"bonusAttack":30},"472":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"473":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"474":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"475":{"name":"Hole in the wall","hitpoints":105,"combatLevel":49,"slayerLevel":35,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"476":{"name":"Wall beast","hitpoints":105,"combatLevel":49,"slayerLevel":35,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"477":{"name":"Giant frog","hitpoints":100,"combatLevel":99,"attackSpeed":4,"attackLevel":100,"strengthLevel":80,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"478":{"name":"Big frog","hitpoints":25,"combatLevel":24,"attackSpeed":4,"attackLevel":25,"strengthLevel":20,"defenceLevel":15,"rangeLevel":1,"magicLevel":1},"480":{"name":"Cave slime","hitpoints":25,"combatLevel":23,"slayerLevel":17,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":35,"rangeLevel":1,"magicLevel":13},"481":{"name":"Cave bug","hitpoints":5,"combatLevel":6,"slayerLevel":7,"attackSpeed":4,"attackLevel":6,"strengthLevel":5,"defenceLevel":6,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"483":{"name":"Cave bug","hitpoints":93,"combatLevel":96,"slayerLevel":7,"attackSpeed":4,"attackLevel":82,"strengthLevel":80,"defenceLevel":84,"rangeLevel":1,"magicLevel":1,"stabDef":72,"slashDef":59,"crushDef":35,"rangeDef":95,"magicDef":25},"484":{"name":"Bloodveld","hitpoints":120,"combatLevel":76,"slayerLevel":50,"attackSpeed":4,"attackLevel":75,"strengthLevel":45,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"485":{"name":"Bloodveld","hitpoints":120,"combatLevel":76,"slayerLevel":50,"attackSpeed":4,"attackLevel":75,"strengthLevel":45,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"486":{"name":"Bloodveld","hitpoints":120,"combatLevel":76,"slayerLevel":50,"attackSpeed":4,"attackLevel":75,"strengthLevel":45,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"487":{"name":"Bloodveld","hitpoints":120,"combatLevel":76,"slayerLevel":50,"attackSpeed":4,"attackLevel":75,"strengthLevel":45,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"492":{"name":"Cave kraken","hitpoints":125,"combatLevel":127,"slayerLevel":87,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":150,"rangeLevel":1,"magicLevel":120,"rangeDef":100},"493":{"name":"Whirlpool","slayerLevel":87,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"rangeDef":300,"magicDef":130},"494":{"name":"Kraken","hitpoints":255,"combatLevel":291,"slayerLevel":87,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"rangeDef":300,"magicDef":130},"496":{"name":"Whirlpool","slayerLevel":87,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"rangeDef":300,"magicDef":130},"498":{"name":"Smoke devil","hitpoints":185,"combatLevel":160,"slayerLevel":93,"attackSpeed":4,"attackLevel":140,"strengthLevel":130,"defenceLevel":275,"rangeLevel":195,"magicLevel":1,"rangeDef":44,"magicDef":600,"poisonImmune":true,"venomImmune":true},"499":{"name":"Thermonuclear smoke devil","hitpoints":240,"combatLevel":301,"slayerLevel":93,"attackSpeed":2,"attackLevel":230,"strengthLevel":220,"defenceLevel":360,"rangeLevel":310,"magicLevel":1,"stabDef":11,"slashDef":4,"crushDef":9,"rangeDef":900,"magicDef":800,"poisonImmune":true,"venomImmune":true},"505":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"506":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"507":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"508":{"name":"Soulless","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"509":{"name":"Death wing","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"510":{"name":"Dark wizard","hitpoints":24,"combatLevel":20,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":14,"rangeLevel":1,"magicLevel":22,"magicDef":3},"511":{"name":"Invrigar the Necromancer","hitpoints":24,"combatLevel":20,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":14,"rangeLevel":1,"magicLevel":18,"magicDef":3},"512":{"name":"Dark wizard","hitpoints":12,"combatLevel":7,"attackSpeed":4,"attackLevel":5,"strengthLevel":2,"defenceLevel":5,"rangeLevel":1,"magicLevel":6,"magicDef":3},"513":{"name":"Mugger","hitpoints":8,"combatLevel":6,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":1},"516":{"name":"Black Knight","hitpoints":42,"combatLevel":33,"attackSpeed":5,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":73,"slashDef":76,"crushDef":70,"rangeDef":72,"bonusAttack":18,"bonusStrength":16},"517":{"name":"Black Knight","hitpoints":42,"combatLevel":33,"attackSpeed":5,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":73,"slashDef":76,"crushDef":70,"rangeDef":72,"bonusAttack":18,"bonusStrength":16},"518":{"name":"Highwayman","hitpoints":13,"combatLevel":5,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":6,"bonusStrength":7},"519":{"name":"Highwayman","hitpoints":13,"combatLevel":5,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":6,"bonusStrength":7},"520":{"name":"Chaos druid","hitpoints":20,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":12,"rangeLevel":1,"magicLevel":10},"521":{"name":"Pirate","hitpoints":20,"combatLevel":23,"attackSpeed":5,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"slashDef":1,"bonusAttack":9,"bonusStrength":10},"522":{"name":"Pirate","hitpoints":20,"combatLevel":23,"attackSpeed":5,"attackLevel":21,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":2,"bonusAttack":8,"bonusStrength":10},"523":{"name":"Pirate","hitpoints":23,"combatLevel":26,"attackSpeed":5,"attackLevel":23,"strengthLevel":23,"defenceLevel":23,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":2,"bonusAttack":8,"bonusStrength":10},"525":{"name":"Thug","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":7,"strengthLevel":5,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":3,"crushDef":3,"bonusAttack":5,"bonusStrength":5},"526":{"name":"Rogue","hitpoints":17,"combatLevel":15,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":6,"slashDef":9,"crushDef":11,"bonusAttack":5,"bonusStrength":5},"527":{"name":"Monk of Zamorak","hitpoints":20,"combatLevel":22,"attackSpeed":4,"attackLevel":18,"strengthLevel":18,"defenceLevel":22,"rangeLevel":1,"magicLevel":25},"528":{"name":"Monk of Zamorak","hitpoints":10,"combatLevel":17,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":12,"rangeLevel":1,"magicLevel":25},"529":{"name":"Monk of Zamorak","hitpoints":40,"combatLevel":45,"attackSpeed":4,"attackLevel":38,"strengthLevel":38,"defenceLevel":42,"rangeLevel":1,"magicLevel":40},"530":{"name":"Tribesman","hitpoints":39,"combatLevel":32,"attackSpeed":4,"attackLevel":23,"strengthLevel":27,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":6,"bonusAttack":8,"bonusStrength":5},"531":{"name":"Dark warrior","hitpoints":17,"combatLevel":8,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":1,"stabDef":96,"slashDef":79,"crushDef":59,"bonusAttack":20,"bonusStrength":16},"532":{"name":"Chaos druid warrior","hitpoints":40,"combatLevel":37,"attackSpeed":5,"attackLevel":32,"strengthLevel":34,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":13,"slashDef":17,"crushDef":14,"rangeDef":14,"bonusAttack":9,"bonusStrength":5},"537":{"name":"Zygomite","hitpoints":65,"combatLevel":74,"slayerLevel":57,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":65,"magicLevel":65,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"magicDef":20,"bonusAttack":30},"561":{"name":"Sorebones","hitpoints":52,"combatLevel":57,"attackSpeed":4,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"562":{"name":"Sorebones","hitpoints":52,"combatLevel":57,"attackSpeed":4,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"563":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"564":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"565":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"566":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"567":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"568":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"569":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"570":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"571":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"572":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"573":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"574":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"575":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"576":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"577":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"578":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"579":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"580":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"581":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"582":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"583":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"584":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"585":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"586":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"587":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"588":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"589":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"590":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"591":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"592":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"593":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"594":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"595":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"596":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"597":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"598":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"599":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"600":{"name":"Barrelchest","hitpoints":134,"combatLevel":190,"attackSpeed":5,"attackLevel":170,"strengthLevel":145,"defenceLevel":140,"rangeLevel":1,"magicLevel":90,"bonusAttack":80,"bonusStrength":80},"613":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"614":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"615":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"616":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"617":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"618":{"name":"Zombie pirate","hitpoints":52,"combatLevel":57,"attackSpeed":3,"attackLevel":49,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"619":{"name":"Zombie swab","hitpoints":50,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"620":{"name":"Zombie swab","hitpoints":50,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"621":{"name":"Zombie swab","hitpoints":50,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"622":{"name":"Zombie swab","hitpoints":50,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"623":{"name":"Zombie swab","hitpoints":50,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"624":{"name":"Zombie swab","hitpoints":50,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"625":{"name":"Evil spirit","hitpoints":90,"combatLevel":150,"attackSpeed":4,"attackLevel":170,"strengthLevel":146,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"626":{"name":"Fever spider","hitpoints":40,"combatLevel":49,"slayerLevel":42,"attackSpeed":4,"attackLevel":60,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":15,"crushDef":10,"rangeDef":15,"magicDef":15},"639":{"name":"Tyras guard","hitpoints":110,"combatLevel":110,"attackSpeed":5,"attackLevel":85,"strengthLevel":95,"defenceLevel":100,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":30},"642":{"name":"Arrg","hitpoints":140,"combatLevel":113,"attackSpeed":4,"attackLevel":70,"strengthLevel":140,"defenceLevel":40,"rangeLevel":70,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"643":{"name":"Arrg","hitpoints":140,"combatLevel":113,"attackSpeed":4,"attackLevel":70,"strengthLevel":140,"defenceLevel":40,"rangeLevel":70,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"645":{"name":"Ice wolf","hitpoints":70,"combatLevel":96,"attackSpeed":4,"attackLevel":100,"strengthLevel":90,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"646":{"name":"Ice wolf","hitpoints":70,"combatLevel":96,"attackSpeed":4,"attackLevel":100,"strengthLevel":90,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"647":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"648":{"name":"Ice troll","hitpoints":80,"combatLevel":124,"attackSpeed":4,"attackLevel":140,"strengthLevel":120,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"649":{"name":"Ice troll","hitpoints":80,"combatLevel":123,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"650":{"name":"Ice troll","hitpoints":100,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"651":{"name":"Ice troll","hitpoints":80,"combatLevel":121,"attackSpeed":4,"attackLevel":100,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"652":{"name":"Ice troll","hitpoints":100,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"653":{"name":"Ice troll","hitpoints":100,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"654":{"name":"Ice troll","hitpoints":80,"combatLevel":121,"attackSpeed":4,"attackLevel":100,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"655":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"656":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"657":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"658":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"659":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"660":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"661":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"662":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"663":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"664":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"665":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"666":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"667":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"668":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"674":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"677":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"678":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"680":{"name":"Giant skeleton","hitpoints":70,"combatLevel":80,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"681":{"name":"Giant skeleton","hitpoints":70,"combatLevel":80,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"682":{"name":"Damis","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":80},"683":{"name":"Damis","hitpoints":200,"combatLevel":174,"attackSpeed":3,"attackLevel":160,"strengthLevel":100,"defenceLevel":160,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":120,"magicDef":80,"bonusStrength":100},"685":{"name":"Stranger","hitpoints":80,"combatLevel":95,"attackSpeed":4,"attackLevel":90,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"magic":1,"stabDef":81,"slashDef":93,"crushDef":98,"rangeDef":82,"magicDef":1,"bonusStrength":40},"690":{"name":"Bandit","hitpoints":65,"combatLevel":74,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"695":{"name":"Bandit","hitpoints":50,"combatLevel":57,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"699":{"name":"Ice troll","hitpoints":80,"combatLevel":124,"attackSpeed":4,"attackLevel":140,"strengthLevel":120,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"700":{"name":"Ice troll","hitpoints":80,"combatLevel":123,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"701":{"name":"Ice troll","hitpoints":100,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"702":{"name":"Ice troll","hitpoints":80,"combatLevel":121,"attackSpeed":4,"attackLevel":100,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"703":{"name":"Ice troll","hitpoints":100,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"704":{"name":"Ice troll","hitpoints":100,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"705":{"name":"Ice troll","hitpoints":80,"combatLevel":121,"attackSpeed":4,"attackLevel":100,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"710":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"711":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"712":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"713":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"714":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"715":{"name":"Ice wolf","hitpoints":70,"combatLevel":132,"attackSpeed":4,"attackLevel":150,"strengthLevel":150,"defenceLevel":70},"717":{"name":"Mummy","hitpoints":1,"combatLevel":96,"attackSpeed":4,"attackLevel":255,"strengthLevel":2,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100},"720":{"name":"Mummy","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":90,"bonusStrength":90},"721":{"name":"Mummy","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":90,"bonusStrength":90},"722":{"name":"Mummy","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":90,"bonusStrength":90},"723":{"name":"Mummy","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":90,"bonusStrength":90},"724":{"name":"Mummy","hitpoints":10,"combatLevel":98,"attackSpeed":4,"attackLevel":255,"strengthLevel":2,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100},"725":{"name":"Mummy","hitpoints":10,"combatLevel":98,"attackSpeed":4,"attackLevel":255,"strengthLevel":2,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100},"726":{"name":"Mummy","hitpoints":10,"combatLevel":98,"attackSpeed":4,"attackLevel":255,"strengthLevel":2,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100},"727":{"name":"Mummy","hitpoints":10,"combatLevel":98,"attackSpeed":4,"attackLevel":255,"strengthLevel":2,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100},"728":{"name":"Mummy","hitpoints":10,"combatLevel":98,"attackSpeed":4,"attackLevel":255,"strengthLevel":2,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100},"729":{"name":"Scarabs","hitpoints":25,"combatLevel":92,"attackSpeed":1,"attackLevel":255,"strengthLevel":2,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":15,"magicDef":15},"734":{"name":"Bandit","hitpoints":30,"combatLevel":56,"attackSpeed":4,"attackLevel":59,"strengthLevel":70,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"slashDef":9,"crushDef":8,"magicDef":10,"bonusAttack":9,"bonusStrength":9},"735":{"name":"Bandit","hitpoints":30,"combatLevel":56,"attackSpeed":4,"attackLevel":59,"strengthLevel":70,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"slashDef":9,"crushDef":8,"magicDef":10,"bonusAttack":9,"bonusStrength":9},"736":{"name":"Bandit","hitpoints":30,"combatLevel":41,"attackSpeed":4,"attackLevel":40,"strengthLevel":50,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"slashDef":9,"crushDef":8,"magicDef":10,"bonusAttack":9,"bonusStrength":9},"737":{"name":"Bandit","hitpoints":30,"combatLevel":41,"attackSpeed":4,"attackLevel":40,"strengthLevel":50,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"slashDef":9,"crushDef":8,"magicDef":10,"bonusAttack":9,"bonusStrength":9},"738":{"name":"Bandit champion","hitpoints":50,"combatLevel":70,"attackSpeed":4,"attackLevel":59,"strengthLevel":80,"defenceLevel":50,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":9,"bonusStrength":9,"poisonImmune":true},"762":{"name":"Baby Roc","hitpoints":50,"combatLevel":75,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":150},"763":{"name":"Giant Roc","hitpoints":250,"combatLevel":172,"attackSpeed":6,"attackLevel":130,"strengthLevel":130,"defenceLevel":100,"rangeLevel":130,"magicLevel":1,"magicDef":150},"764":{"name":"Shadow","hitpoints":15,"combatLevel":73,"attackSpeed":4,"attackLevel":90,"strengthLevel":120,"defenceLevel":5,"rangeLevel":1,"magicLevel":90,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":30,"magicDef":30},"773":{"name":"Skraeling","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"774":{"name":"Skraeling","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"785":{"name":"Me","hitpoints":45,"combatLevel":79,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":74,"rangeLevel":1,"magicLevel":1},"786":{"name":"Me","hitpoints":45,"combatLevel":79,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":74,"rangeLevel":1,"magicLevel":1},"787":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"788":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"789":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"790":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"791":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"792":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"793":{"name":"Suqah","hitpoints":105,"combatLevel":111,"attackSpeed":6,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":90},"794":{"name":"Scarab mage","hitpoints":50,"combatLevel":93,"attackSpeed":15,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":70,"magic":70,"stabDef":40,"slashDef":90,"crushDef":90,"magicDef":34},"795":{"name":"Locust rider","hitpoints":90,"combatLevel":106,"attackSpeed":6,"attackLevel":100,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":40},"796":{"name":"Locust rider","hitpoints":90,"combatLevel":98,"attackSpeed":6,"attackLevel":105,"strengthLevel":90,"defenceLevel":50,"rangeLevel":90,"magicLevel":1,"range":80,"stabDef":50,"slashDef":90,"crushDef":50,"rangeDef":66,"magicDef":34,"bonusRangeStrength":80},"797":{"name":"Giant scarab","hitpoints":130,"combatLevel":191,"attackSpeed":4,"attackLevel":169,"strengthLevel":190,"defenceLevel":169,"rangeLevel":190,"magicLevel":1,"stabDef":70,"slashDef":99,"crushDef":99,"rangeDef":149,"magicDef":159},"798":{"name":"Giant scarab","hitpoints":130,"combatLevel":191,"attackSpeed":4,"attackLevel":169,"strengthLevel":190,"defenceLevel":169,"rangeLevel":190,"magicLevel":1,"stabDef":70,"slashDef":99,"crushDef":99,"rangeDef":149,"magicDef":159},"799":{"name":"Scarab mage","hitpoints":50,"combatLevel":66,"attackSpeed":15,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":70,"magic":70,"stabDef":40,"slashDef":90,"crushDef":90,"magicDef":34},"800":{"name":"Locust rider","hitpoints":90,"combatLevel":68,"attackSpeed":6,"attackLevel":100,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":40},"801":{"name":"Locust rider","hitpoints":90,"combatLevel":68,"attackSpeed":6,"attackLevel":105,"strengthLevel":90,"defenceLevel":50,"rangeLevel":90,"magicLevel":1,"range":80,"stabDef":50,"slashDef":90,"crushDef":50,"rangeDef":66,"magicDef":34,"bonusRangeStrength":80},"817":{"name":"Elvarg","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":70,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"820":{"name":"Wormbrain","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"823":{"name":"Melzar the Mad","hitpoints":44,"combatLevel":43,"attackSpeed":4,"attackLevel":37,"strengthLevel":37,"defenceLevel":34,"rangeLevel":1,"magicLevel":40,"poisonImmune":true,"venomImmune":true},"852":{"name":"Icelord","hitpoints":60,"combatLevel":51,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"853":{"name":"Icelord","hitpoints":60,"combatLevel":51,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"854":{"name":"Icelord","hitpoints":60,"combatLevel":51,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"855":{"name":"Icelord","hitpoints":60,"combatLevel":51,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"866":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"867":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"868":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"869":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"870":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"871":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"872":{"name":"Skogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"873":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"874":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"875":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"876":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"877":{"name":"Zogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"878":{"name":"Skogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"879":{"name":"Skogre","hitpoints":71,"combatLevel":44,"attackSpeed":6,"attackLevel":20,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"880":{"name":"Zombie","hitpoints":50,"combatLevel":39,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"882":{"name":"Slash Bash","hitpoints":100,"combatLevel":111,"attackSpeed":6,"attackLevel":100,"strengthLevel":120,"defenceLevel":60,"rangeLevel":100,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":30,"bonusAttack":22},"891":{"name":"Moss Guardian","hitpoints":120,"combatLevel":84,"attackSpeed":6,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"bonusAttack":66,"bonusStrength":62},"911":{"name":"Agrith Naar","hitpoints":95,"combatLevel":100,"attackSpeed":4,"attackLevel":83,"strengthLevel":90,"defenceLevel":82,"rangeLevel":100,"magicLevel":100},"924":{"name":"Skeleton","hitpoints":18,"combatLevel":13,"attackSpeed":4},"925":{"name":"Rock","hitpoints":140,"combatLevel":111,"attackSpeed":4,"attackLevel":70,"strengthLevel":110,"defenceLevel":70,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"926":{"name":"Stick","hitpoints":135,"combatLevel":104,"attackSpeed":6,"attackLevel":60,"strengthLevel":110,"defenceLevel":60,"stabDef":30,"slashDef":30,"crushDef":50,"rangeDef":200,"magicDef":200,"bonusAttack":50,"bonusStrength":80},"927":{"name":"Pee Hat","hitpoints":120,"combatLevel":91,"attackSpeed":6,"attackLevel":50,"strengthLevel":100,"defenceLevel":50,"stabDef":25,"slashDef":25,"crushDef":40,"rangeDef":200,"magicDef":200,"bonusAttack":40,"bonusStrength":70},"928":{"name":"Kraka","hitpoints":120,"combatLevel":91,"attackSpeed":6,"attackLevel":50,"strengthLevel":100,"defenceLevel":50,"stabDef":25,"slashDef":25,"crushDef":40,"rangeDef":200,"magicDef":200,"bonusAttack":40,"bonusStrength":70},"931":{"name":"Thrower Troll","hitpoints":95,"combatLevel":67,"attackSpeed":7,"attackLevel":30,"strengthLevel":80,"defenceLevel":30,"rangeLevel":60,"rangeDef":200,"magicDef":200},"932":{"name":"Thrower Troll","hitpoints":95,"combatLevel":67,"attackSpeed":7,"attackLevel":30,"strengthLevel":80,"defenceLevel":30,"rangeLevel":60,"rangeDef":200,"magicDef":200},"933":{"name":"Thrower Troll","hitpoints":95,"combatLevel":67,"attackSpeed":7,"attackLevel":30,"strengthLevel":80,"defenceLevel":30,"rangeLevel":60,"rangeDef":200,"magicDef":200},"934":{"name":"Thrower Troll","hitpoints":95,"combatLevel":67,"attackSpeed":7,"attackLevel":30,"strengthLevel":80,"defenceLevel":30,"rangeLevel":60,"rangeDef":200,"magicDef":200},"935":{"name":"Thrower Troll","hitpoints":95,"combatLevel":67,"attackSpeed":7,"attackLevel":30,"strengthLevel":80,"defenceLevel":30,"rangeLevel":60,"rangeDef":200,"magicDef":200},"936":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"937":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"938":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"939":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"940":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"941":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"942":{"name":"Mountain troll","hitpoints":90,"combatLevel":69,"attackSpeed":6,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"946":{"name":"Ghast","hitpoints":45,"combatLevel":30,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"949":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"950":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"951":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"952":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"953":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"955":{"name":"Kalphite Worker","hitpoints":40,"combatLevel":28,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":1,"rangeDef":10,"magicDef":10},"956":{"name":"Kalphite Worker","hitpoints":40,"combatLevel":28,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":1,"rangeDef":10,"magicDef":10},"957":{"name":"Kalphite Soldier","hitpoints":90,"combatLevel":85,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"958":{"name":"Kalphite Soldier","hitpoints":90,"combatLevel":85,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"959":{"name":"Kalphite Guardian","hitpoints":170,"combatLevel":141,"attackSpeed":4,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"960":{"name":"Kalphite Guardian","hitpoints":170,"combatLevel":141,"attackSpeed":4,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"961":{"name":"Kalphite Worker","hitpoints":40,"combatLevel":28,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":1,"rangeDef":10,"magicDef":10},"962":{"name":"Kalphite Guardian","hitpoints":170,"combatLevel":141,"attackSpeed":4,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"963":{"name":"Kalphite Queen","hitpoints":255,"combatLevel":333,"attackSpeed":4,"attackLevel":300,"strengthLevel":300,"defenceLevel":300,"rangeLevel":1,"magicLevel":150,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":100,"magicDef":100},"965":{"name":"Kalphite Queen","hitpoints":255,"combatLevel":333,"attackSpeed":4,"attackLevel":300,"strengthLevel":300,"defenceLevel":300,"rangeLevel":1,"magicLevel":150,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":10,"magicDef":10},"970":{"name":"Dagannoth","hitpoints":70,"combatLevel":74,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"971":{"name":"Dagannoth","hitpoints":70,"combatLevel":74,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"972":{"name":"Dagannoth","hitpoints":70,"combatLevel":74,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"973":{"name":"Dagannoth","hitpoints":120,"combatLevel":92,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1,"rangeDef":50,"magicDef":50},"974":{"name":"Dagannoth","hitpoints":120,"combatLevel":92,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1,"rangeDef":50,"magicDef":50},"975":{"name":"Dagannoth","hitpoints":120,"combatLevel":92,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1,"rangeDef":50,"magicDef":50},"976":{"name":"Dagannoth","hitpoints":160,"combatLevel":100},"977":{"name":"Dagannoth","hitpoints":160,"combatLevel":100},"978":{"name":"Dagannoth","hitpoints":160,"combatLevel":100},"979":{"name":"Dagannoth","hitpoints":160,"combatLevel":100},"980":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"981":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"982":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"983":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"984":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"985":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"986":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"987":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"988":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"990":{"name":"Sigmund","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"magic":8,"venomImmune":true},"991":{"name":"Sigmund","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"magic":8,"venomImmune":true},"992":{"name":"Sigmund","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"magic":8,"venomImmune":true},"993":{"name":"Sigmund","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"magic":8,"venomImmune":true},"994":{"name":"Sigmund","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"magic":8,"venomImmune":true},"1024":{"name":"Zygomite","hitpoints":75,"combatLevel":86,"slayerLevel":57,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":75,"rangeLevel":75,"magicLevel":75,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"magicDef":20,"bonusAttack":30},"1025":{"name":"Necromancer","hitpoints":40,"combatLevel":26,"attackSpeed":4,"attackLevel":18,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":18},"1026":{"name":"Bandit","hitpoints":27,"combatLevel":22,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":11,"bonusStrength":12},"1027":{"name":"Guard Bandit","hitpoints":27,"combatLevel":22,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":8,"crushDef":5,"rangeDef":4,"magicDef":1,"bonusAttack":11,"bonusStrength":12},"1037":{"name":"Snake","hitpoints":25,"combatLevel":35,"attackSpeed":3,"attackLevel":25,"strengthLevel":50,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"1038":{"name":"Monkey","hitpoints":6,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"1039":{"name":"Albino bat","hitpoints":33,"combatLevel":52,"attackSpeed":4,"attackLevel":57,"strengthLevel":57,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"1041":{"name":"Giant mosquito","hitpoints":3,"combatLevel":13,"attackSpeed":12,"attackLevel":5,"strengthLevel":1,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":2,"crushDef":5,"magicDef":2},"1042":{"name":"Jungle horror","hitpoints":45,"combatLevel":70,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"1043":{"name":"Jungle horror","hitpoints":45,"combatLevel":70,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"1044":{"name":"Jungle horror","hitpoints":45,"combatLevel":70,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"1045":{"name":"Jungle horror","hitpoints":45,"combatLevel":70,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"1046":{"name":"Jungle horror","hitpoints":45,"combatLevel":70,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"1047":{"name":"Cave horror","hitpoints":55,"combatLevel":80,"slayerLevel":58,"attackSpeed":4,"attackLevel":80,"strengthLevel":77,"defenceLevel":62,"rangeLevel":1,"magicLevel":80},"1048":{"name":"Cave horror","hitpoints":55,"combatLevel":80,"slayerLevel":58,"attackSpeed":4,"attackLevel":80,"strengthLevel":77,"defenceLevel":62,"rangeLevel":1,"magicLevel":80},"1049":{"name":"Cave horror","hitpoints":55,"combatLevel":80,"slayerLevel":58,"attackSpeed":4,"attackLevel":80,"strengthLevel":77,"defenceLevel":62,"rangeLevel":1,"magicLevel":80},"1050":{"name":"Cave horror","hitpoints":55,"combatLevel":80,"slayerLevel":58,"attackSpeed":4,"attackLevel":80,"strengthLevel":77,"defenceLevel":62,"rangeLevel":1,"magicLevel":80},"1051":{"name":"Cave horror","hitpoints":55,"combatLevel":80,"slayerLevel":58,"attackSpeed":4,"attackLevel":80,"strengthLevel":77,"defenceLevel":62,"rangeLevel":1,"magicLevel":80},"1060":{"name":"Angry bear","combatLevel":40},"1061":{"name":"Angry unicorn","hitpoints":50,"combatLevel":45},"1062":{"name":"Angry giant rat","hitpoints":50,"combatLevel":45},"1063":{"name":"Angry giant rat","hitpoints":50,"combatLevel":45},"1064":{"name":"Angry giant rat","hitpoints":50,"combatLevel":45},"1065":{"name":"Angry goblin","hitpoints":50,"combatLevel":45},"1066":{"name":"Fear reaper","hitpoints":25,"combatLevel":42,"attackSpeed":4,"attackLevel":39,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"1067":{"name":"Confusion beast","hitpoints":28,"combatLevel":43,"attackSpeed":4,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"1068":{"name":"Confusion beast","hitpoints":28,"combatLevel":43,"attackSpeed":4,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"1069":{"name":"Confusion beast","hitpoints":28,"combatLevel":43,"attackSpeed":4,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"1070":{"name":"Confusion beast","hitpoints":28,"combatLevel":43,"attackSpeed":4,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"1071":{"name":"Confusion beast","hitpoints":28,"combatLevel":43,"attackSpeed":4,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"1072":{"name":"Hopeless creature","hitpoints":25,"combatLevel":40,"attackSpeed":4,"attackLevel":38,"strengthLevel":36,"defenceLevel":39,"rangeLevel":1,"magicLevel":1},"1073":{"name":"Hopeless creature","hitpoints":25,"combatLevel":40,"attackSpeed":4,"attackLevel":38,"strengthLevel":36,"defenceLevel":39,"rangeLevel":1,"magicLevel":1},"1074":{"name":"Hopeless creature","hitpoints":25,"combatLevel":40,"attackSpeed":4,"attackLevel":38,"strengthLevel":36,"defenceLevel":39,"rangeLevel":1,"magicLevel":1},"1075":{"name":"Tolna","hitpoints":37,"combatLevel":46,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"rangeDef":50,"magicDef":50},"1076":{"name":"Tolna","hitpoints":37,"combatLevel":46,"attackSpeed":5,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"rangeDef":50,"magicDef":50},"1077":{"name":"Tolna","hitpoints":37,"combatLevel":46,"attackLevel":42,"strengthLevel":41,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"rangeDef":50,"magicDef":50},"1097":{"name":"Sea Snake Young","hitpoints":85,"combatLevel":90,"slayerLevel":40,"attackSpeed":4,"attackLevel":90,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"1098":{"name":"Sea Snake Hatchling","hitpoints":50,"combatLevel":62,"slayerLevel":40,"attackSpeed":4,"attackLevel":60,"strengthLevel":55,"defenceLevel":50,"magicLevel":1},"1101":{"name":"Giant Sea Snake","hitpoints":100,"combatLevel":149,"attackSpeed":4,"attackLevel":170,"strengthLevel":90,"defenceLevel":160,"rangeLevel":130,"magicLevel":1},"1115":{"name":"Mourner","hitpoints":25,"combatLevel":24,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":2,"crushDef":4},"1118":{"name":"Man","hitpoints":13,"combatLevel":4,"attackSpeed":4,"attackLevel":2,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"crushDef":1},"1119":{"name":"Woman","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"1126":{"name":"Barrelchest (hard)","hitpoints":255,"combatLevel":380,"attackSpeed":5,"attackLevel":306,"strengthLevel":261,"defenceLevel":140,"rangeLevel":1,"magicLevel":162,"bonusAttack":80,"bonusStrength":80},"1127":{"name":"Giant scarab (hard)","hitpoints":255,"combatLevel":316,"attackSpeed":4,"attackLevel":305,"strengthLevel":342,"defenceLevel":169,"rangeLevel":342,"magicLevel":1,"stabDef":70,"slashDef":99,"crushDef":99,"rangeDef":149,"magicDef":159},"1128":{"name":"Dessous (hard)","hitpoints":255,"combatLevel":217,"attackSpeed":3,"attackLevel":198,"strengthLevel":198,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"1129":{"name":"Kamil (hard)","hitpoints":255,"combatLevel":273,"attackSpeed":4,"attackLevel":380,"strengthLevel":160,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":60,"crushDef":35,"bonusAttack":60,"bonusStrength":100},"1130":{"name":"Woman","hitpoints":13,"combatLevel":4,"attackSpeed":4,"attackLevel":2,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"crushDef":1},"1131":{"name":"Woman","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"1134":{"name":"Damis (hard)","hitpoints":198,"combatLevel":200,"attackSpeed":4,"attackLevel":198,"strengthLevel":198,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":80},"1135":{"name":"Damis (hard)","hitpoints":255,"combatLevel":272,"attackSpeed":3,"attackLevel":320,"strengthLevel":200,"defenceLevel":160,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":120,"magicDef":80,"bonusStrength":100},"1139":{"name":"Woman","hitpoints":13,"combatLevel":4,"attackSpeed":4,"attackLevel":2,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"crushDef":1},"1140":{"name":"Woman","hitpoints":13,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"1141":{"name":"Woman","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"1142":{"name":"Woman","hitpoints":23,"combatLevel":14,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"1144":{"name":"Paladin","hitpoints":66,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"1148":{"name":"Mourner","hitpoints":25,"combatLevel":24,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":1,"crushDef":3},"1153":{"name":"Ogre","hitpoints":60,"combatLevel":63,"attackSpeed":6,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":19,"slashDef":23,"crushDef":24,"bonusAttack":8,"bonusStrength":6},"1163":{"name":"Tree spirit","hitpoints":85,"combatLevel":101,"attackSpeed":4,"attackLevel":90,"strengthLevel":95,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"1173":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1174":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1175":{"name":"Rooster","hitpoints":7,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1204":{"name":"Alomone","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":6,"crushDef":8},"1206":{"name":"Clivet","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":6,"crushDef":8},"1207":{"name":"Hazeel Cultist","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":6,"crushDef":8},"1208":{"name":"Khazard Guard","hitpoints":25,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":18,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":29,"crushDef":21,"bonusAttack":10,"bonusStrength":14},"1210":{"name":"Khazard Guard","hitpoints":25,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":18,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":29,"crushDef":21,"bonusAttack":10,"bonusStrength":14},"1211":{"name":"Khazard Guard","hitpoints":25,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":18,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":29,"crushDef":21,"bonusAttack":10,"bonusStrength":14},"1213":{"name":"General Khazard","hitpoints":170,"combatLevel":112,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"1224":{"name":"Bouncer","hitpoints":116,"combatLevel":137,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"1225":{"name":"Khazard Ogre","hitpoints":60,"combatLevel":63,"attackSpeed":6,"attackLevel":54,"strengthLevel":53,"defenceLevel":53,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20},"1226":{"name":"Khazard Scorpion","hitpoints":40,"combatLevel":44,"attackSpeed":4,"attackLevel":40,"strengthLevel":39,"defenceLevel":34,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"1227":{"name":"Arzinian Avatar of Strength","attackSpeed":4},"1228":{"name":"Arzinian Avatar of Strength","hitpoints":100,"combatLevel":125,"attackSpeed":4,"attackLevel":110,"strengthLevel":100,"defenceLevel":95,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":80,"magicDef":15},"1229":{"name":"Arzinian Avatar of Strength","hitpoints":70,"combatLevel":75,"attackSpeed":4,"attackLevel":70,"strengthLevel":65,"defenceLevel":50,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":40,"magicDef":10},"1230":{"name":"Arzinian Avatar of Ranging","attackSpeed":4},"1231":{"name":"Arzinian Avatar of Ranging","hitpoints":100,"combatLevel":125,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":120,"rangeLevel":110,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":20,"magicDef":80,"bonusRangeStrength":10},"1232":{"name":"Arzinian Avatar of Ranging","hitpoints":70,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":40,"defenceLevel":75,"rangeLevel":75,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":15,"magicDef":40,"bonusRangeStrength":5},"1233":{"name":"Arzinian Avatar of Magic","attackSpeed":4},"1234":{"name":"Arzinian Avatar of Magic","hitpoints":100,"combatLevel":125,"attackSpeed":4,"attackLevel":10,"strengthLevel":90,"defenceLevel":120,"magicLevel":120,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":15,"magicDef":20},"1235":{"name":"Arzinian Avatar of Magic","hitpoints":70,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":50,"defenceLevel":75,"magicLevel":75,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":10,"magicDef":15},"1261":{"name":"Ram","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1262":{"name":"Ram","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1263":{"name":"Ram","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1264":{"name":"Ram","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1265":{"name":"Ram","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1267":{"name":"Vulture","hitpoints":10,"combatLevel":31,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"1268":{"name":"Vulture","hitpoints":10,"combatLevel":31,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"1273":{"name":"Experiment","hitpoints":40,"combatLevel":51,"attackSpeed":4,"attackLevel":40,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"1274":{"name":"Experiment","hitpoints":100,"combatLevel":25,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"1275":{"name":"Experiment","hitpoints":100,"combatLevel":25,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"1276":{"name":"Loar Shadow","hitpoints":38,"combatLevel":40,"attackSpeed":4,"attackLevel":45,"strengthLevel":30,"defenceLevel":26},"1277":{"name":"Loar Shade","hitpoints":38,"combatLevel":40,"attackSpeed":4,"attackLevel":45,"strengthLevel":30,"defenceLevel":26},"1279":{"name":"Phrin Shadow","hitpoints":56,"combatLevel":60,"attackSpeed":4,"attackLevel":64,"strengthLevel":47,"defenceLevel":42},"1280":{"name":"Phrin Shade","hitpoints":56,"combatLevel":60,"attackSpeed":4,"attackLevel":64,"strengthLevel":47,"defenceLevel":42},"1281":{"name":"Riyl Shadow","hitpoints":76,"combatLevel":80,"attackSpeed":4,"attackLevel":88,"strengthLevel":55,"defenceLevel":60},"1282":{"name":"Riyl Shade","hitpoints":76,"combatLevel":80,"attackSpeed":4,"attackLevel":88,"strengthLevel":55,"defenceLevel":60},"1283":{"name":"Asyn Shadow","hitpoints":90,"combatLevel":100,"attackSpeed":4,"attackLevel":102,"strengthLevel":84,"defenceLevel":70},"1284":{"name":"Asyn Shade","hitpoints":90,"combatLevel":100,"attackSpeed":4,"attackLevel":102,"strengthLevel":84,"defenceLevel":70},"1285":{"name":"Fiyr Shadow","hitpoints":110,"combatLevel":120,"attackSpeed":6,"attackLevel":120,"strengthLevel":100,"defenceLevel":85},"1286":{"name":"Fiyr Shade","hitpoints":110,"combatLevel":120,"attackSpeed":6,"attackLevel":120,"strengthLevel":100,"defenceLevel":85},"1293":{"name":"Afflicted","hitpoints":30,"combatLevel":37,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"1294":{"name":"Afflicted","hitpoints":28,"combatLevel":34,"attackSpeed":4,"attackLevel":28,"strengthLevel":28,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"1297":{"name":"Afflicted","hitpoints":26,"combatLevel":32,"attackSpeed":4,"attackLevel":26,"strengthLevel":26,"defenceLevel":36,"rangeLevel":1,"magicLevel":1},"1298":{"name":"Afflicted","hitpoints":24,"combatLevel":30,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":34,"rangeLevel":1,"magicLevel":1},"1338":{"name":"Seagull","hitpoints":6,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1339":{"name":"Seagull","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1354":{"name":"Dwarf gang member","hitpoints":40,"combatLevel":44,"attackSpeed":5,"attackLevel":40,"strengthLevel":40,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"1355":{"name":"Dwarf gang member","hitpoints":25,"combatLevel":48,"attackSpeed":5,"attackLevel":80,"strengthLevel":30,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"1356":{"name":"Dwarf gang member","hitpoints":25,"combatLevel":49,"attackSpeed":5,"attackLevel":30,"strengthLevel":60,"defenceLevel":57,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"1362":{"name":"Slagilith","hitpoints":60,"combatLevel":92,"attackSpeed":4,"attackLevel":60,"strengthLevel":120,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":10},"1364":{"name":"Slagilith","hitpoints":60,"combatLevel":92,"attackSpeed":4,"attackLevel":60,"strengthLevel":120,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":10},"1365":{"name":"Fire elemental","hitpoints":30,"combatLevel":35,"attackSpeed":4,"attackLevel":35,"strengthLevel":35,"defenceLevel":20,"rangeLevel":20,"magicLevel":20},"1366":{"name":"Earth elemental","hitpoints":35,"combatLevel":35,"attackSpeed":6,"attackLevel":20,"strengthLevel":35,"defenceLevel":35,"rangeLevel":30,"magicLevel":10},"1367":{"name":"Earth elemental","hitpoints":35,"combatLevel":35,"attackSpeed":6,"attackLevel":20,"strengthLevel":35,"defenceLevel":35,"rangeLevel":30,"magicLevel":10,"poisonImmune":true,"venomImmune":true},"1369":{"name":"Air elemental","hitpoints":30,"combatLevel":34,"attackSpeed":4,"attackLevel":40,"strengthLevel":20,"defenceLevel":30,"rangeLevel":20,"magicLevel":40},"1370":{"name":"Water elemental","hitpoints":30,"combatLevel":34,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":30,"magicLevel":30},"1377":{"name":"The Kendal","hitpoints":50,"combatLevel":70,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":60,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"bonusAttack":5,"bonusStrength":10},"1378":{"name":"The Kendal","hitpoints":50,"combatLevel":70,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":60,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"bonusAttack":5,"bonusStrength":10},"1379":{"name":"Camp dweller","hitpoints":30,"combatLevel":31,"attackSpeed":4,"attackLevel":25,"strengthLevel":30,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"1380":{"name":"Camp dweller","hitpoints":30,"combatLevel":31,"attackSpeed":4,"attackLevel":25,"strengthLevel":30,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"1381":{"name":"Camp dweller","hitpoints":30,"combatLevel":31,"attackSpeed":4,"attackLevel":25,"strengthLevel":30,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"1382":{"name":"Camp dweller","hitpoints":30,"combatLevel":31,"attackSpeed":4,"attackLevel":25,"strengthLevel":30,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"1383":{"name":"Camp dweller","hitpoints":25,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":25,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"1401":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1402":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1403":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1404":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1405":{"name":"Dwarf","hitpoints":16,"combatLevel":10,"attackSpeed":5,"attackLevel":8,"strengthLevel":8,"defenceLevel":6,"rangeLevel":1,"magicLevel":1,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1406":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1407":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1408":{"name":"Dwarf","hitpoints":10,"combatLevel":7,"attackSpeed":4,"attackLevel":6,"strengthLevel":6,"defenceLevel":6,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"1409":{"name":"Black Guard","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"1410":{"name":"Black Guard","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"1411":{"name":"Black Guard","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"1412":{"name":"Black Guard","hitpoints":30,"combatLevel":25,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"1429":{"name":"Foreman","hitpoints":20,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"slashDef":1,"crushDef":1},"1430":{"name":"Shipyard worker","hitpoints":10,"combatLevel":11,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"bonusAttack":6,"bonusStrength":6},"1432":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"1443":{"name":"Jungle Demon","hitpoints":170,"combatLevel":195,"attackSpeed":6,"attackLevel":170,"strengthLevel":170,"defenceLevel":170,"rangeLevel":1,"magicLevel":170,"slashDef":50,"magicDef":50,"bonusAttack":50,"bonusStrength":50},"1447":{"name":"Pirate","hitpoints":20,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"slashDef":1,"bonusAttack":9,"bonusStrength":10},"1448":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"1461":{"name":"Mugger","hitpoints":8,"combatLevel":6,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":1},"1475":{"name":"Chompy bird","hitpoints":10,"combatLevel":6,"attackLevel":5,"strengthLevel":5,"defenceLevel":3},"1537":{"name":"Skeleton hero","hitpoints":124,"combatLevel":149,"attackSpeed":4,"attackLevel":145,"strengthLevel":134,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"1538":{"name":"Skeleton brute","hitpoints":124,"combatLevel":132,"attackSpeed":4,"attackLevel":115,"strengthLevel":114,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"1539":{"name":"Skeleton warlord","hitpoints":124,"combatLevel":132,"attackSpeed":4,"attackLevel":115,"strengthLevel":114,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"1540":{"name":"Skeleton heavy","hitpoints":124,"combatLevel":132,"attackSpeed":4,"attackLevel":115,"strengthLevel":114,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"1541":{"name":"Skeleton thug","hitpoints":124,"combatLevel":132,"attackSpeed":4,"attackLevel":115,"strengthLevel":114,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"1543":{"name":"Gargoyle","hitpoints":105,"combatLevel":111,"slayerLevel":75,"attackSpeed":4,"attackLevel":75,"strengthLevel":105,"defenceLevel":107,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":20,"magicDef":20},"1545":{"name":"Black knight","hitpoints":42,"combatLevel":33,"attackSpeed":5,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":73,"slashDef":76,"crushDef":70,"rangeDef":72,"bonusAttack":18,"bonusStrength":16},"1546":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"1547":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"1548":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"1549":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"1550":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"1556":{"name":"Fire wizard","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":10,"magicLevel":10,"magicDef":15},"1557":{"name":"Water wizard","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":10,"magicLevel":10,"magicDef":15},"1558":{"name":"Earth wizard","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":10,"magicLevel":10,"magicDef":15},"1559":{"name":"Air wizard","hitpoints":25,"combatLevel":13,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":10,"magicLevel":10,"magicDef":15},"1603":{"name":"Kolodion","hitpoints":3,"attackSpeed":7},"1604":{"name":"Kolodion","hitpoints":3,"attackSpeed":7},"1605":{"name":"Kolodion","hitpoints":3,"attackSpeed":7},"1606":{"name":"Kolodion","hitpoints":65,"attackSpeed":7},"1607":{"name":"Kolodion","hitpoints":65,"attackSpeed":7},"1608":{"name":"Kolodion","hitpoints":78,"attackSpeed":7},"1609":{"name":"Kolodion","hitpoints":107,"combatLevel":112,"attackSpeed":4,"attackLevel":85,"strengthLevel":98,"defenceLevel":105,"rangeLevel":1,"magicLevel":80},"1610":{"name":"Battle mage","hitpoints":120,"combatLevel":54,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":50},"1611":{"name":"Battle mage","hitpoints":120,"combatLevel":54,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":50},"1612":{"name":"Battle mage","hitpoints":120,"combatLevel":54,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":50},"1667":{"name":"Penance Fighter","hitpoints":28,"combatLevel":30,"attackSpeed":4,"defenceLevel":25},"1668":{"name":"Penance Ranger","hitpoints":20,"combatLevel":21,"attackSpeed":4,"defenceLevel":21},"1672":{"name":"Ahrim the Blighted","hitpoints":100,"combatLevel":98,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":1,"magicLevel":100,"magic":73,"stabDef":103,"slashDef":85,"crushDef":117,"magicDef":73,"bonusStrength":68},"1673":{"name":"Dharok the Wretched","hitpoints":100,"combatLevel":115,"attackSpeed":7,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":252,"slashDef":250,"crushDef":244,"rangeDef":249,"bonusStrength":105},"1674":{"name":"Guthan the Infested","hitpoints":100,"combatLevel":115,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":259,"slashDef":257,"crushDef":241,"rangeDef":250,"bonusStrength":75},"1675":{"name":"Karil the Tainted","hitpoints":100,"combatLevel":98,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":100,"magicLevel":1,"range":134,"stabDef":79,"slashDef":71,"crushDef":90,"rangeDef":100,"magicDef":106,"bonusRangeStrength":55},"1676":{"name":"Torag the Corrupted","hitpoints":100,"combatLevel":115,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":221,"slashDef":235,"crushDef":222,"rangeDef":221,"bonusStrength":72},"1677":{"name":"Verac the Defiled","hitpoints":100,"combatLevel":115,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":227,"slashDef":230,"crushDef":221,"rangeDef":225,"bonusStrength":72},"1678":{"name":"Bloodworm","hitpoints":45,"combatLevel":52,"attackSpeed":4,"attackLevel":80,"strengthLevel":20,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"1679":{"name":"Crypt rat","hitpoints":35,"combatLevel":43,"attackSpeed":4,"attackLevel":70,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"1680":{"name":"Giant crypt rat","hitpoints":70,"combatLevel":76,"attackSpeed":4,"attackLevel":80,"strengthLevel":50,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"1681":{"name":"Giant crypt rat","hitpoints":70,"combatLevel":76,"attackSpeed":4,"attackLevel":80,"strengthLevel":50,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"1682":{"name":"Giant crypt rat","hitpoints":70,"combatLevel":76,"attackSpeed":4,"attackLevel":80,"strengthLevel":50,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"1683":{"name":"Crypt spider","hitpoints":45,"combatLevel":56,"attackSpeed":4,"attackLevel":45,"strengthLevel":47,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":10,"rangeDef":20,"magicDef":17},"1684":{"name":"Giant crypt spider","hitpoints":80,"combatLevel":79,"attackSpeed":4,"attackLevel":65,"strengthLevel":67,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"1685":{"name":"Skeleton","hitpoints":51,"combatLevel":77,"attackSpeed":4,"attackLevel":72,"strengthLevel":72,"defenceLevel":72,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"1686":{"name":"Skeleton","hitpoints":51,"combatLevel":77,"attackSpeed":4,"attackLevel":72,"strengthLevel":72,"defenceLevel":72,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"1687":{"name":"Skeleton","hitpoints":51,"combatLevel":77,"attackSpeed":4,"attackLevel":72,"strengthLevel":72,"defenceLevel":72,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"1688":{"name":"Skeleton","hitpoints":51,"combatLevel":77,"attackSpeed":4,"attackLevel":72,"strengthLevel":72,"defenceLevel":72,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"1689":{"name":"Splatter","hitpoints":13,"combatLevel":22,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":33,"magicLevel":17},"1690":{"name":"Splatter","hitpoints":23,"combatLevel":33,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":53,"magicLevel":22},"1691":{"name":"Splatter","hitpoints":33,"combatLevel":44,"attackSpeed":4,"attackLevel":27,"strengthLevel":27,"defenceLevel":73,"magicLevel":27},"1692":{"name":"Splatter","hitpoints":43,"combatLevel":54,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":93,"magicLevel":32},"1693":{"name":"Splatter","hitpoints":53,"combatLevel":65,"attackSpeed":4,"attackLevel":37,"strengthLevel":37,"defenceLevel":113,"magicLevel":37},"1694":{"name":"Shifter","hitpoints":23,"combatLevel":38,"attackSpeed":4,"attackLevel":47,"strengthLevel":37,"defenceLevel":23,"magicLevel":27},"1695":{"name":"Shifter","hitpoints":23,"combatLevel":38,"attackSpeed":4,"attackLevel":47,"strengthLevel":37,"defenceLevel":23,"magicLevel":27},"1696":{"name":"Shifter","hitpoints":38,"combatLevel":57,"attackSpeed":4,"attackLevel":67,"strengthLevel":52,"defenceLevel":38,"magicLevel":37},"1697":{"name":"Shifter","hitpoints":38,"combatLevel":57,"attackSpeed":4,"attackLevel":67,"strengthLevel":52,"defenceLevel":38,"magicLevel":37},"1698":{"name":"Shifter","hitpoints":53,"combatLevel":76,"attackSpeed":4,"attackLevel":87,"strengthLevel":67,"defenceLevel":53,"magicLevel":47},"1699":{"name":"Shifter","hitpoints":53,"combatLevel":76,"attackSpeed":4,"attackLevel":87,"strengthLevel":67,"defenceLevel":53,"magicLevel":47},"1700":{"name":"Shifter","hitpoints":68,"combatLevel":90,"attackSpeed":4,"attackLevel":100,"strengthLevel":75,"defenceLevel":68,"magicLevel":50},"1701":{"name":"Shifter","hitpoints":68,"combatLevel":90,"attackSpeed":4,"attackLevel":100,"strengthLevel":75,"defenceLevel":68,"magicLevel":50},"1702":{"name":"Shifter","hitpoints":83,"combatLevel":104,"attackSpeed":4,"attackLevel":110,"strengthLevel":83,"defenceLevel":83,"magicLevel":60},"1703":{"name":"Shifter","hitpoints":83,"combatLevel":104,"attackSpeed":4,"attackLevel":110,"strengthLevel":83,"defenceLevel":83,"magicLevel":60},"1704":{"name":"Ravager","hitpoints":23,"combatLevel":36,"attackSpeed":4,"attackLevel":27,"strengthLevel":57,"defenceLevel":13},"1705":{"name":"Ravager","hitpoints":38,"combatLevel":53,"attackSpeed":4,"attackLevel":37,"strengthLevel":82,"defenceLevel":23},"1706":{"name":"Ravager","hitpoints":53,"combatLevel":71,"attackSpeed":4,"attackLevel":47,"strengthLevel":107,"defenceLevel":33},"1707":{"name":"Ravager","hitpoints":68,"combatLevel":89,"attackSpeed":4,"attackLevel":57,"strengthLevel":132,"defenceLevel":43},"1708":{"name":"Ravager","hitpoints":83,"combatLevel":106,"attackSpeed":4,"attackLevel":67,"strengthLevel":157,"defenceLevel":53},"1709":{"name":"Spinner","hitpoints":33,"combatLevel":36,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":43,"magicLevel":37},"1710":{"name":"Spinner","hitpoints":53,"combatLevel":55,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":68,"magicLevel":52},"1711":{"name":"Spinner","hitpoints":73,"combatLevel":74,"attackSpeed":4,"attackLevel":27,"strengthLevel":27,"defenceLevel":93,"magicLevel":67},"1712":{"name":"Spinner","hitpoints":101,"combatLevel":92,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":118,"magicLevel":82},"1713":{"name":"Spinner","hitpoints":93,"combatLevel":88,"attackSpeed":4,"attackLevel":37,"strengthLevel":37,"defenceLevel":143,"magicLevel":37},"1714":{"name":"Torcher","hitpoints":18,"combatLevel":33,"attackSpeed":4,"defenceLevel":25,"magicLevel":46},"1715":{"name":"Torcher","hitpoints":18,"combatLevel":33,"attackSpeed":4,"defenceLevel":25,"magicLevel":46},"1716":{"name":"Torcher","hitpoints":30,"combatLevel":49,"attackSpeed":4,"defenceLevel":41,"magicLevel":66},"1717":{"name":"Torcher","hitpoints":30,"combatLevel":49,"attackSpeed":4,"defenceLevel":41,"magicLevel":66},"1718":{"name":"Torcher","hitpoints":45,"combatLevel":66,"attackSpeed":4,"defenceLevel":57,"magicLevel":85},"1719":{"name":"Torcher","hitpoints":45,"combatLevel":66,"attackSpeed":4,"defenceLevel":57,"magicLevel":85},"1720":{"name":"Torcher","hitpoints":57,"combatLevel":79,"attackSpeed":4,"defenceLevel":73,"magicLevel":97},"1721":{"name":"Torcher","hitpoints":57,"combatLevel":79,"attackSpeed":4,"defenceLevel":73,"magicLevel":97},"1722":{"name":"Torcher","hitpoints":71,"combatLevel":91,"attackSpeed":4,"defenceLevel":89,"magicLevel":107},"1723":{"name":"Torcher","hitpoints":71,"combatLevel":92,"attackSpeed":4,"defenceLevel":89,"magicLevel":107},"1724":{"name":"Defiler","hitpoints":27,"combatLevel":33,"attackSpeed":4,"defenceLevel":17,"rangeLevel":46},"1725":{"name":"Defiler","hitpoints":27,"combatLevel":33,"attackSpeed":4,"defenceLevel":17,"rangeLevel":46},"1726":{"name":"Defiler","hitpoints":45,"combatLevel":50,"attackSpeed":4,"defenceLevel":29,"rangeLevel":66},"1727":{"name":"Defiler","hitpoints":45,"combatLevel":50,"attackSpeed":4,"defenceLevel":29,"rangeLevel":66},"1728":{"name":"Defiler","hitpoints":62,"combatLevel":66,"attackSpeed":4,"defenceLevel":41,"rangeLevel":84},"1729":{"name":"Defiler","hitpoints":62,"combatLevel":67,"attackSpeed":4,"defenceLevel":41,"rangeLevel":85},"1730":{"name":"Defiler","hitpoints":78,"combatLevel":80,"attackSpeed":4,"defenceLevel":53,"rangeLevel":98},"1731":{"name":"Defiler","hitpoints":78,"combatLevel":80,"attackSpeed":4,"defenceLevel":53,"rangeLevel":98},"1732":{"name":"Defiler","hitpoints":97,"combatLevel":97,"attackSpeed":4,"defenceLevel":65,"rangeLevel":117},"1733":{"name":"Defiler","hitpoints":97,"combatLevel":97,"attackSpeed":4,"defenceLevel":65,"rangeLevel":117},"1734":{"name":"Brawler","hitpoints":53,"combatLevel":51,"attackSpeed":4,"attackLevel":27,"strengthLevel":57,"defenceLevel":43},"1735":{"name":"Brawler","hitpoints":83,"combatLevel":76,"attackSpeed":4,"attackLevel":37,"strengthLevel":82,"defenceLevel":67},"1736":{"name":"Brawler","hitpoints":113,"combatLevel":101,"attackSpeed":4,"attackLevel":47,"strengthLevel":107,"defenceLevel":93},"1737":{"name":"Brawler","hitpoints":143,"combatLevel":129,"attackSpeed":4,"attackLevel":67,"strengthLevel":132,"defenceLevel":118},"1738":{"name":"Brawler","hitpoints":173,"combatLevel":158,"attackSpeed":4,"attackLevel":87,"strengthLevel":157,"defenceLevel":143},"1739":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1740":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1741":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1742":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1743":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1744":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1745":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1746":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1747":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1748":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1749":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1750":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1751":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1752":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1753":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1754":{"name":"Portal","defenceLevel":85,"rangeDef":70,"magicDef":70},"1777":{"name":"Double agent","hitpoints":80,"combatLevel":65,"attackSpeed":4,"attackLevel":80,"strengthLevel":43,"defenceLevel":24,"rangeLevel":1,"magicLevel":24,"stabDef":3,"slashDef":3,"crushDef":3},"1778":{"name":"Double agent","hitpoints":120,"combatLevel":108,"attackSpeed":4,"attackLevel":100,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":80,"stabDef":20,"slashDef":20,"crushDef":20,"bonusAttack":40,"bonusStrength":40},"1782":{"name":"Scarab swarm","hitpoints":25,"combatLevel":98,"attackSpeed":1,"attackLevel":255,"strengthLevel":5,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":5,"rangeDef":90,"magicDef":90},"1792":{"name":"Goat","hitpoints":21,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"rangeDef":10,"bonusAttack":29,"bonusStrength":10},"1793":{"name":"Goat","hitpoints":21,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"rangeDef":10,"bonusAttack":29,"bonusStrength":10},"1794":{"name":"Billy Goat","hitpoints":28,"combatLevel":33,"attackSpeed":4,"attackLevel":31,"strengthLevel":29,"defenceLevel":29,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"rangeDef":10,"bonusAttack":29,"bonusStrength":10},"1795":{"name":"Goat","hitpoints":21,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"rangeDef":10,"bonusAttack":29,"bonusStrength":10},"1796":{"name":"Goat","hitpoints":21,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"rangeDef":10,"bonusAttack":29,"bonusStrength":10},"1797":{"name":"Billy Goat","hitpoints":28,"combatLevel":33,"attackSpeed":4,"attackLevel":31,"strengthLevel":29,"defenceLevel":29,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"rangeDef":10,"bonusAttack":29,"bonusStrength":10},"1798":{"name":"White Knight","hitpoints":52,"combatLevel":36,"attackSpeed":7,"attackLevel":27,"strengthLevel":29,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":83,"slashDef":76,"crushDef":70,"rangeDef":74,"bonusAttack":30,"bonusStrength":31},"1799":{"name":"White Knight","hitpoints":52,"combatLevel":38,"attackSpeed":7,"attackLevel":30,"strengthLevel":29,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":83,"slashDef":76,"crushDef":70,"rangeDef":74,"bonusAttack":30,"bonusStrength":31},"1800":{"name":"White Knight","hitpoints":52,"combatLevel":39,"attackSpeed":7,"attackLevel":32,"strengthLevel":29,"defenceLevel":27,"rangeLevel":1,"magicLevel":1,"stabDef":83,"slashDef":76,"crushDef":70,"rangeDef":74,"bonusAttack":30,"bonusStrength":31},"1829":{"name":"White Knight","hitpoints":55,"combatLevel":42,"attackSpeed":7,"attackLevel":32,"strengthLevel":35,"defenceLevel":27,"rangeLevel":1,"magicLevel":1,"stabDef":83,"slashDef":76,"crushDef":70,"rangeDef":74,"bonusAttack":30,"bonusStrength":31},"1834":{"name":"Gorak","hitpoints":112,"combatLevel":145,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":131,"rangeLevel":1,"magicLevel":1},"1838":{"name":"Duck","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1839":{"name":"Duck","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1845":{"name":"Stag","hitpoints":19,"combatLevel":15,"attackSpeed":4,"attackLevel":11,"strengthLevel":13,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"1852":{"name":"Rabbit","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1853":{"name":"Rabbit","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"1861":{"name":"Tree spirit","hitpoints":25,"combatLevel":14,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":8,"rangeLevel":1,"magicLevel":1},"1862":{"name":"Tree spirit","hitpoints":40,"combatLevel":29,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1},"1863":{"name":"Tree spirit","hitpoints":60,"combatLevel":49,"attackSpeed":4,"attackLevel":38,"strengthLevel":38,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"1864":{"name":"Tree spirit","hitpoints":86,"combatLevel":79,"attackSpeed":4,"attackLevel":64,"strengthLevel":64,"defenceLevel":64,"rangeLevel":1,"magicLevel":1},"1865":{"name":"Tree spirit","hitpoints":120,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"1866":{"name":"Tree spirit","hitpoints":170,"combatLevel":159,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1},"1870":{"name":"Evil Chicken","hitpoints":120,"combatLevel":159,"attackSpeed":4,"defenceLevel":126,"magicLevel":200},"1871":{"name":"Baby black dragon","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"1872":{"name":"Baby black dragon","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"1874":{"name":"Ice troll runt","hitpoints":60,"combatLevel":74,"attackSpeed":4,"attackLevel":60,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"1875":{"name":"Ice troll male","hitpoints":80,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"1876":{"name":"Ice troll female","hitpoints":80,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"1877":{"name":"Ice troll grunt","hitpoints":80,"combatLevel":102,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"2001":{"name":"Duckling","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2005":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"2006":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"2007":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"2008":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"2018":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"2025":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2026":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2027":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2028":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2029":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2030":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2031":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2032":{"name":"Greater demon","hitpoints":89,"combatLevel":92,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"2042":{"name":"Zulrah","hitpoints":500,"combatLevel":725,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":300,"rangeLevel":300,"magicLevel":300,"range":50,"magic":50,"rangeDef":50,"bonusRangeStrength":20,"bonusMagicDamage":20},"2043":{"name":"Zulrah","hitpoints":500,"combatLevel":725,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":300,"rangeLevel":300,"magicLevel":300,"range":50,"magic":50,"rangeDef":300,"bonusRangeStrength":20,"bonusMagicDamage":20},"2044":{"name":"Zulrah","hitpoints":500,"combatLevel":725,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":300,"rangeLevel":300,"magicLevel":300,"range":50,"magic":50,"magicDef":300,"bonusRangeStrength":20,"bonusMagicDamage":20},"2045":{"name":"Snakeling","hitpoints":1,"combatLevel":90,"attackSpeed":3,"attackLevel":140,"strengthLevel":138,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"bonusAttack":120},"2046":{"name":"Snakeling","hitpoints":1,"combatLevel":90,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":185,"bonusAttack":120},"2048":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"2049":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"2050":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"2051":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"2052":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"2054":{"name":"Chaos Elemental","hitpoints":250,"combatLevel":305,"attackSpeed":5,"attackLevel":270,"strengthLevel":270,"defenceLevel":270,"rangeLevel":270,"magicLevel":270,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":70,"magicDef":70},"2056":{"name":"Dark wizard","hitpoints":25,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":10,"defenceLevel":20,"rangeLevel":1,"magicLevel":25,"magicDef":5},"2057":{"name":"Dark wizard","hitpoints":24,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":10,"defenceLevel":15,"rangeLevel":1,"magicLevel":25,"magicDef":5},"2058":{"name":"Dark wizard","hitpoints":15,"combatLevel":11,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":10,"rangeLevel":1,"magicLevel":10,"magicDef":5},"2059":{"name":"Dark wizard","hitpoints":15,"combatLevel":11,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":10,"rangeLevel":1,"magicLevel":10,"magicDef":5},"2062":{"name":"Oomlie bird","hitpoints":40,"combatLevel":46,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"2063":{"name":"Penguin","hitpoints":4,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":2,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2064":{"name":"Terrorbird","hitpoints":34,"combatLevel":28,"attackSpeed":6,"attackLevel":23,"strengthLevel":23,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"2065":{"name":"Terrorbird","hitpoints":34,"combatLevel":28,"attackSpeed":6,"attackLevel":23,"strengthLevel":23,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"2066":{"name":"Terrorbird","hitpoints":34,"combatLevel":28,"attackSpeed":6,"attackLevel":23,"strengthLevel":23,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"2067":{"name":"Mounted terrorbird gnome","hitpoints":36,"combatLevel":31,"attackSpeed":4,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"stabDef":16,"slashDef":16,"crushDef":18,"rangeDef":10,"magicDef":15},"2068":{"name":"Mounted terrorbird gnome","hitpoints":55,"combatLevel":49,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":16,"slashDef":16,"crushDef":18,"rangeDef":10,"magicDef":15,"bonusRangeStrength":2},"2075":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2076":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2077":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2078":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2079":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2080":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2081":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2082":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2083":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2084":{"name":"Fire giant","hitpoints":111,"combatLevel":86,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2085":{"name":"Ice giant","hitpoints":70,"combatLevel":53,"attackSpeed":5,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2086":{"name":"Ice giant","hitpoints":70,"combatLevel":53,"attackSpeed":5,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2087":{"name":"Ice giant","hitpoints":70,"combatLevel":53,"attackSpeed":5,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2088":{"name":"Ice giant","hitpoints":70,"combatLevel":53,"attackSpeed":5,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2089":{"name":"Ice giant","hitpoints":70,"combatLevel":53,"attackSpeed":5,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"2090":{"name":"Moss giant","hitpoints":60,"combatLevel":42,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"2091":{"name":"Moss giant","hitpoints":60,"combatLevel":42,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"2092":{"name":"Moss giant","hitpoints":60,"combatLevel":42,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"2093":{"name":"Moss giant","hitpoints":60,"combatLevel":42,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"2094":{"name":"Jogre","hitpoints":60,"combatLevel":53,"attackSpeed":6,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20},"2095":{"name":"Ogre","hitpoints":60,"combatLevel":53,"attackSpeed":6,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20},"2096":{"name":"Ogre","hitpoints":60,"combatLevel":53,"attackSpeed":6,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20},"2097":{"name":"Cyclops","hitpoints":75,"combatLevel":56,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2098":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"2099":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"2100":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"2101":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"2102":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"2103":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"2115":{"name":"Thing under the bed","hitpoints":25,"attackSpeed":4},"2120":{"name":"Market Guard","hitpoints":22,"combatLevel":20,"attackSpeed":5,"attackLevel":17,"strengthLevel":18,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16,"magicDef":4,"bonusAttack":9,"bonusStrength":7},"2137":{"name":"Cyclops","hitpoints":150,"combatLevel":106,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"2138":{"name":"Cyclops","hitpoints":150,"combatLevel":106,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"2139":{"name":"Cyclops","hitpoints":150,"combatLevel":106,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"2140":{"name":"Cyclops","hitpoints":150,"combatLevel":106,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"2141":{"name":"Cyclops","hitpoints":150,"combatLevel":106,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"2142":{"name":"Cyclops","hitpoints":150,"combatLevel":106,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"2145":{"name":"Undead Druid","hitpoints":140,"combatLevel":105,"attackSpeed":4,"attackLevel":60,"strengthLevel":50,"defenceLevel":60,"magicLevel":115,"magic":100,"stabDef":40,"slashDef":30,"crushDef":80,"rangeDef":40,"magicDef":140,"bonusAttack":50,"bonusStrength":50,"bonusMagicDamage":50},"2154":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2155":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2156":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2157":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2158":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2159":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2160":{"name":"TzHaar-Mej","hitpoints":100,"combatLevel":103,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":120},"2161":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"2162":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"2163":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"2164":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"2165":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"2166":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"2167":{"name":"TzHaar-Xil","hitpoints":120,"combatLevel":133,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":100,"rangeLevel":120,"magicLevel":40},"2168":{"name":"TzHaar-Xil","hitpoints":120,"combatLevel":133,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":100,"rangeLevel":120,"magicLevel":40},"2169":{"name":"TzHaar-Xil","hitpoints":120,"combatLevel":133,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":100,"rangeLevel":120,"magicLevel":40},"2170":{"name":"TzHaar-Xil","hitpoints":120,"combatLevel":133,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":100,"rangeLevel":120,"magicLevel":40},"2171":{"name":"TzHaar-Xil","hitpoints":120,"combatLevel":133,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":100,"rangeLevel":120,"magicLevel":40},"2172":{"name":"TzHaar-Xil","hitpoints":120,"combatLevel":133,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":100,"rangeLevel":120,"magicLevel":40},"2173":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2174":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2175":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2176":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2177":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2178":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2179":{"name":"TzHaar-Ket","hitpoints":140,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"2189":{"name":"Tz-Kih","hitpoints":10,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":30,"defenceLevel":15,"rangeLevel":30,"magicLevel":15},"2190":{"name":"Tz-Kih","hitpoints":10,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":30,"defenceLevel":15,"rangeLevel":30,"magicLevel":15},"2191":{"name":"Tz-Kek","hitpoints":20,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":60,"defenceLevel":30,"rangeLevel":60,"magicLevel":30},"2192":{"name":"Tz-Kek","hitpoints":20,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":60,"defenceLevel":30,"rangeLevel":60,"magicLevel":30},"2193":{"name":"Tok-Xil","hitpoints":40,"combatLevel":90,"attackSpeed":4,"attackLevel":80,"strengthLevel":120,"defenceLevel":60,"rangeLevel":120,"magicLevel":60},"2194":{"name":"Tok-Xil","hitpoints":40,"combatLevel":90,"attackSpeed":4,"attackLevel":80,"strengthLevel":120,"defenceLevel":60,"rangeLevel":120,"magicLevel":60},"2205":{"name":"Commander Zilyana","hitpoints":255,"combatLevel":596,"attackSpeed":2,"attackLevel":280,"strengthLevel":196,"defenceLevel":300,"rangeLevel":250,"magicLevel":300,"magic":200,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":100,"magicDef":100,"bonusAttack":195,"bonusStrength":20,"poisonImmune":true,"venomImmune":true},"2206":{"name":"Starlight","hitpoints":160,"combatLevel":149,"attackSpeed":5,"attackLevel":120,"strengthLevel":125,"defenceLevel":120,"rangeLevel":1,"magicLevel":125,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":60,"bonusStrength":10},"2207":{"name":"Growler","hitpoints":146,"combatLevel":139,"attackSpeed":5,"attackLevel":100,"strengthLevel":101,"defenceLevel":120,"rangeLevel":1,"magicLevel":150,"stabDef":12,"slashDef":14,"crushDef":14,"rangeDef":5,"magicDef":18,"bonusAttack":10,"bonusStrength":7},"2208":{"name":"Bree","hitpoints":162,"combatLevel":146,"attackSpeed":5,"attackLevel":110,"strengthLevel":80,"defenceLevel":130,"rangeLevel":150,"magicLevel":80,"stabDef":12,"slashDef":14,"crushDef":14,"rangeDef":5,"magicDef":18,"bonusAttack":10,"bonusStrength":7},"2209":{"name":"Saradomin priest","hitpoints":89,"combatLevel":113,"attackSpeed":5,"attackLevel":120,"strengthLevel":46,"defenceLevel":120,"rangeLevel":1,"magicLevel":125,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":9,"bonusStrength":5},"2210":{"name":"Spiritual warrior","hitpoints":110,"combatLevel":125,"slayerLevel":68,"attackSpeed":5,"attackLevel":110,"strengthLevel":106,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":9,"bonusStrength":6},"2211":{"name":"Spiritual ranger","hitpoints":106,"combatLevel":122,"slayerLevel":63,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":146,"magicLevel":1,"stabDef":3,"slashDef":5,"crushDef":13,"rangeDef":23,"magicDef":16},"2212":{"name":"Spiritual mage","hitpoints":85,"combatLevel":120,"slayerLevel":83,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":86,"rangeLevel":1,"magicLevel":160,"stabDef":8,"slashDef":7,"crushDef":3,"rangeDef":2,"magicDef":16},"2213":{"name":"Knight of Saradomin","hitpoints":135,"combatLevel":103,"attackSpeed":6,"attackLevel":70,"strengthLevel":85,"defenceLevel":70,"rangeLevel":1,"magicLevel":60,"stabDef":10,"slashDef":10,"crushDef":7,"rangeDef":13,"bonusAttack":8,"bonusStrength":8},"2214":{"name":"Knight of Saradomin","hitpoints":108,"combatLevel":101,"attackSpeed":6,"attackLevel":75,"strengthLevel":90,"defenceLevel":82,"rangeLevel":1,"magicLevel":60,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"bonusAttack":13,"bonusStrength":11},"2215":{"name":"General Graardor","hitpoints":255,"combatLevel":624,"attackSpeed":6,"attackLevel":280,"strengthLevel":350,"defenceLevel":250,"rangeLevel":350,"magicLevel":80,"range":100,"stabDef":90,"slashDef":90,"crushDef":90,"rangeDef":90,"magicDef":298,"bonusAttack":120,"bonusStrength":43,"bonusRangeStrength":40,"poisonImmune":true,"venomImmune":true},"2216":{"name":"Sergeant Strongstack","hitpoints":128,"combatLevel":141,"attackSpeed":5,"attackLevel":124,"strengthLevel":118,"defenceLevel":125,"rangeLevel":50,"magicLevel":50,"bonusStrength":14},"2217":{"name":"Sergeant Steelwill","hitpoints":127,"combatLevel":142,"attackSpeed":5,"attackLevel":80,"strengthLevel":50,"defenceLevel":150,"rangeLevel":1,"magicLevel":150,"bonusStrength":6},"2218":{"name":"Sergeant Grimspike","hitpoints":146,"combatLevel":142,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":132,"rangeLevel":150,"magicLevel":50,"range":20,"bonusRangeStrength":20},"2232":{"name":"Black Guard Berserker","hitpoints":50,"combatLevel":66,"attackSpeed":3,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"bonusAttack":55,"bonusStrength":55},"2233":{"name":"Ogre","hitpoints":70,"combatLevel":58,"attackSpeed":6,"attackLevel":46,"strengthLevel":48,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":21},"2234":{"name":"Jogre","hitpoints":70,"combatLevel":58,"attackSpeed":6,"attackLevel":46,"strengthLevel":48,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusStrength":21},"2235":{"name":"Cyclops","hitpoints":110,"combatLevel":81,"attackSpeed":4,"attackLevel":60,"strengthLevel":70,"defenceLevel":48,"rangeLevel":1,"magicLevel":1,"bonusAttack":22},"2236":{"name":"Cyclops","hitpoints":110,"combatLevel":81,"attackSpeed":4,"attackLevel":60,"strengthLevel":70,"defenceLevel":48,"rangeLevel":1,"magicLevel":1,"bonusAttack":22},"2237":{"name":"Ork","hitpoints":110,"combatLevel":107,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":19,"bonusStrength":4},"2238":{"name":"Ork","hitpoints":110,"combatLevel":107,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":19,"bonusStrength":4},"2239":{"name":"Ork","hitpoints":110,"combatLevel":107,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":19,"bonusStrength":4},"2240":{"name":"Ork","hitpoints":110,"combatLevel":107,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":19,"bonusStrength":4},"2241":{"name":"Hobgoblin","hitpoints":52,"combatLevel":47,"attackSpeed":4,"attackLevel":39,"strengthLevel":39,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2242":{"name":"Spiritual ranger","hitpoints":131,"combatLevel":115,"slayerLevel":63,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":96,"rangeLevel":121,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5},"2243":{"name":"Spiritual warrior","hitpoints":131,"combatLevel":134,"slayerLevel":68,"attackSpeed":5,"attackLevel":116,"strengthLevel":120,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":29,"bonusStrength":13},"2244":{"name":"Spiritual mage","hitpoints":106,"combatLevel":121,"slayerLevel":83,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":103,"rangeLevel":1,"magicLevel":142,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":35},"2245":{"name":"Goblin","hitpoints":18,"combatLevel":17,"attackSpeed":4,"attackLevel":14,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"2246":{"name":"Goblin","hitpoints":3,"combatLevel":12,"attackSpeed":4,"attackLevel":15,"strengthLevel":6,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"2247":{"name":"Goblin","hitpoints":15,"combatLevel":12,"attackSpeed":4,"attackLevel":12,"strengthLevel":6,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"2248":{"name":"Goblin","hitpoints":16,"combatLevel":15,"attackSpeed":4,"attackLevel":16,"strengthLevel":6,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"2249":{"name":"Goblin","hitpoints":13,"combatLevel":13,"attackSpeed":4,"attackLevel":11,"strengthLevel":6,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"2259":{"name":"Dagannoth","hitpoints":85,"combatLevel":88,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":75,"rangeLevel":100,"magicLevel":1,"slashDef":10,"crushDef":90,"rangeDef":150,"magicDef":200},"2261":{"name":"Giant Rock Crab","hitpoints":180,"combatLevel":137,"attackSpeed":4,"attackLevel":50,"strengthLevel":80,"defenceLevel":200,"rangeLevel":1,"magicLevel":1,"stabDef":225,"slashDef":200,"crushDef":175,"rangeDef":250},"2264":{"name":"Dagannoth fledgeling","hitpoints":100,"combatLevel":70,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"2265":{"name":"Dagannoth Supreme","hitpoints":255,"combatLevel":303,"attackSpeed":4,"attackLevel":255,"strengthLevel":255,"defenceLevel":128,"rangeLevel":255,"magicLevel":255,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":550,"magicDef":255},"2266":{"name":"Dagannoth Prime","hitpoints":255,"combatLevel":303,"attackSpeed":4,"attackLevel":255,"strengthLevel":255,"defenceLevel":255,"magicLevel":255,"stabDef":255,"slashDef":255,"crushDef":255,"rangeDef":10,"magicDef":255},"2267":{"name":"Dagannoth Rex","hitpoints":255,"combatLevel":303,"attackSpeed":4,"attackLevel":255,"strengthLevel":255,"defenceLevel":255,"rangeLevel":255,"stabDef":255,"slashDef":255,"crushDef":255,"rangeDef":255,"magicDef":10},"2316":{"name":"Guard","hitpoints":26,"combatLevel":26,"attackSpeed":6,"attackLevel":25,"strengthLevel":18,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":16,"crushDef":19,"rangeDef":12},"2317":{"name":"Guard","hitpoints":26,"combatLevel":24,"attackSpeed":6,"attackLevel":22,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"slashDef":15,"crushDef":19,"rangeDef":12},"2423":{"name":"Chaos dwarf","hitpoints":61,"combatLevel":48,"attackSpeed":4,"attackLevel":38,"strengthLevel":42,"defenceLevel":28,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":34,"crushDef":25,"rangeDef":35,"magicDef":10,"bonusAttack":13,"bonusStrength":9},"2450":{"name":"Animated Bronze Armour","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2451":{"name":"Animated Iron Armour","hitpoints":20,"combatLevel":23,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2452":{"name":"Animated Steel Armour","hitpoints":40,"combatLevel":46,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2453":{"name":"Animated Black Armour","hitpoints":60,"combatLevel":69,"attackSpeed":4,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2454":{"name":"Animated Mithril Armour","hitpoints":80,"combatLevel":92,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2455":{"name":"Animated Adamant Armour","hitpoints":99,"combatLevel":113,"attackSpeed":4,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2456":{"name":"Animated Rune Armour","hitpoints":120,"combatLevel":138,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":25,"crushDef":19,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5},"2463":{"name":"Cyclops","hitpoints":75,"combatLevel":56,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2464":{"name":"Cyclops","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2465":{"name":"Cyclops","hitpoints":75,"combatLevel":56,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2466":{"name":"Cyclops","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2467":{"name":"Cyclops","hitpoints":75,"combatLevel":56,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2468":{"name":"Cyclops","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"2474":{"name":"Catablepon","hitpoints":40,"combatLevel":49,"attackSpeed":4,"attackLevel":45,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":60,"stabDef":40,"slashDef":30,"crushDef":20,"rangeDef":40,"magicDef":20},"2475":{"name":"Catablepon","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":45,"stabDef":50,"slashDef":40,"crushDef":30,"rangeDef":50,"magicDef":30},"2476":{"name":"Catablepon","hitpoints":50,"combatLevel":68,"attackSpeed":4,"attackLevel":65,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":60,"stabDef":50,"slashDef":40,"crushDef":30,"rangeDef":50,"magicDef":30},"2477":{"name":"Giant spider","hitpoints":50,"combatLevel":50,"attackSpeed":4,"attackLevel":41,"strengthLevel":51,"defenceLevel":31,"rangeLevel":1,"magicLevel":1},"2478":{"name":"Spider","hitpoints":22,"combatLevel":24,"attackSpeed":6,"attackLevel":21,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":53,"slashDef":53,"crushDef":53,"rangeDef":53,"magicDef":53,"bonusAttack":35,"bonusStrength":58},"2479":{"name":"Scorpion","hitpoints":55,"combatLevel":59,"attackSpeed":4,"attackLevel":50,"strengthLevel":52,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":55},"2480":{"name":"Scorpion","hitpoints":37,"combatLevel":37,"attackSpeed":4,"attackLevel":31,"strengthLevel":32,"defenceLevel":31,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":15,"crushDef":15,"rangeDef":35,"magicDef":30},"2481":{"name":"Minotaur","hitpoints":10,"combatLevel":12,"attackSpeed":4,"attackLevel":12,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2483":{"name":"Minotaur","hitpoints":22,"combatLevel":27,"attackSpeed":4,"attackLevel":23,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"2484":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"2485":{"name":"Goblin","hitpoints":16,"combatLevel":13,"attackSpeed":4,"attackLevel":12,"strengthLevel":13,"defenceLevel":7,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":8,"rangeDef":4,"magicDef":4},"2486":{"name":"Goblin","hitpoints":7,"combatLevel":11,"attackSpeed":6,"attackLevel":10,"strengthLevel":3,"defenceLevel":7,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"2487":{"name":"Goblin","hitpoints":22,"combatLevel":16,"attackSpeed":4,"attackLevel":13,"strengthLevel":11,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":22},"2488":{"name":"Goblin","hitpoints":26,"combatLevel":25,"attackSpeed":4,"attackLevel":22,"strengthLevel":23,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":8,"rangeDef":4,"magicDef":4},"2489":{"name":"Goblin","hitpoints":22,"combatLevel":16,"attackSpeed":4,"attackLevel":13,"strengthLevel":11,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":22},"2490":{"name":"Wolf","hitpoints":15,"combatLevel":14,"attackSpeed":4,"attackLevel":10,"strengthLevel":15,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2491":{"name":"Wolf","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2492":{"name":"Rat","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2498":{"name":"Flesh Crawler","hitpoints":25,"combatLevel":28,"attackSpeed":3,"attackLevel":60,"strengthLevel":2,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":15,"magicDef":15},"2499":{"name":"Flesh Crawler","hitpoints":25,"combatLevel":35,"attackSpeed":3,"attackLevel":80,"strengthLevel":2,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":15,"magicDef":15},"2500":{"name":"Flesh Crawler","hitpoints":25,"combatLevel":41,"attackSpeed":3,"attackLevel":100,"strengthLevel":2,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":15,"magicDef":15},"2501":{"name":"Zombie","hitpoints":30,"combatLevel":30,"attackSpeed":4,"attackLevel":25,"strengthLevel":29,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"2502":{"name":"Zombie","hitpoints":30,"combatLevel":30,"attackSpeed":4,"attackLevel":25,"strengthLevel":29,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"2503":{"name":"Zombie","hitpoints":30,"combatLevel":30,"attackSpeed":4,"attackLevel":25,"strengthLevel":29,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"2504":{"name":"Zombie","hitpoints":40,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"2505":{"name":"Zombie","hitpoints":40,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"2506":{"name":"Zombie","hitpoints":40,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"2507":{"name":"Zombie","hitpoints":50,"combatLevel":53,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":48,"rangeLevel":1,"magicLevel":1},"2508":{"name":"Zombie","hitpoints":50,"combatLevel":53,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":48,"rangeLevel":1,"magicLevel":1},"2509":{"name":"Zombie","hitpoints":50,"combatLevel":53,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":48,"rangeLevel":1,"magicLevel":1},"2510":{"name":"Giant rat","hitpoints":25,"combatLevel":26,"attackSpeed":4,"attackLevel":22,"strengthLevel":23,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"2511":{"name":"Giant rat","hitpoints":25,"combatLevel":26,"attackSpeed":4,"attackLevel":22,"strengthLevel":23,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"2512":{"name":"Giant rat","hitpoints":25,"combatLevel":26,"attackSpeed":4,"attackLevel":22,"strengthLevel":23,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"2513":{"name":"Rat","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2514":{"name":"Ankou","hitpoints":60,"combatLevel":75,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":60,"rangeLevel":1,"magicLevel":1},"2515":{"name":"Ankou","hitpoints":65,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"2516":{"name":"Ankou","hitpoints":70,"combatLevel":86,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"2517":{"name":"Ankou","hitpoints":60,"combatLevel":75,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":60,"rangeLevel":1,"magicLevel":1},"2518":{"name":"Ankou","hitpoints":65,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"2519":{"name":"Ankou","hitpoints":70,"combatLevel":86,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"2520":{"name":"Skeleton","hitpoints":70,"combatLevel":68,"attackSpeed":4,"attackLevel":60,"strengthLevel":50,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"rangeDef":35},"2521":{"name":"Skeleton","hitpoints":70,"combatLevel":60,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"rangeDef":35},"2522":{"name":"Skeleton","hitpoints":70,"combatLevel":60,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"rangeDef":35},"2523":{"name":"Skeleton","hitpoints":70,"combatLevel":60,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"rangeDef":35},"2524":{"name":"Skeleton","hitpoints":77,"combatLevel":85,"attackSpeed":4,"attackLevel":74,"strengthLevel":74,"defenceLevel":74,"rangeLevel":1,"magicLevel":1,"stabDef":79,"slashDef":31,"crushDef":20,"rangeDef":70,"magicDef":5,"bonusAttack":15,"bonusStrength":14},"2525":{"name":"Skeleton","hitpoints":77,"combatLevel":85,"attackSpeed":4,"attackLevel":74,"strengthLevel":74,"defenceLevel":74,"rangeLevel":1,"magicLevel":1,"stabDef":79,"slashDef":31,"crushDef":20,"rangeDef":70,"magicDef":5,"bonusAttack":15,"bonusStrength":14},"2526":{"name":"Skeleton","hitpoints":77,"combatLevel":85,"attackSpeed":4,"attackLevel":74,"strengthLevel":74,"defenceLevel":74,"rangeLevel":1,"magicLevel":1,"stabDef":79,"slashDef":31,"crushDef":20,"rangeDef":70,"magicDef":5,"bonusAttack":15,"bonusStrength":14},"2527":{"name":"Ghost","hitpoints":80,"combatLevel":77,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":55,"slashDef":55,"crushDef":5,"rangeDef":55,"magicDef":55},"2528":{"name":"Ghost","hitpoints":80,"combatLevel":77,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":55,"slashDef":55,"crushDef":5,"rangeDef":55,"magicDef":55},"2529":{"name":"Ghost","hitpoints":80,"combatLevel":77,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":55,"slashDef":55,"crushDef":5,"rangeDef":55,"magicDef":55},"2530":{"name":"Ghost","hitpoints":80,"combatLevel":77,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":55,"slashDef":55,"crushDef":5,"rangeDef":55,"magicDef":55},"2531":{"name":"Ghost","hitpoints":75,"combatLevel":76,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":5,"rangeDef":45},"2532":{"name":"Ghost","hitpoints":75,"combatLevel":76,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":5,"rangeDef":45},"2533":{"name":"Ghost","hitpoints":75,"combatLevel":76,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":5,"rangeDef":45},"2534":{"name":"Ghost","hitpoints":75,"combatLevel":76,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":5,"rangeDef":45},"2536":{"name":"H.A.M. Guard","hitpoints":15,"combatLevel":12,"attackSpeed":7,"attackLevel":8,"strengthLevel":12,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"2537":{"name":"H.A.M. Guard","hitpoints":20,"combatLevel":18,"attackSpeed":6,"attackLevel":14,"strengthLevel":18,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":6,"slashDef":6,"rangeDef":6},"2538":{"name":"H.A.M. Guard","hitpoints":30,"combatLevel":22,"attackSpeed":5,"attackLevel":16,"strengthLevel":20,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"2579":{"name":"Monk","hitpoints":15,"combatLevel":5,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"2584":{"name":"Abyssal leech","hitpoints":10,"combatLevel":41,"attackSpeed":2,"attackLevel":95,"strengthLevel":5,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":100,"rangeDef":10,"magicDef":50,"bonusAttack":100,"bonusStrength":10},"2585":{"name":"Abyssal guardian","hitpoints":55,"combatLevel":59,"attackSpeed":4,"attackLevel":30,"strengthLevel":90,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":70,"magicDef":150,"bonusAttack":15,"bonusStrength":5},"2586":{"name":"Abyssal walker","hitpoints":95,"combatLevel":81,"attackSpeed":5,"attackLevel":5,"strengthLevel":100,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":75,"slashDef":75,"crushDef":10,"rangeDef":75,"magicDef":75,"bonusAttack":5,"bonusStrength":10},"2592":{"name":"Mogre","hitpoints":48,"combatLevel":60,"slayerLevel":32,"attackSpeed":6,"attackLevel":58,"strengthLevel":55,"defenceLevel":48,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20,"poisonImmune":true,"venomImmune":true},"2593":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2594":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2595":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2596":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2597":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2598":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2599":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2600":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2601":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2602":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2603":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2604":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2605":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2606":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2607":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2608":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2609":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2610":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2611":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2612":{"name":"Werewolf","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"magicDef":60},"2613":{"name":"Boris","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2614":{"name":"Imre","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2615":{"name":"Yuri","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2616":{"name":"Joseph","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2617":{"name":"Nikolai","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2618":{"name":"Eduard","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2619":{"name":"Lev","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2620":{"name":"Georgy","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2621":{"name":"Svetlana","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2622":{"name":"Irina","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2623":{"name":"Alexis","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2624":{"name":"Milla","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2625":{"name":"Galina","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2626":{"name":"Sofiya","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2627":{"name":"Ksenia","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2628":{"name":"Yadviga","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2629":{"name":"Nikita","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2630":{"name":"Vera","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2631":{"name":"Zoja","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2632":{"name":"Liliya","hitpoints":60,"combatLevel":24,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2634":{"name":"Myre Blamish Snail","hitpoints":8,"combatLevel":9,"attackSpeed":4,"defenceLevel":22,"rangeLevel":5,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2645":{"name":"Blood Blamish Snail","hitpoints":13,"combatLevel":20,"attackSpeed":6,"defenceLevel":45,"rangeLevel":12,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"bonusAttack":10,"bonusStrength":10},"2646":{"name":"Ochre Blamish Snail","hitpoints":10,"combatLevel":10,"attackSpeed":6,"defenceLevel":18,"rangeLevel":7,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2647":{"name":"Bruise Blamish Snail","hitpoints":12,"combatLevel":20,"attackSpeed":6,"defenceLevel":40,"rangeLevel":15,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2648":{"name":"Bark Blamish Snail","hitpoints":22,"combatLevel":15,"attackSpeed":6,"defenceLevel":20,"rangeLevel":10,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":30,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2649":{"name":"Myre Blamish Snail","hitpoints":13,"combatLevel":10,"attackSpeed":4,"defenceLevel":12,"rangeLevel":8,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2650":{"name":"Blood Blamish Snail","hitpoints":10,"combatLevel":20,"attackSpeed":6,"defenceLevel":30,"rangeLevel":21,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"bonusAttack":10,"bonusStrength":10},"2651":{"name":"Ochre Blamish Snail","hitpoints":20,"combatLevel":15,"attackSpeed":6,"defenceLevel":25,"rangeLevel":8,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2652":{"name":"Bruise Blamish Snail","hitpoints":15,"combatLevel":20,"attackSpeed":6,"defenceLevel":27,"rangeLevel":20,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":50,"magicDef":5,"bonusAttack":10,"bonusStrength":10},"2790":{"name":"Cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"2791":{"name":"Cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"2792":{"name":"Cow calf","hitpoints":6,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2793":{"name":"Cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"2794":{"name":"Cow calf","hitpoints":6,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2795":{"name":"Cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"2801":{"name":"Cow calf","hitpoints":6,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2804":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2805":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2806":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2827":{"name":"Bat","hitpoints":8,"combatLevel":6,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":2,"crushDef":5,"magicDef":2},"2834":{"name":"Giant bat","hitpoints":32,"combatLevel":27,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":12,"rangeDef":8,"magicDef":10},"2837":{"name":"Unicorn","hitpoints":19,"combatLevel":15,"attackSpeed":4,"attackLevel":11,"strengthLevel":13,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"2838":{"name":"Grizzly bear","hitpoints":27,"combatLevel":21,"attackSpeed":4,"attackLevel":17,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1},"2839":{"name":"Black bear","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":15,"strengthLevel":16,"defenceLevel":13},"2840":{"name":"Earth warrior","hitpoints":54,"combatLevel":51,"attackSpeed":4,"attackLevel":42,"strengthLevel":42,"defenceLevel":42,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":40,"crushDef":20,"rangeDef":30,"magicDef":10},"2841":{"name":"Ice warrior","hitpoints":59,"combatLevel":57,"attackSpeed":4,"attackLevel":47,"strengthLevel":47,"defenceLevel":47,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":40,"crushDef":20,"rangeDef":30,"magicDef":10},"2842":{"name":"Ice warrior","hitpoints":59,"combatLevel":57,"attackSpeed":4,"attackLevel":47,"strengthLevel":47,"defenceLevel":47,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":40,"crushDef":20,"rangeDef":30,"magicDef":10},"2843":{"name":"Otherworldly being","hitpoints":66,"combatLevel":64,"attackSpeed":4,"attackLevel":56,"strengthLevel":56,"defenceLevel":46,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":10,"crushDef":20,"rangeDef":15},"2844":{"name":"Magic axe","hitpoints":44,"combatLevel":42,"attackSpeed":4,"attackLevel":38,"strengthLevel":38,"defenceLevel":29,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":5,"crushDef":15,"rangeDef":10,"magicDef":5},"2845":{"name":"Snake","hitpoints":6,"combatLevel":5,"attackSpeed":4,"attackLevel":4,"strengthLevel":5,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"2848":{"name":"Monkey","hitpoints":6,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2849":{"name":"Black unicorn","hitpoints":29,"combatLevel":27,"attackSpeed":4,"attackLevel":21,"strengthLevel":23,"defenceLevel":23,"rangeLevel":1,"magicLevel":1},"2851":{"name":"Ice warrior","hitpoints":59,"combatLevel":57,"attackSpeed":4,"attackLevel":47,"strengthLevel":47,"defenceLevel":47,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":40,"crushDef":20,"rangeDef":30,"magicDef":10},"2853":{"name":"Shadow warrior","hitpoints":67,"combatLevel":48,"attackSpeed":4,"attackLevel":36,"strengthLevel":36,"defenceLevel":33,"stabDef":43,"slashDef":31,"crushDef":19,"rangeDef":38,"magicDef":15,"bonusAttack":20,"bonusStrength":26},"2854":{"name":"Rat","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2855":{"name":"Rat","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2856":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2857":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2858":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2859":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2860":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2861":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2862":{"name":"Giant rat","hitpoints":10,"combatLevel":6,"attackSpeed":4,"attackLevel":6,"strengthLevel":5,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2863":{"name":"Giant rat","hitpoints":10,"combatLevel":6,"attackSpeed":4,"attackLevel":6,"strengthLevel":5,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2864":{"name":"Giant rat","hitpoints":10,"combatLevel":6,"attackSpeed":4,"attackLevel":6,"strengthLevel":5,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2865":{"name":"Dungeon rat","hitpoints":12,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2866":{"name":"Dungeon rat","hitpoints":12,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2867":{"name":"Dungeon rat","hitpoints":12,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"2916":{"name":"Waterfiend","hitpoints":128,"combatLevel":115,"attackSpeed":4,"defenceLevel":128,"rangeLevel":105,"magicLevel":105,"stabDef":100,"slashDef":100,"crushDef":10,"rangeDef":100,"magicDef":100,"poisonImmune":true,"venomImmune":true},"2917":{"name":"Waterfiend","hitpoints":128,"combatLevel":115,"attackSpeed":4,"defenceLevel":128,"rangeLevel":105,"magicLevel":105,"stabDef":100,"slashDef":100,"crushDef":10,"rangeDef":100,"magicDef":100,"poisonImmune":true,"venomImmune":true},"2918":{"name":"Brutal green dragon","hitpoints":175,"combatLevel":227,"attackSpeed":4,"attackLevel":268,"strengthLevel":168,"defenceLevel":168,"magicLevel":168,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"poisonImmune":true,"venomImmune":true},"2919":{"name":"Mithril dragon","hitpoints":254,"combatLevel":304,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":268,"rangeLevel":168,"magicLevel":168,"stabDef":50,"slashDef":100,"crushDef":70,"rangeDef":90,"magicDef":30,"poisonImmune":true,"venomImmune":true},"2920":{"name":"Confused barbarian","hitpoints":124,"combatLevel":132,"attackSpeed":4,"attackLevel":115,"strengthLevel":114,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"2921":{"name":"Lost barbarian","hitpoints":124,"combatLevel":132,"attackSpeed":4,"attackLevel":115,"strengthLevel":114,"defenceLevel":110,"rangeLevel":1,"magicLevel":1},"2946":{"name":"Nail beast","hitpoints":55,"combatLevel":69,"attackSpeed":8,"attackLevel":150,"strengthLevel":6,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"2947":{"name":"Nail beast","hitpoints":65,"combatLevel":98,"attackSpeed":8,"attackLevel":215,"strengthLevel":16,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"2948":{"name":"Nail beast","hitpoints":75,"combatLevel":141,"attackSpeed":8,"attackLevel":320,"strengthLevel":26,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"2954":{"name":"Zamorak wizard","hitpoints":73,"combatLevel":65,"attackSpeed":4,"attackLevel":24,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":80,"magicDef":3},"2955":{"name":"Saradomin wizard","hitpoints":120,"combatLevel":108,"attackSpeed":4,"attackLevel":100,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":80,"magicDef":30,"bonusAttack":40,"bonusStrength":40},"2978":{"name":"Big Snake","hitpoints":120,"combatLevel":84,"attackSpeed":6,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"bonusAttack":66,"bonusStrength":62},"2992":{"name":"Undead cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2993":{"name":"Undead chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"2994":{"name":"Giant lobster","hitpoints":32,"combatLevel":32,"attackSpeed":4,"attackLevel":18,"strengthLevel":26,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":5,"rangeDef":5},"2999":{"name":"Tortured soul","hitpoints":51,"combatLevel":59,"attackSpeed":4,"attackLevel":52,"strengthLevel":62,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"3010":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"3011":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"3014":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3015":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3016":{"name":"Shadow spider","hitpoints":55,"combatLevel":52,"attackSpeed":4,"attackLevel":44,"strengthLevel":42,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":15,"crushDef":10,"rangeDef":15,"magicDef":15},"3017":{"name":"Giant spider","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3018":{"name":"Giant spider","hitpoints":32,"combatLevel":27,"attackSpeed":4,"attackLevel":20,"strengthLevel":24,"defenceLevel":21,"rangeLevel":1,"magicLevel":1},"3019":{"name":"Spider","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3020":{"name":"Jungle spider","hitpoints":50,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":37,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":10,"rangeDef":20,"magicDef":17},"3021":{"name":"Deadly red spider","hitpoints":35,"combatLevel":34,"attackSpeed":4,"attackLevel":30,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":16,"crushDef":7,"rangeDef":16,"magicDef":12},"3022":{"name":"Ice spider","hitpoints":65,"combatLevel":61,"attackSpeed":4,"attackLevel":50,"strengthLevel":55,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":17,"crushDef":12,"rangeDef":13,"magicDef":13},"3023":{"name":"Poison spider","hitpoints":64,"combatLevel":64,"attackSpeed":6,"attackLevel":50,"strengthLevel":52,"defenceLevel":58,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":17,"crushDef":10,"rangeDef":14,"magicDef":14},"3024":{"name":"Scorpion","hitpoints":17,"combatLevel":14,"attackSpeed":4,"attackLevel":11,"strengthLevel":12,"defenceLevel":11,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"3025":{"name":"Poison Scorpion","hitpoints":23,"combatLevel":20,"attackSpeed":4,"attackLevel":16,"strengthLevel":17,"defenceLevel":15,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"3026":{"name":"Pit Scorpion","hitpoints":32,"combatLevel":28,"attackSpeed":4,"attackLevel":23,"strengthLevel":23,"defenceLevel":23,"rangeLevel":1,"magicLevel":1,"slashDef":5,"crushDef":5},"3027":{"name":"King Scorpion","hitpoints":30,"combatLevel":32,"attackSpeed":4,"attackLevel":30,"strengthLevel":29,"defenceLevel":23,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"3028":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3029":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3030":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3031":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3032":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3033":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3034":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3035":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3036":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3037":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3038":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3039":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3040":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3041":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3042":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3043":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3044":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3045":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3046":{"name":"Goblin","hitpoints":16,"combatLevel":13,"attackSpeed":4,"attackLevel":12,"strengthLevel":13,"defenceLevel":7,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":8,"rangeDef":4,"magicDef":4},"3047":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3048":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3049":{"name":"Hobgoblin","hitpoints":29,"combatLevel":28,"attackSpeed":4,"attackLevel":22,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1},"3050":{"name":"Hobgoblin","hitpoints":49,"combatLevel":42,"attackSpeed":4,"attackLevel":33,"strengthLevel":31,"defenceLevel":36,"rangeLevel":1,"magicLevel":1,"bonusAttack":8,"bonusStrength":10},"3051":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3052":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3053":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3054":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3055":{"name":"Barbarian","hitpoints":24,"combatLevel":17,"attackSpeed":4,"attackLevel":15,"strengthLevel":14,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3056":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3057":{"name":"Barbarian","hitpoints":24,"combatLevel":17,"attackSpeed":4,"attackLevel":15,"strengthLevel":14,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3058":{"name":"Barbarian","hitpoints":24,"combatLevel":17,"attackSpeed":4,"attackLevel":15,"strengthLevel":14,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3059":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3060":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3061":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3062":{"name":"Barbarian","hitpoints":24,"combatLevel":17,"attackSpeed":4,"attackLevel":15,"strengthLevel":14,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3064":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3065":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3066":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3067":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3068":{"name":"Barbarian","hitpoints":24,"combatLevel":15,"attackSpeed":4,"attackLevel":15,"strengthLevel":3,"defenceLevel":10,"rangeLevel":15,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3069":{"name":"Barbarian","hitpoints":24,"combatLevel":17,"attackSpeed":4,"attackLevel":15,"strengthLevel":14,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3070":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3071":{"name":"Barbarian","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"3072":{"name":"Barbarian","hitpoints":20,"combatLevel":9,"attackSpeed":4,"attackLevel":6,"strengthLevel":7,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":10,"crushDef":10,"rangeDef":5,"bonusAttack":9,"bonusStrength":16},"3073":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3074":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3075":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3076":{"name":"Goblin","hitpoints":12,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":12},"3106":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3107":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3108":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3109":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3110":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3111":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3112":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3113":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3114":{"name":"Farmer","hitpoints":12,"combatLevel":7,"attackSpeed":6,"attackLevel":3,"strengthLevel":4,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":6},"3116":{"name":"Tz-Kih","hitpoints":10,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":30,"defenceLevel":15,"rangeLevel":30,"magicLevel":15},"3117":{"name":"Tz-Kih","hitpoints":10,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":30,"defenceLevel":15,"rangeLevel":30,"magicLevel":15},"3118":{"name":"Tz-Kek","hitpoints":20,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":60,"defenceLevel":30,"rangeLevel":60,"magicLevel":30},"3119":{"name":"Tz-Kek","hitpoints":20,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":60,"defenceLevel":30,"rangeLevel":60,"magicLevel":30},"3120":{"name":"Tz-Kek","hitpoints":10,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":30,"defenceLevel":15,"rangeLevel":30,"magicLevel":15},"3121":{"name":"Tok-Xil","hitpoints":40,"combatLevel":90,"attackSpeed":4,"attackLevel":80,"strengthLevel":120,"defenceLevel":60,"rangeLevel":120,"magicLevel":60},"3122":{"name":"Tok-Xil","hitpoints":40,"combatLevel":90,"attackSpeed":4,"attackLevel":80,"strengthLevel":120,"defenceLevel":60,"rangeLevel":120,"magicLevel":60},"3123":{"name":"Yt-MejKot","hitpoints":80,"combatLevel":180,"attackSpeed":4,"attackLevel":160,"strengthLevel":240,"defenceLevel":120,"rangeLevel":240,"magicLevel":120},"3124":{"name":"Yt-MejKot","hitpoints":80,"combatLevel":180,"attackSpeed":4,"attackLevel":160,"strengthLevel":240,"defenceLevel":120,"rangeLevel":240,"magicLevel":120},"3125":{"name":"Ket-Zek","hitpoints":160,"combatLevel":360,"attackSpeed":4,"attackLevel":320,"strengthLevel":480,"defenceLevel":240,"rangeLevel":480,"magicLevel":240,"magic":60},"3126":{"name":"Ket-Zek","hitpoints":160,"combatLevel":360,"attackSpeed":4,"attackLevel":320,"strengthLevel":480,"defenceLevel":240,"rangeLevel":480,"magicLevel":240,"magic":60},"3127":{"name":"TzTok-Jad","hitpoints":250,"combatLevel":702,"attackSpeed":8,"attackLevel":640,"strengthLevel":960,"defenceLevel":480,"rangeLevel":960,"magicLevel":480,"magic":60},"3128":{"name":"Yt-HurKot","hitpoints":60,"combatLevel":108,"attackSpeed":4,"attackLevel":140,"strengthLevel":100,"defenceLevel":60,"rangeLevel":120,"magicLevel":120,"rangeDef":100,"magicDef":100},"3129":{"name":"K'ril Tsutsaroth","hitpoints":255,"combatLevel":650,"attackSpeed":6,"attackLevel":340,"strengthLevel":300,"defenceLevel":270,"rangeLevel":1,"magicLevel":200,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":80,"magicDef":130,"bonusAttack":160,"bonusStrength":31},"3130":{"name":"Tstanon Karlak","hitpoints":142,"combatLevel":145,"attackSpeed":5,"attackLevel":124,"strengthLevel":118,"defenceLevel":125,"rangeLevel":50,"magicLevel":50,"bonusStrength":14},"3131":{"name":"Zakl'n Gritch","hitpoints":150,"combatLevel":142,"attackSpeed":5,"attackLevel":83,"strengthLevel":76,"defenceLevel":127,"rangeLevel":150,"magicLevel":50,"range":20,"bonusRangeStrength":20},"3132":{"name":"Balfrug Kreeyath","hitpoints":161,"combatLevel":151,"attackSpeed":5,"attackLevel":115,"strengthLevel":60,"defenceLevel":153,"rangeLevel":1,"magicLevel":150,"magicDef":10},"3133":{"name":"Hellhound","hitpoints":116,"combatLevel":127,"attackSpeed":4,"attackLevel":107,"strengthLevel":116,"defenceLevel":106,"rangeLevel":1,"magicLevel":1},"3134":{"name":"Imp","hitpoints":10,"combatLevel":7,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":6,"rangeLevel":1,"magicLevel":1},"3135":{"name":"Werewolf","hitpoints":92,"combatLevel":93,"attackSpeed":4,"attackLevel":77,"strengthLevel":76,"defenceLevel":85,"rangeLevel":1,"magicLevel":1},"3136":{"name":"Werewolf","hitpoints":92,"combatLevel":93,"attackSpeed":4,"attackLevel":77,"strengthLevel":76,"defenceLevel":85,"rangeLevel":1,"magicLevel":1},"3137":{"name":"Feral Vampyre","hitpoints":60,"combatLevel":77,"attackSpeed":4,"attackLevel":65,"strengthLevel":66,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"3138":{"name":"Bloodveld","hitpoints":134,"combatLevel":81,"slayerLevel":50,"attackSpeed":4,"attackLevel":80,"strengthLevel":46,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"3139":{"name":"Pyrefiend","hitpoints":48,"combatLevel":48,"slayerLevel":30,"attackSpeed":4,"attackLevel":60,"strengthLevel":36,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"3140":{"name":"Icefiend","hitpoints":20,"combatLevel":18,"attackSpeed":4,"attackLevel":12,"strengthLevel":16,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"3141":{"name":"Gorak","hitpoints":128,"combatLevel":149,"attackSpeed":4,"attackLevel":133,"strengthLevel":126,"defenceLevel":135,"rangeLevel":1,"magicLevel":1},"3159":{"name":"Spiritual warrior","hitpoints":100,"combatLevel":115,"slayerLevel":68,"attackSpeed":5,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"3160":{"name":"Spiritual ranger","hitpoints":120,"combatLevel":118,"slayerLevel":63,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":80,"rangeLevel":140,"magicLevel":1},"3161":{"name":"Spiritual mage","hitpoints":75,"combatLevel":121,"slayerLevel":83,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":61,"rangeLevel":1,"magicLevel":180},"3162":{"name":"Kree'arra","hitpoints":255,"combatLevel":580,"attackSpeed":3,"attackLevel":300,"strengthLevel":200,"defenceLevel":260,"rangeLevel":380,"magicLevel":200,"range":120,"stabDef":180,"slashDef":180,"crushDef":180,"rangeDef":200,"magicDef":200,"bonusAttack":136,"bonusStrength":12,"bonusRangeStrength":50},"3163":{"name":"Wingman Skree","hitpoints":121,"combatLevel":143,"attackSpeed":5,"attackLevel":80,"strengthLevel":50,"defenceLevel":160,"rangeLevel":100,"magicLevel":150,"bonusAttack":45,"bonusStrength":25},"3164":{"name":"Flockleader Geerin","hitpoints":132,"combatLevel":149,"attackSpeed":5,"attackLevel":80,"strengthLevel":80,"defenceLevel":175,"rangeLevel":150,"magicLevel":50,"range":60,"bonusRangeStrength":35},"3165":{"name":"Flight Kilisa","hitpoints":133,"combatLevel":159,"attackSpeed":5,"attackLevel":124,"strengthLevel":118,"defenceLevel":175,"rangeLevel":169,"magicLevel":50,"bonusStrength":14},"3166":{"name":"Spiritual warrior","hitpoints":98,"combatLevel":123,"slayerLevel":68,"attackSpeed":5,"attackLevel":1,"strengthLevel":1,"defenceLevel":120,"rangeLevel":141,"magicLevel":1,"stabDef":23,"slashDef":25,"crushDef":13,"rangeDef":35,"magicDef":35},"3167":{"name":"Spiritual ranger","hitpoints":89,"combatLevel":127,"slayerLevel":63,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":130,"rangeLevel":150,"magicLevel":1,"stabDef":17,"slashDef":16,"crushDef":8,"rangeDef":41,"magicDef":35},"3168":{"name":"Spiritual mage","hitpoints":75,"combatLevel":123,"slayerLevel":83,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":111,"rangeLevel":1,"magicLevel":150,"stabDef":9,"slashDef":12,"crushDef":5,"rangeDef":28,"magicDef":45},"3169":{"name":"Aviansie","hitpoints":70,"combatLevel":69,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":71,"magicLevel":1},"3170":{"name":"Aviansie","hitpoints":83,"combatLevel":79,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":85,"magicLevel":1},"3171":{"name":"Aviansie","hitpoints":86,"combatLevel":84,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":93,"magicLevel":1},"3172":{"name":"Aviansie","hitpoints":86,"combatLevel":83,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":76,"magicLevel":1},"3173":{"name":"Aviansie","hitpoints":95,"combatLevel":92,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":90,"magicLevel":1},"3174":{"name":"Aviansie","hitpoints":98,"combatLevel":97,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":99,"magicLevel":1},"3175":{"name":"Aviansie","hitpoints":124,"combatLevel":137,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":160,"rangeLevel":136,"magicLevel":1},"3176":{"name":"Aviansie","hitpoints":139,"combatLevel":148,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":160,"rangeLevel":151,"magicLevel":1},"3177":{"name":"Aviansie","hitpoints":63,"combatLevel":71,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":55,"rangeLevel":86,"magicLevel":1},"3178":{"name":"Aviansie","hitpoints":67,"combatLevel":73,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":55,"rangeLevel":89,"magicLevel":1},"3179":{"name":"Aviansie","hitpoints":77,"combatLevel":79,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":55,"rangeLevel":96,"magicLevel":1},"3180":{"name":"Aviansie","hitpoints":69,"combatLevel":89,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":115,"rangeLevel":89,"magicLevel":1},"3181":{"name":"Aviansie","hitpoints":75,"combatLevel":94,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":115,"rangeLevel":96,"magicLevel":1},"3182":{"name":"Aviansie","hitpoints":79,"combatLevel":97,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":115,"rangeLevel":101,"magicLevel":1},"3183":{"name":"Aviansie","hitpoints":115,"combatLevel":131,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":175,"rangeLevel":143,"magicLevel":1},"3184":{"name":"Dagannoth spawn","hitpoints":35,"combatLevel":42,"attackSpeed":4,"attackLevel":50,"strengthLevel":35,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"3185":{"name":"Dagannoth","hitpoints":95,"combatLevel":90,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50,"bonusAttack":50,"bonusStrength":50},"3233":{"name":"Leech","hitpoints":45,"combatLevel":52,"attackSpeed":4,"attackLevel":80,"strengthLevel":20,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"3234":{"name":"Feral Vampyre","hitpoints":50,"combatLevel":72,"attackSpeed":4,"attackLevel":65,"strengthLevel":70,"defenceLevel":65,"rangeLevel":1,"magicLevel":50},"3237":{"name":"Feral Vampyre","hitpoints":40,"combatLevel":61,"attackSpeed":4,"attackLevel":55,"strengthLevel":60,"defenceLevel":55,"rangeLevel":1,"magicLevel":40},"3243":{"name":"Farmer","hitpoints":12,"combatLevel":7,"attackSpeed":6,"attackLevel":3,"strengthLevel":4,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":6},"3244":{"name":"Farmer","hitpoints":12,"combatLevel":7,"attackSpeed":6,"attackLevel":3,"strengthLevel":4,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":6},"3245":{"name":"Farmer","hitpoints":12,"combatLevel":7,"attackSpeed":6,"attackLevel":3,"strengthLevel":4,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":6},"3250":{"name":"Farmer","hitpoints":12,"combatLevel":7,"attackSpeed":6,"attackLevel":3,"strengthLevel":4,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":6},"3251":{"name":"Farmer","hitpoints":12,"combatLevel":7,"attackSpeed":6,"attackLevel":3,"strengthLevel":4,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":6},"3252":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"3253":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"3254":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"3257":{"name":"Wizard","hitpoints":14,"combatLevel":9,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":5,"rangeLevel":1,"magicLevel":10,"magicDef":3},"3258":{"name":"Druid","hitpoints":30,"combatLevel":33,"attackSpeed":4,"attackLevel":28,"strengthLevel":28,"defenceLevel":32,"rangeLevel":1,"magicLevel":25},"3260":{"name":"Warrior woman","hitpoints":20,"combatLevel":24,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"range":6,"magic":6,"stabDef":40,"slashDef":41,"crushDef":37,"rangeDef":38,"bonusAttack":6,"bonusStrength":7},"3261":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3262":{"name":"Barbarian","hitpoints":14,"combatLevel":8,"attackSpeed":6,"attackLevel":6,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"bonusAttack":8,"bonusStrength":10},"3263":{"name":"Drunken man","hitpoints":7,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":3,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3264":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3265":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3268":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3269":{"name":"Guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"3270":{"name":"Guard","hitpoints":22,"combatLevel":22,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":26,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":6,"bonusStrength":10,"bonusRangeStrength":10},"3271":{"name":"Guard","hitpoints":22,"combatLevel":19,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":6,"bonusStrength":10},"3272":{"name":"Guard","hitpoints":22,"combatLevel":22,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":26,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":6,"bonusStrength":10},"3273":{"name":"Guard","hitpoints":22,"combatLevel":22,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":26,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":6,"bonusStrength":10},"3274":{"name":"Guard","hitpoints":22,"combatLevel":22,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":26,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":6,"bonusStrength":10},"3275":{"name":"Gardener","hitpoints":7,"combatLevel":4,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"3276":{"name":"Gardener","hitpoints":7,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":1,"defenceLevel":5,"rangeLevel":1,"magicLevel":1},"3279":{"name":"Cuffs","hitpoints":7,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":3,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"magicDef":5},"3280":{"name":"Narf","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3281":{"name":"Rusty","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3282":{"name":"Jeff","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3283":{"name":"Guard","hitpoints":22,"combatLevel":19,"attackSpeed":6,"attackLevel":15,"strengthLevel":15,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":6,"bonusStrength":10},"3284":{"name":"Hengel","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3285":{"name":"Anja","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3286":{"name":"Hobgoblin","hitpoints":29,"combatLevel":28,"attackSpeed":4,"attackLevel":22,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1},"3287":{"name":"Hobgoblin","hitpoints":29,"combatLevel":28,"attackSpeed":4,"attackLevel":22,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1},"3288":{"name":"Hobgoblin","hitpoints":29,"combatLevel":28,"attackSpeed":4,"attackLevel":22,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1},"3289":{"name":"Hobgoblin","hitpoints":29,"combatLevel":28,"attackSpeed":4,"attackLevel":22,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1},"3292":{"name":"Al-Kharid warrior","hitpoints":19,"combatLevel":9,"attackSpeed":4,"attackLevel":7,"strengthLevel":5,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":15,"crushDef":10,"rangeDef":12,"bonusAttack":10,"bonusStrength":9},"3293":{"name":"Paladin","hitpoints":66,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"3294":{"name":"Paladin","hitpoints":66,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"3295":{"name":"Hero","hitpoints":82,"combatLevel":69,"attackSpeed":5,"attackLevel":54,"strengthLevel":55,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"3297":{"name":"Knight of Ardougne","hitpoints":52,"combatLevel":46,"attackSpeed":5,"attackLevel":38,"strengthLevel":40,"defenceLevel":31,"rangeLevel":1,"magicLevel":1,"stabDef":39,"slashDef":40,"crushDef":36,"rangeDef":36,"bonusAttack":8,"bonusStrength":10},"3298":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3299":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3300":{"name":"Knight of Ardougne","hitpoints":52,"combatLevel":46,"attackSpeed":5,"attackLevel":38,"strengthLevel":40,"defenceLevel":31,"rangeLevel":1,"magicLevel":1,"stabDef":39,"slashDef":40,"crushDef":36,"rangeDef":36,"bonusAttack":8,"bonusStrength":10},"3301":{"name":"Archer","hitpoints":50,"combatLevel":37,"attackSpeed":6,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":40,"magicLevel":1,"range":19,"stabDef":18,"slashDef":23,"crushDef":27,"rangeDef":19,"magicDef":10,"bonusRangeStrength":8},"3313":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"3314":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"3315":{"name":"Giant rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"3316":{"name":"Chicken","hitpoints":3,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3328":{"name":"Earth Warrior Champion","hitpoints":108,"combatLevel":102,"attackSpeed":4,"attackLevel":84,"strengthLevel":84,"defenceLevel":84,"rangeLevel":1,"magicLevel":1},"3329":{"name":"Giant Champion","hitpoints":70,"combatLevel":56,"attackSpeed":4,"attackLevel":36,"strengthLevel":44,"defenceLevel":52,"rangeLevel":1,"magicLevel":1},"3330":{"name":"Ghoul Champion","hitpoints":100,"combatLevel":85,"attackSpeed":4},"3353":{"name":"Goblin Champion","hitpoints":32,"combatLevel":24,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":14,"rangeLevel":1,"magicLevel":26},"3354":{"name":"Hobgoblin Champion","hitpoints":58,"combatLevel":56,"attackSpeed":4,"attackLevel":44,"strengthLevel":48,"defenceLevel":48,"rangeLevel":44,"magicLevel":1},"3355":{"name":"Imp Champion","hitpoints":40,"combatLevel":14,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":5,"magicLevel":1},"3356":{"name":"Jogre Champion","hitpoints":120,"combatLevel":107,"attackSpeed":4,"attackLevel":86,"strengthLevel":86,"defenceLevel":86,"rangeLevel":1,"magicLevel":1},"3357":{"name":"Lesser Demon Champion","hitpoints":148,"combatLevel":162,"attackSpeed":4,"attackLevel":136,"strengthLevel":140,"defenceLevel":142,"rangeLevel":1,"magicLevel":136},"3358":{"name":"Skeleton Champion","hitpoints":58,"combatLevel":40,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":34,"rangeLevel":36,"magicLevel":1},"3359":{"name":"Zombies Champion","hitpoints":60,"combatLevel":51,"attackSpeed":4,"attackLevel":38,"strengthLevel":42,"defenceLevel":42,"rangeLevel":1,"magicLevel":1},"3360":{"name":"Leon d'Cour","hitpoints":123,"combatLevel":141,"attackSpeed":4},"3361":{"name":"Kourend guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"3400":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3420":{"name":"Rabbit","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3421":{"name":"Rabbit","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3422":{"name":"Rabbit","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3423":{"name":"Grizzly bear","hitpoints":35,"combatLevel":42,"attackSpeed":4,"attackLevel":40,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"3424":{"name":"Grizzly bear cub","hitpoints":35,"combatLevel":33,"attackSpeed":4,"attackLevel":30,"strengthLevel":26,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"3425":{"name":"Grizzly bear cub","hitpoints":35,"combatLevel":36,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":28,"rangeLevel":1,"magicLevel":1},"3426":{"name":"Dire Wolf","hitpoints":85,"combatLevel":88,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":1},"3428":{"name":"Iorwerth Archer","hitpoints":105,"combatLevel":90,"attackSpeed":5,"attackLevel":10,"strengthLevel":10,"defenceLevel":80,"rangeLevel":90,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"3429":{"name":"Iorwerth Warrior","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"3433":{"name":"Tyras guard","hitpoints":110,"combatLevel":110,"attackSpeed":5,"attackLevel":85,"strengthLevel":95,"defenceLevel":100,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":30},"3434":{"name":"Tyras guard","hitpoints":110,"combatLevel":110,"attackSpeed":5,"attackLevel":85,"strengthLevel":95,"defenceLevel":100,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":30},"3443":{"name":"Lucien","hitpoints":17,"combatLevel":14,"attackSpeed":4,"attackLevel":12,"strengthLevel":11,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":6,"slashDef":8,"crushDef":7,"rangeDef":4,"bonusAttack":5,"bonusStrength":4},"3444":{"name":"Lucien","hitpoints":17,"combatLevel":14,"attackSpeed":4,"attackLevel":12,"strengthLevel":11,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":6,"slashDef":8,"crushDef":7,"rangeDef":4,"bonusAttack":5,"bonusStrength":4},"3445":{"name":"Guardian of Armadyl","hitpoints":50,"combatLevel":45,"attackSpeed":5,"attackLevel":37,"strengthLevel":37,"defenceLevel":37,"rangeLevel":1,"magicLevel":1,"stabDef":43,"slashDef":55,"crushDef":50,"rangeDef":51,"bonusAttack":14,"bonusStrength":16},"3446":{"name":"Guardian of Armadyl","hitpoints":40,"combatLevel":43,"attackSpeed":5,"attackLevel":37,"strengthLevel":37,"defenceLevel":37,"rangeLevel":1,"magicLevel":1,"stabDef":43,"slashDef":55,"crushDef":50,"rangeDef":51,"bonusAttack":14,"bonusStrength":16},"3448":{"name":"Fire Warrior of Lesarkus","hitpoints":59,"combatLevel":84,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":78,"rangeLevel":1,"magicLevel":62,"stabDef":34,"slashDef":37,"crushDef":35,"rangeDef":35},"3449":{"name":"Shadow Hound","hitpoints":62,"combatLevel":63,"attackSpeed":4,"attackLevel":53,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1},"3456":{"name":"Fareed","hitpoints":130,"combatLevel":167,"attackSpeed":4,"attackLevel":190,"strengthLevel":120,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"bonusStrength":120},"3458":{"name":"Kamil","hitpoints":130,"combatLevel":154,"attackSpeed":4,"attackLevel":190,"strengthLevel":80,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":60,"crushDef":35,"bonusAttack":60,"bonusStrength":100},"3459":{"name":"Dessous","hitpoints":200,"combatLevel":139,"attackSpeed":3,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"3460":{"name":"Dessous","hitpoints":200,"combatLevel":139,"attackSpeed":3,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"3473":{"name":"The Inadequacy","hitpoints":180,"combatLevel":343,"attackSpeed":4,"attackLevel":564,"strengthLevel":170,"defenceLevel":240,"rangeLevel":170,"magicLevel":1},"3474":{"name":"The Everlasting","hitpoints":230,"combatLevel":223,"attackSpeed":6,"attackLevel":187,"strengthLevel":231,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"3475":{"name":"The Untouchable","hitpoints":90,"combatLevel":274,"attackSpeed":6,"attackLevel":187,"strengthLevel":255,"defenceLevel":434,"rangeLevel":1,"magicLevel":1},"3476":{"name":"The Illusive","hitpoints":140,"combatLevel":108,"attackSpeed":6},"3477":{"name":"A Doubt","hitpoints":50,"combatLevel":78,"attackSpeed":6},"3478":{"name":"The Illusive","hitpoints":140,"combatLevel":108,"attackSpeed":6},"3481":{"name":"Count Draynor","hitpoints":35,"combatLevel":34,"attackSpeed":4,"attackLevel":30,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":1,"crushDef":3},"3482":{"name":"Count Draynor","hitpoints":35,"combatLevel":34,"attackSpeed":4,"attackLevel":30,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":1,"crushDef":3},"3484":{"name":"Monk of Zamorak","hitpoints":20,"combatLevel":22,"attackSpeed":4,"attackLevel":18,"strengthLevel":18,"defenceLevel":22,"rangeLevel":1,"magicLevel":25},"3485":{"name":"Monk of Zamorak","hitpoints":10,"combatLevel":17,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":12,"rangeLevel":1,"magicLevel":25},"3486":{"name":"Monk of Zamorak","hitpoints":25,"combatLevel":30,"attackSpeed":4,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":40},"3508":{"name":"Bouncer","hitpoints":120,"combatLevel":160,"attackSpeed":4,"attackLevel":145,"strengthLevel":145,"defenceLevel":145,"rangeLevel":1,"magicLevel":1},"3509":{"name":"Bouncer","hitpoints":120,"combatLevel":160,"attackSpeed":4,"attackLevel":145,"strengthLevel":145,"defenceLevel":145,"rangeLevel":1,"magicLevel":1},"3517":{"name":"Renegade Knight","hitpoints":48,"combatLevel":37,"attackSpeed":4,"attackLevel":28,"strengthLevel":28,"defenceLevel":28,"rangeLevel":1,"magicLevel":1,"stabDef":99,"slashDef":82,"crushDef":62,"bonusAttack":20,"bonusStrength":16},"3518":{"name":"Thrantax the Mighty","hitpoints":80,"combatLevel":92,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"3527":{"name":"Sir Mordred","hitpoints":38,"combatLevel":39,"attackSpeed":4,"attackLevel":33,"strengthLevel":33,"defenceLevel":33,"rangeLevel":1,"magicLevel":1,"stabDef":99,"slashDef":82,"crushDef":45,"bonusAttack":20,"bonusStrength":16},"3544":{"name":"Desert snake","hitpoints":6,"combatLevel":5,"attackSpeed":4,"attackLevel":4,"strengthLevel":5,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3549":{"name":"Menaphite Thug","hitpoints":60,"combatLevel":55,"attackSpeed":4,"attackLevel":60,"strengthLevel":50,"defenceLevel":20,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":4,"bonusStrength":9},"3550":{"name":"Menaphite Thug","hitpoints":60,"combatLevel":55,"attackSpeed":4,"attackLevel":60,"strengthLevel":50,"defenceLevel":20,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":4,"bonusStrength":9},"3551":{"name":"Tough Guy","hitpoints":75,"combatLevel":75,"attackSpeed":4,"attackLevel":85,"strengthLevel":50,"defenceLevel":50,"magicLevel":80,"stabDef":9,"slashDef":8,"crushDef":10,"bonusAttack":9,"bonusStrength":9,"poisonImmune":true},"3565":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4},"3600":{"name":"Frogeel","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":91,"strengthLevel":89,"defenceLevel":90,"rangeLevel":1,"magicLevel":1},"3601":{"name":"Unicow","hitpoints":24,"combatLevel":25,"attackSpeed":4,"attackLevel":18,"strengthLevel":26,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"3602":{"name":"Spidine","hitpoints":35,"combatLevel":42,"attackSpeed":4,"attackLevel":37,"strengthLevel":40,"defenceLevel":36,"rangeLevel":1,"magicLevel":1},"3603":{"name":"Swordchick","hitpoints":35,"combatLevel":46,"attackSpeed":4,"attackLevel":41,"strengthLevel":42,"defenceLevel":45,"rangeLevel":1,"magicLevel":1},"3604":{"name":"Jubster","hitpoints":60,"combatLevel":87,"attackSpeed":4,"attackLevel":82,"strengthLevel":78,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"3605":{"name":"Newtroost","hitpoints":18,"combatLevel":19,"attackSpeed":4,"attackLevel":18,"strengthLevel":16,"defenceLevel":17,"rangeLevel":1,"magicLevel":1},"3607":{"name":"Dungeon rat","hitpoints":12,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3608":{"name":"Dungeon rat","hitpoints":12,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3609":{"name":"Dungeon rat","hitpoints":12,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3612":{"name":"Possessed pickaxe","hitpoints":40,"combatLevel":50,"attackSpeed":4,"attackLevel":40,"strengthLevel":55,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"3615":{"name":"Skeletal miner","hitpoints":39,"combatLevel":42,"attackSpeed":4,"attackLevel":35,"strengthLevel":37,"defenceLevel":38,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"3616":{"name":"Treus Dayth","hitpoints":100,"combatLevel":95,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3625":{"name":"Ghost","hitpoints":27,"combatLevel":29,"attackSpeed":4,"attackLevel":23,"strengthLevel":23,"defenceLevel":28,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3651":{"name":"Gardener","hitpoints":7,"combatLevel":4,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"3652":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3661":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3662":{"name":"Chicken","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3663":{"name":"Rooster","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3664":{"name":"Rabbit","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3665":{"name":"Rabbit","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3674":{"name":"Ragnar","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3675":{"name":"Einar","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3676":{"name":"Alrik","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3677":{"name":"Thorhild","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3678":{"name":"Halla","hitpoints":1,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3681":{"name":"Rannveig","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3682":{"name":"Thora","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3683":{"name":"Valgerd","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3684":{"name":"Skraeling","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3685":{"name":"Broddi","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3686":{"name":"Skraeling","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3687":{"name":"Ragnvald","hitpoints":1,"combatLevel":2,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"3690":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"3691":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"3692":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"3693":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"3694":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"3695":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"3696":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"3697":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"3698":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"3699":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"3707":{"name":"Feral Vampyre","hitpoints":80,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":30,"defenceLevel":60,"rangeLevel":1,"magicLevel":1},"3708":{"name":"Feral Vampyre","hitpoints":80,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":30,"defenceLevel":60,"rangeLevel":1,"magicLevel":1},"3709":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3710":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3711":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3712":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3713":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3714":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3715":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3716":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3717":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3718":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3719":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3720":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3721":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3722":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3723":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3724":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3725":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3726":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3727":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3728":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3729":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3730":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3731":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3732":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3734":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"3735":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"3736":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"3737":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"3738":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"3739":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"3748":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3749":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3750":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3751":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3752":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3753":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3754":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3755":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3756":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3757":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3758":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3759":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3760":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"3761":{"name":"Vyrewatch","hitpoints":90,"combatLevel":110,"attackSpeed":8,"attackLevel":110,"strengthLevel":96,"defenceLevel":85,"rangeLevel":110,"magicLevel":110},"3762":{"name":"Vyrewatch","hitpoints":105,"combatLevel":120,"attackSpeed":8,"attackLevel":120,"strengthLevel":106,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3763":{"name":"Vyrewatch","hitpoints":110,"combatLevel":125,"attackSpeed":8,"attackLevel":120,"strengthLevel":116,"defenceLevel":85,"rangeLevel":120,"magicLevel":120},"3851":{"name":"Moss giant","hitpoints":85,"combatLevel":48,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"3852":{"name":"Moss giant","hitpoints":85,"combatLevel":48,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"3863":{"name":"Jake","hitpoints":50,"combatLevel":37,"attackSpeed":4,"attackLevel":27,"strengthLevel":31,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"3865":{"name":"Wilson","hitpoints":50,"combatLevel":37,"attackSpeed":4,"attackLevel":27,"strengthLevel":31,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"3869":{"name":"Palmer","hitpoints":50,"combatLevel":37,"attackSpeed":4,"attackLevel":27,"strengthLevel":31,"defenceLevel":25,"rangeLevel":1,"magicLevel":1},"3897":{"name":"Koschei the deathless","hitpoints":30,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20},"3898":{"name":"Koschei the deathless","hitpoints":50,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"magicDef":40},"3899":{"name":"Koschei the deathless","hitpoints":70,"attackSpeed":4,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60},"3900":{"name":"Koschei the deathless","hitpoints":255,"attackSpeed":1,"attackLevel":255,"strengthLevel":5,"defenceLevel":255},"3901":{"name":"Fox","hitpoints":30,"combatLevel":19,"attackSpeed":4,"attackLevel":1,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":30,"magicDef":30,"bonusAttack":30,"bonusStrength":30},"3902":{"name":"Bunny","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3903":{"name":"Bunny","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"3908":{"name":"Bear Cub","hitpoints":20,"combatLevel":15,"attackSpeed":4,"attackLevel":10,"strengthLevel":15,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3909":{"name":"Bear Cub","hitpoints":20,"combatLevel":15,"attackSpeed":4,"attackLevel":10,"strengthLevel":15,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3910":{"name":"Unicorn Foal","hitpoints":15,"combatLevel":12,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"3911":{"name":"Black unicorn Foal","hitpoints":25,"combatLevel":22,"attackSpeed":4,"attackLevel":18,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"3912":{"name":"Wolf","hitpoints":69,"combatLevel":64,"attackSpeed":4,"attackLevel":50,"strengthLevel":55,"defenceLevel":52,"rangeLevel":1,"magicLevel":1},"3922":{"name":"The Draugen","hitpoints":60,"combatLevel":69,"attackSpeed":4,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":500,"magicDef":500,"poisonImmune":true,"venomImmune":true},"3938":{"name":"Freidir","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusStrength":20},"3939":{"name":"Borrokar","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusStrength":20},"3940":{"name":"Lanzig","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusStrength":20},"3943":{"name":"Lensa","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusStrength":20},"3944":{"name":"Jennella","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusStrength":20},"3949":{"name":"Market Guard","hitpoints":50,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50,"bonusAttack":40,"bonusStrength":40},"3950":{"name":"Warrior","hitpoints":50,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50,"bonusAttack":40,"bonusStrength":40},"3957":{"name":"Ungadulu","hitpoints":65,"combatLevel":70,"attackSpeed":4,"attackLevel":57,"strengthLevel":65,"defenceLevel":61,"magicLevel":65,"bonusStrength":3,"bonusRangeStrength":7},"3958":{"name":"Ungadulu","hitpoints":150,"combatLevel":169,"attackSpeed":4,"attackLevel":147,"strengthLevel":147,"defenceLevel":147,"bonusStrength":3,"bonusRangeStrength":7},"3959":{"name":"Jungle savage","hitpoints":90,"combatLevel":90,"attackSpeed":4,"attackLevel":76,"strengthLevel":76,"defenceLevel":76,"bonusAttack":8,"bonusStrength":10},"3962":{"name":"Nezikchened","hitpoints":150,"combatLevel":187,"attackSpeed":5,"attackLevel":165,"strengthLevel":168,"defenceLevel":167,"rangeLevel":160,"magicLevel":160},"3963":{"name":"Viyeldi","hitpoints":80,"combatLevel":79,"attackSpeed":4,"attackLevel":62,"strengthLevel":66,"defenceLevel":70,"bonusAttack":7,"bonusStrength":3},"3964":{"name":"San Tojalon","hitpoints":120,"combatLevel":106,"attackSpeed":4,"attackLevel":86,"strengthLevel":84,"defenceLevel":86,"stabDef":18,"slashDef":22,"crushDef":20,"rangeDef":20,"bonusAttack":16,"bonusStrength":17},"3965":{"name":"Irvig Senay","hitpoints":125,"combatLevel":100,"attackSpeed":4,"attackLevel":76,"strengthLevel":74,"defenceLevel":81,"stabDef":27,"slashDef":31,"crushDef":29,"rangeDef":29,"bonusAttack":29,"bonusStrength":31},"3966":{"name":"Ranalph Devere","hitpoints":130,"combatLevel":92,"attackSpeed":4,"attackLevel":66,"strengthLevel":67,"defenceLevel":66,"stabDef":38,"slashDef":40,"crushDef":36,"rangeDef":38,"bonusAttack":45,"bonusStrength":44},"3969":{"name":"Zombie rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"3970":{"name":"Zombie rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"3971":{"name":"Zombie rat","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":3,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"3972":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"3973":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"3974":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":11,"rangeDef":4,"magicDef":1,"bonusAttack":15,"bonusStrength":14},"3975":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3976":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3977":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3978":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3979":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"3980":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":5,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":8,"crushDef":12,"rangeDef":11,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"3981":{"name":"Zombie","hitpoints":30,"combatLevel":24,"attackSpeed":5,"attackLevel":19,"strengthLevel":21,"defenceLevel":16,"rangeLevel":1,"magicLevel":1,"stabDef":9,"slashDef":8,"crushDef":12,"rangeDef":11,"magicDef":10,"bonusAttack":5,"bonusStrength":7},"3982":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"3996":{"name":"Witch's experiment","hitpoints":21,"combatLevel":19,"attackSpeed":4,"attackLevel":18,"strengthLevel":10,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"3997":{"name":"Witch's experiment (second form)","hitpoints":31,"combatLevel":30,"attackSpeed":4,"attackLevel":28,"strengthLevel":20,"defenceLevel":29,"rangeLevel":1,"magicLevel":1},"3998":{"name":"Witch's experiment (third form)","hitpoints":41,"combatLevel":42,"attackSpeed":4,"attackLevel":38,"strengthLevel":30,"defenceLevel":39,"rangeLevel":1,"magicLevel":1},"3999":{"name":"Witch's experiment (fourth form)","hitpoints":51,"combatLevel":53,"attackSpeed":4,"attackLevel":48,"strengthLevel":40,"defenceLevel":49,"rangeLevel":1,"magicLevel":1},"4004":{"name":"Shadow","hitpoints":15,"combatLevel":73,"attackSpeed":4,"attackLevel":90,"strengthLevel":120,"defenceLevel":5,"rangeLevel":1,"magicLevel":90,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":30,"magicDef":30},"4005":{"name":"Dark beast","hitpoints":220,"combatLevel":182,"slayerLevel":90,"attackSpeed":4,"attackLevel":140,"strengthLevel":160,"defenceLevel":120,"rangeLevel":1,"magicLevel":160,"stabDef":30,"slashDef":40,"crushDef":100,"rangeDef":100,"magicDef":90},"4043":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4044":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4045":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4046":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4047":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4048":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4049":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4050":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4051":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4052":{"name":"Pirate","hitpoints":20,"combatLevel":57},"4067":{"name":"Black Knight Titan","hitpoints":142,"combatLevel":120,"attackSpeed":7,"attackLevel":91,"strengthLevel":100,"defenceLevel":91,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":27,"crushDef":18,"rangeDef":1000,"magicDef":1000,"bonusAttack":27,"bonusStrength":22},"4068":{"name":"Monk","hitpoints":15,"combatLevel":5,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4095":{"name":"Eadburg","hitpoints":10,"combatLevel":4,"attackSpeed":4,"attackLevel":3,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4096":{"name":"Archer","hitpoints":50,"combatLevel":42,"attackSpeed":6,"defenceLevel":40,"rangeLevel":40,"magicLevel":1,"stabDef":39,"slashDef":50,"crushDef":49,"rangeDef":45,"magicDef":18,"bonusAttack":34},"4097":{"name":"Archer","hitpoints":50,"combatLevel":42,"attackSpeed":6,"defenceLevel":40,"rangeLevel":40,"magicLevel":1,"stabDef":39,"slashDef":50,"crushDef":49,"rangeDef":45,"magicDef":18,"bonusAttack":34},"4098":{"name":"Archer","hitpoints":50,"combatLevel":42,"attackSpeed":6,"defenceLevel":40,"rangeLevel":40,"magicLevel":1,"stabDef":39,"slashDef":50,"crushDef":49,"rangeDef":45,"magicDef":18,"bonusAttack":34},"4099":{"name":"Guard","hitpoints":40,"combatLevel":37,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"magicLevel":1,"stabDef":36,"slashDef":48,"crushDef":50,"rangeDef":38,"bonusAttack":18,"bonusStrength":16},"4100":{"name":"Guard","hitpoints":40,"combatLevel":37,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"magicLevel":1,"stabDef":36,"slashDef":48,"crushDef":50,"rangeDef":38,"bonusAttack":18,"bonusStrength":16},"4104":{"name":"Servant","hitpoints":10,"combatLevel":5,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4107":{"name":"Breoca","hitpoints":10,"combatLevel":5,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4108":{"name":"Ocga","hitpoints":10,"combatLevel":5,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4109":{"name":"Penda","hitpoints":10,"combatLevel":5,"attackSpeed":4,"attackLevel":3,"strengthLevel":3,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4110":{"name":"Hygd","hitpoints":10,"combatLevel":4,"attackSpeed":4,"attackLevel":3,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4111":{"name":"Ceolburg","hitpoints":10,"combatLevel":4,"attackSpeed":4,"attackLevel":3,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4114":{"name":"White Knight","hitpoints":55,"combatLevel":42,"attackSpeed":7,"attackLevel":32,"strengthLevel":35,"defenceLevel":27,"rangeLevel":1,"magicLevel":1,"stabDef":83,"slashDef":76,"crushDef":70,"rangeDef":74,"bonusAttack":30,"bonusStrength":31},"4115":{"name":"Fareed (hard)","hitpoints":255,"combatLevel":299,"attackSpeed":4,"attackLevel":380,"strengthLevel":240,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"bonusStrength":120},"4120":{"name":"Troll general","hitpoints":140,"combatLevel":113,"attackSpeed":4,"attackLevel":70,"strengthLevel":140,"defenceLevel":40,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"4121":{"name":"Troll general","hitpoints":140,"combatLevel":113,"attackSpeed":4,"attackLevel":70,"strengthLevel":140,"defenceLevel":40,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"4122":{"name":"Troll general","hitpoints":140,"combatLevel":113,"attackSpeed":4,"attackLevel":70,"strengthLevel":140,"defenceLevel":40,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"4123":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4124":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4125":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4126":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4127":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4128":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4129":{"name":"Troll spectator","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4130":{"name":"Dad","hitpoints":120,"combatLevel":101,"attackSpeed":8,"attackLevel":60,"strengthLevel":120,"defenceLevel":50,"stabDef":25,"slashDef":25,"crushDef":40,"rangeDef":200,"magicDef":200,"bonusAttack":40,"bonusStrength":70},"4131":{"name":"Twig","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4132":{"name":"Berry","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4133":{"name":"Twig","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4134":{"name":"Berry","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4135":{"name":"Thrower troll","hitpoints":95,"combatLevel":68,"attackSpeed":7,"attackLevel":30,"strengthLevel":95,"defenceLevel":15,"rangeLevel":60,"rangeDef":200,"magicDef":200},"4136":{"name":"Thrower troll","hitpoints":95,"combatLevel":68,"attackSpeed":7,"attackLevel":30,"strengthLevel":95,"defenceLevel":15,"rangeLevel":60,"rangeDef":200,"magicDef":200},"4137":{"name":"Thrower troll","hitpoints":95,"combatLevel":68,"attackSpeed":7,"attackLevel":30,"strengthLevel":95,"defenceLevel":15,"rangeLevel":60,"rangeDef":200,"magicDef":200},"4138":{"name":"Thrower troll","hitpoints":95,"combatLevel":68,"attackSpeed":7,"attackLevel":30,"strengthLevel":95,"defenceLevel":15,"rangeLevel":60,"rangeDef":200,"magicDef":200},"4139":{"name":"Thrower troll","hitpoints":95,"combatLevel":68,"attackSpeed":7,"attackLevel":30,"strengthLevel":95,"defenceLevel":15,"rangeLevel":60,"rangeDef":200,"magicDef":200},"4143":{"name":"Mountain troll","hitpoints":90,"combatLevel":71,"attackSpeed":6,"attackLevel":40,"strengthLevel":90,"defenceLevel":25,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"4167":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4168":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4169":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4170":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4171":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4172":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4173":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4174":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4175":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4176":{"name":"Outlaw","hitpoints":20,"combatLevel":32,"attackSpeed":6,"attackLevel":35,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4184":{"name":"Crocodile","hitpoints":62,"combatLevel":63,"attackSpeed":4,"attackLevel":53,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1},"4185":{"name":"Jackal","hitpoints":27,"combatLevel":21,"attackSpeed":4,"attackLevel":17,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1},"4186":{"name":"Locust","hitpoints":27,"combatLevel":18,"attackSpeed":4,"attackLevel":17,"strengthLevel":18,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4188":{"name":"Plague frog","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":18,"strengthLevel":10,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4192":{"name":"Scarab swarm","hitpoints":25,"combatLevel":98,"attackSpeed":1,"attackLevel":255,"strengthLevel":5,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":5,"rangeDef":90,"magicDef":90},"4195":{"name":"Het","hitpoints":75,"combatLevel":81,"attackSpeed":4,"attackLevel":80,"strengthLevel":83,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"4196":{"name":"Apmeken","hitpoints":70,"combatLevel":75,"attackSpeed":4,"attackLevel":30,"strengthLevel":40,"defenceLevel":60,"rangeLevel":1,"magicLevel":85,"stabDef":65,"slashDef":65,"crushDef":65,"magicDef":50},"4197":{"name":"Scabaras","hitpoints":50,"combatLevel":75,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":66,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":70},"4198":{"name":"Crondis","hitpoints":60,"combatLevel":75,"attackSpeed":4,"attackLevel":80,"strengthLevel":70,"defenceLevel":35,"rangeLevel":1,"magicLevel":40,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":40,"magicDef":70},"4210":{"name":"Possessed Priest","hitpoints":90,"combatLevel":91,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":40,"rangeLevel":1,"magicLevel":60,"stabDef":90,"crushDef":90,"bonusStrength":90},"4246":{"name":"Monk","hitpoints":5,"combatLevel":3,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"4247":{"name":"Thief","hitpoints":17,"combatLevel":14,"attackSpeed":4,"attackLevel":14,"strengthLevel":10,"defenceLevel":9,"rangeLevel":2,"stabDef":8,"slashDef":13,"crushDef":14,"rangeDef":9,"magicDef":4,"bonusAttack":6,"bonusStrength":7},"4248":{"name":"Head Thief","hitpoints":37,"combatLevel":26,"attackSpeed":5,"attackLevel":24,"strengthLevel":18,"defenceLevel":16,"rangeLevel":2,"stabDef":8,"slashDef":14,"crushDef":15,"rangeDef":9,"magicDef":4,"bonusStrength":10},"4276":{"name":"Jail guard","hitpoints":32,"combatLevel":26,"attackSpeed":5,"attackLevel":19,"strengthLevel":23,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":10,"rangeDef":9,"magicDef":4,"bonusAttack":9,"bonusStrength":5},"4277":{"name":"Jail guard","hitpoints":32,"combatLevel":26,"attackSpeed":5,"attackLevel":19,"strengthLevel":23,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":10,"rangeDef":9,"magicDef":4,"bonusAttack":9,"bonusStrength":5},"4278":{"name":"Jail guard","hitpoints":32,"combatLevel":26,"attackSpeed":5,"attackLevel":19,"strengthLevel":23,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":10,"rangeDef":9,"magicDef":4,"bonusAttack":9,"bonusStrength":5},"4279":{"name":"Jail guard","hitpoints":32,"combatLevel":26,"attackSpeed":5,"attackLevel":19,"strengthLevel":23,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":10,"rangeDef":9,"magicDef":4,"bonusAttack":9,"bonusStrength":5},"4303":{"name":"Kalphite Queen","hitpoints":255,"combatLevel":333,"attackSpeed":4,"attackLevel":300,"strengthLevel":300,"defenceLevel":300,"rangeLevel":1,"magicLevel":150,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":100,"magicDef":100},"4304":{"name":"Kalphite Queen","hitpoints":255,"combatLevel":333,"attackSpeed":4,"attackLevel":300,"strengthLevel":300,"defenceLevel":300,"rangeLevel":1,"magicLevel":150,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":10,"magicDef":10},"4308":{"name":"Sea troll","hitpoints":100,"combatLevel":79,"attackSpeed":4,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1},"4309":{"name":"Sea troll","hitpoints":80,"combatLevel":65,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"4310":{"name":"Sea troll","hitpoints":80,"combatLevel":87,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":1},"4311":{"name":"Sea troll","hitpoints":80,"combatLevel":101,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1},"4315":{"name":"Sea Troll Queen","hitpoints":200,"combatLevel":170,"attackSpeed":4,"attackLevel":100,"strengthLevel":70,"defenceLevel":100,"rangeLevel":1,"magicLevel":150,"stabDef":20,"slashDef":40,"crushDef":40,"magicDef":40,"bonusAttack":100,"bonusStrength":40},"4319":{"name":"Skeleton Mage","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":60,"rangeLevel":1,"magicLevel":100,"magicDef":15},"4321":{"name":"Renegade Knight","hitpoints":48,"combatLevel":37,"attackSpeed":4,"attackLevel":28,"strengthLevel":28,"defenceLevel":28,"rangeLevel":1,"magicLevel":1,"stabDef":99,"slashDef":82,"crushDef":62,"bonusAttack":20,"bonusStrength":16},"4328":{"name":"Sigmund","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":45,"strengthLevel":45,"defenceLevel":24,"rangeLevel":1,"magicLevel":1,"magic":8,"venomImmune":true},"4331":{"name":"Black Knight","hitpoints":42,"combatLevel":33,"attackSpeed":5,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":73,"slashDef":76,"crushDef":70,"rangeDef":72,"bonusAttack":18,"bonusStrength":16},"4342":{"name":"Sir Lucan","hitpoints":105,"combatLevel":120,"attackSpeed":5,"attackLevel":105,"strengthLevel":110,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":275,"slashDef":65,"crushDef":45,"bonusAttack":40,"bonusStrength":40},"4343":{"name":"Sir Palomedes","hitpoints":100,"combatLevel":118,"attackSpeed":5,"attackLevel":105,"strengthLevel":105,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":250,"slashDef":250,"crushDef":50,"bonusAttack":35,"bonusStrength":35},"4344":{"name":"Sir Lancelot","hitpoints":115,"combatLevel":127,"attackSpeed":4,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":300,"slashDef":85,"crushDef":300,"bonusAttack":60,"bonusStrength":60},"4345":{"name":"Sir Bedivere","hitpoints":90,"combatLevel":110,"attackSpeed":5,"attackLevel":99,"strengthLevel":95,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":15,"crushDef":190,"bonusAttack":20,"bonusStrength":20},"4346":{"name":"Sir Tristram","hitpoints":105,"combatLevel":115,"attackSpeed":5,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"crushDef":225,"bonusAttack":25,"bonusStrength":25},"4347":{"name":"Sir Pelleas","hitpoints":99,"combatLevel":112,"attackSpeed":5,"attackLevel":95,"strengthLevel":99,"defenceLevel":98,"rangeLevel":1,"magicLevel":1,"stabDef":200,"crushDef":5,"bonusAttack":22,"bonusStrength":22},"4348":{"name":"Sir Gawain","hitpoints":110,"combatLevel":122,"attackSpeed":5,"attackLevel":110,"strengthLevel":105,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":65,"slashDef":90,"crushDef":300,"bonusAttack":40,"bonusStrength":45},"4349":{"name":"Sir Kay","hitpoints":110,"combatLevel":124,"attackSpeed":5,"attackLevel":110,"strengthLevel":110,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":300,"crushDef":65,"bonusAttack":55,"bonusStrength":55},"4354":{"name":"Sir Lancelot","hitpoints":115,"combatLevel":127,"attackSpeed":4,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":300,"slashDef":85,"crushDef":300,"bonusAttack":60,"bonusStrength":60},"4355":{"name":"Sir Kay","hitpoints":110,"combatLevel":124,"attackSpeed":5,"attackLevel":110,"strengthLevel":110,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":300,"crushDef":65,"bonusAttack":55,"bonusStrength":55},"4356":{"name":"Sir Gawain","hitpoints":110,"combatLevel":122,"attackSpeed":5,"attackLevel":110,"strengthLevel":105,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":65,"slashDef":90,"crushDef":300,"bonusAttack":40,"bonusStrength":45},"4357":{"name":"Sir Lucan","hitpoints":105,"combatLevel":120,"attackSpeed":5,"attackLevel":105,"strengthLevel":110,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":275,"slashDef":65,"crushDef":45,"bonusAttack":40,"bonusStrength":40},"4358":{"name":"Sir Palomedes","hitpoints":100,"combatLevel":118,"attackSpeed":5,"attackLevel":105,"strengthLevel":105,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":250,"slashDef":250,"crushDef":50,"bonusAttack":35,"bonusStrength":35},"4359":{"name":"Sir Tristram","hitpoints":105,"combatLevel":115,"attackSpeed":5,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"crushDef":225,"bonusAttack":25,"bonusStrength":25},"4360":{"name":"Sir Pelleas","hitpoints":99,"combatLevel":112,"attackSpeed":5,"attackLevel":95,"strengthLevel":99,"defenceLevel":98,"rangeLevel":1,"magicLevel":1,"stabDef":200,"crushDef":5,"bonusAttack":22,"bonusStrength":22},"4361":{"name":"Sir Bedivere","hitpoints":90,"combatLevel":110,"attackSpeed":5,"attackLevel":99,"strengthLevel":95,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":15,"crushDef":190,"bonusAttack":20,"bonusStrength":20},"4362":{"name":"Ogre chieftain","hitpoints":60,"combatLevel":81,"attackSpeed":4,"attackLevel":75,"strengthLevel":71,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":21,"crushDef":16,"bonusAttack":5,"bonusStrength":7},"4363":{"name":"Ogre chieftain","hitpoints":60,"combatLevel":81,"attackSpeed":4,"attackLevel":75,"strengthLevel":71,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":21,"crushDef":16,"bonusAttack":5,"bonusStrength":7},"4367":{"name":"Gorad","hitpoints":80,"combatLevel":68,"attackSpeed":4,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":27,"crushDef":21,"bonusAttack":8,"bonusStrength":8},"4373":{"name":"City guard","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":13,"slashDef":24,"crushDef":19,"bonusAttack":9,"bonusStrength":13},"4381":{"name":"Enclave guard","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":13,"slashDef":24,"crushDef":19,"bonusAttack":9,"bonusStrength":13},"4382":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4383":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4384":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4385":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"4387":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4389":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4391":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4393":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4395":{"name":"Ogre shaman","hitpoints":1,"combatLevel":113,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4405":{"name":"Tower guard","hitpoints":22,"combatLevel":28,"attackSpeed":4,"attackLevel":26,"strengthLevel":26,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"stabDef":23,"slashDef":35,"crushDef":28,"bonusAttack":8,"bonusStrength":8},"4406":{"name":"Colonel Radick","hitpoints":65,"combatLevel":38,"attackSpeed":4,"attackLevel":25,"strengthLevel":25,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":39,"slashDef":31,"crushDef":34,"bonusAttack":9,"bonusStrength":13},"4421":{"name":"Undead cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4427":{"name":"Vampyre Juvinate","hitpoints":65,"combatLevel":54,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4428":{"name":"Vampyre Juvinate","hitpoints":65,"combatLevel":54,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4429":{"name":"Vampyre Juvinate","hitpoints":65,"combatLevel":54,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4430":{"name":"Vampyre Juvinate","hitpoints":65,"combatLevel":54,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4431":{"name":"Feral Vampyre","hitpoints":80,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":30,"defenceLevel":60,"rangeLevel":1,"magicLevel":1},"4432":{"name":"Vampyre Juvinate","hitpoints":65,"combatLevel":54,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4436":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4437":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4438":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4439":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4442":{"name":"Vampyre Juvinate","hitpoints":110,"combatLevel":75,"attackSpeed":4,"attackLevel":70,"strengthLevel":50,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"4443":{"name":"Vampyre Juvinate","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":50,"strengthLevel":37,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4483":{"name":"Gadderanks","hitpoints":20,"combatLevel":35,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10,"magicDef":10,"poisonImmune":true,"venomImmune":true},"4484":{"name":"Gadderanks","hitpoints":20,"combatLevel":35,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10,"magicDef":10,"poisonImmune":true,"venomImmune":true},"4485":{"name":"Gadderanks","hitpoints":20,"combatLevel":35,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10,"magicDef":10,"poisonImmune":true,"venomImmune":true},"4486":{"name":"Vampyre Juvinate","hitpoints":60,"combatLevel":50,"attackSpeed":4,"attackLevel":50,"strengthLevel":37,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4487":{"name":"Vampyre Juvinate","hitpoints":65,"combatLevel":54,"attackSpeed":4,"attackLevel":55,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"4491":{"name":"Skeleton fremennik","hitpoints":25,"combatLevel":40,"attackSpeed":6,"attackLevel":53,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4492":{"name":"Skeleton fremennik","hitpoints":25,"combatLevel":40,"attackSpeed":6,"attackLevel":53,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4493":{"name":"Skeleton fremennik","hitpoints":25,"combatLevel":40,"attackSpeed":6,"attackLevel":53,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4494":{"name":"Skeleton fremennik","hitpoints":35,"combatLevel":50,"attackSpeed":6,"attackLevel":60,"strengthLevel":43,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4495":{"name":"Skeleton fremennik","hitpoints":35,"combatLevel":50,"attackSpeed":6,"attackLevel":60,"strengthLevel":43,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4496":{"name":"Skeleton fremennik","hitpoints":35,"combatLevel":50,"attackSpeed":6,"attackLevel":60,"strengthLevel":43,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4497":{"name":"Skeleton fremennik","hitpoints":40,"combatLevel":60,"attackSpeed":6,"attackLevel":72,"strengthLevel":54,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4498":{"name":"Skeleton fremennik","hitpoints":40,"combatLevel":60,"attackSpeed":6,"attackLevel":72,"strengthLevel":54,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4499":{"name":"Skeleton fremennik","hitpoints":40,"combatLevel":60,"attackSpeed":6,"attackLevel":72,"strengthLevel":54,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":15,"rangeDef":15,"bonusAttack":8,"bonusStrength":13},"4500":{"name":"Ulfric","hitpoints":60,"combatLevel":100,"attackSpeed":6,"attackLevel":100,"strengthLevel":82,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"4501":{"name":"Brine rat","hitpoints":50,"combatLevel":70,"slayerLevel":47,"attackSpeed":4,"attackLevel":70,"strengthLevel":79,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"4504":{"name":"Giant bat","hitpoints":32,"combatLevel":27,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":12,"rangeDef":8,"magicDef":10},"4505":{"name":"Ulfric","hitpoints":60,"combatLevel":100,"attackSpeed":6,"attackLevel":100,"strengthLevel":82,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"4522":{"name":"Guard","hitpoints":30,"combatLevel":22,"attackSpeed":5,"attackLevel":16,"strengthLevel":20,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"4523":{"name":"Guard","hitpoints":30,"combatLevel":22,"attackSpeed":5,"attackLevel":16,"strengthLevel":20,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"4524":{"name":"Guard","hitpoints":30,"combatLevel":22,"attackSpeed":5,"attackLevel":16,"strengthLevel":20,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"4525":{"name":"Guard","hitpoints":30,"combatLevel":22,"attackSpeed":5,"attackLevel":16,"strengthLevel":20,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"4526":{"name":"Guard","hitpoints":30,"combatLevel":22,"attackSpeed":5,"attackLevel":16,"strengthLevel":20,"defenceLevel":12,"rangeLevel":1,"magicLevel":1,"stabDef":7,"slashDef":7,"rangeDef":7},"4533":{"name":"Blessed spider","hitpoints":32,"combatLevel":39,"attackSpeed":4,"attackLevel":35,"strengthLevel":35,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":16,"crushDef":7,"rangeDef":16,"magicDef":12},"4534":{"name":"Blessed giant rat","hitpoints":30,"combatLevel":9,"attackSpeed":4,"attackLevel":5,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4535":{"name":"Blessed giant rat","hitpoints":30,"combatLevel":9,"attackSpeed":4,"attackLevel":5,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4561":{"name":"Spider","hitpoints":2,"combatLevel":1,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4634":{"name":"Rowdy slave","hitpoints":16,"combatLevel":10,"attackSpeed":4,"attackLevel":7,"strengthLevel":7,"defenceLevel":7},"4635":{"name":"Mercenary Captain","hitpoints":80,"combatLevel":47,"attackSpeed":4,"attackLevel":32,"strengthLevel":29,"defenceLevel":32,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4643":{"name":"Shantay Guard","hitpoints":32,"combatLevel":22,"attackSpeed":4,"attackLevel":17,"strengthLevel":16,"defenceLevel":16,"stabDef":12,"slashDef":15,"crushDef":18,"rangeDef":15,"magicDef":6,"bonusAttack":11,"bonusStrength":12},"4648":{"name":"Shantay Guard","hitpoints":32,"combatLevel":22,"attackSpeed":4,"attackLevel":17,"strengthLevel":16,"defenceLevel":16,"stabDef":12,"slashDef":15,"crushDef":18,"rangeDef":15,"magicDef":6,"bonusAttack":11,"bonusStrength":12},"4649":{"name":"Desert Wolf","hitpoints":34,"combatLevel":27,"attackSpeed":4,"attackLevel":20,"strengthLevel":21,"defenceLevel":22},"4650":{"name":"Desert Wolf","hitpoints":34,"combatLevel":27,"attackSpeed":4,"attackLevel":20,"strengthLevel":21,"defenceLevel":22},"4651":{"name":"Desert Wolf","hitpoints":34,"combatLevel":27,"attackSpeed":4,"attackLevel":20,"strengthLevel":21,"defenceLevel":22},"4652":{"name":"Ugthanki","hitpoints":45,"combatLevel":42,"attackSpeed":4,"attackLevel":35,"strengthLevel":35,"defenceLevel":35},"4655":{"name":"Bedabin Nomad Fighter","hitpoints":50,"combatLevel":56,"attackSpeed":4,"attackLevel":40,"strengthLevel":50,"defenceLevel":60,"bonusAttack":9,"bonusStrength":14},"4656":{"name":"Mercenary","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4657":{"name":"Mercenary","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4658":{"name":"Mercenary","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4659":{"name":"Mercenary","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4660":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4661":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4662":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4663":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4664":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4665":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4666":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4667":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4668":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4669":{"name":"Guard","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":39,"stabDef":17,"slashDef":15,"crushDef":19,"rangeDef":19,"bonusAttack":9,"bonusStrength":14},"4682":{"name":"Sir Leye","hitpoints":20,"combatLevel":20,"attackSpeed":5},"4688":{"name":"Angry unicorn","hitpoints":50,"combatLevel":47,"attackSpeed":4,"attackLevel":38,"strengthLevel":41,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"4689":{"name":"Angry giant rat","hitpoints":50,"combatLevel":47,"attackSpeed":4,"attackLevel":38,"strengthLevel":41,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"4690":{"name":"Angry giant rat","hitpoints":50,"combatLevel":47,"attackSpeed":4,"attackLevel":38,"strengthLevel":41,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"4691":{"name":"Angry goblin","hitpoints":50,"combatLevel":47,"attackSpeed":4,"attackLevel":38,"strengthLevel":41,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"4692":{"name":"Angry bear","hitpoints":50,"combatLevel":47,"attackSpeed":4,"attackLevel":38,"strengthLevel":41,"defenceLevel":38,"rangeLevel":1,"magicLevel":1},"4693":{"name":"Fear reaper","hitpoints":57,"combatLevel":55,"attackSpeed":4,"attackLevel":45,"strengthLevel":48,"defenceLevel":45,"rangeLevel":1,"magicLevel":1},"4694":{"name":"Confusion beast","hitpoints":64,"combatLevel":63,"attackSpeed":4,"attackLevel":52,"strengthLevel":55,"defenceLevel":52,"rangeLevel":1,"magicLevel":1},"4695":{"name":"Hopeless creature","hitpoints":71,"combatLevel":71,"attackSpeed":4,"attackLevel":59,"strengthLevel":62,"defenceLevel":59,"rangeLevel":1,"magicLevel":1},"4708":{"name":"The Shaikahan","hitpoints":100,"combatLevel":83,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"rangeDef":10000,"magicDef":10000},"4709":{"name":"The Shaikahan","hitpoints":100,"combatLevel":83,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"rangeDef":10000,"magicDef":10000},"4742":{"name":"Black golem","hitpoints":80,"combatLevel":75,"attackSpeed":4,"attackLevel":80,"strengthLevel":30,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":300,"slashDef":300,"crushDef":1,"rangeDef":300,"magicDef":300},"4743":{"name":"White golem","hitpoints":80,"combatLevel":75,"attackSpeed":4,"attackLevel":80,"strengthLevel":30,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":300,"crushDef":300,"rangeDef":300,"magicDef":300},"4744":{"name":"Grey golem","hitpoints":80,"combatLevel":75,"attackSpeed":4,"attackLevel":80,"strengthLevel":30,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":300,"slashDef":1,"crushDef":300,"rangeDef":300,"magicDef":300},"4757":{"name":"Tarik","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"4758":{"name":"Poltenip","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"4759":{"name":"Radat","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":4,"bonusStrength":5},"4772":{"name":"Fortress Guard","hitpoints":22,"combatLevel":20,"attackSpeed":6,"attackLevel":18,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":14,"rangeDef":15,"bonusAttack":5,"bonusStrength":6},"4773":{"name":"Fortress Guard","hitpoints":22,"combatLevel":20,"attackSpeed":6,"attackLevel":18,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":14,"rangeDef":15,"bonusAttack":5,"bonusStrength":6},"4774":{"name":"Fortress Guard","hitpoints":22,"combatLevel":20,"attackSpeed":6,"attackLevel":18,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":14,"rangeDef":15,"bonusAttack":5,"bonusStrength":6},"4775":{"name":"Fortress Guard","hitpoints":22,"combatLevel":20,"attackSpeed":6,"attackLevel":18,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":14,"rangeDef":15,"bonusAttack":5,"bonusStrength":6},"4776":{"name":"Fortress Guard","hitpoints":22,"combatLevel":20,"attackSpeed":6,"attackLevel":18,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1,"stabDef":14,"slashDef":18,"crushDef":14,"rangeDef":15,"bonusAttack":5,"bonusStrength":6},"4797":{"name":"Slug Prince","hitpoints":70,"combatLevel":62,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":99,"magicDef":99},"4798":{"name":"Slug Prince","hitpoints":70,"combatLevel":62,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":99,"magicDef":99},"4799":{"name":"Giant lobster","hitpoints":50,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":15,"rangeDef":70,"magicDef":99},"4800":{"name":"Giant lobster","hitpoints":50,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":15,"rangeDef":70,"magicDef":99},"4805":{"name":"Hobgoblin","hitpoints":29,"combatLevel":28,"attackSpeed":4,"attackLevel":22,"strengthLevel":24,"defenceLevel":24,"rangeLevel":1,"magicLevel":1},"4813":{"name":"Icefiend","hitpoints":15,"combatLevel":13,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"4819":{"name":"Crab","hitpoints":19,"combatLevel":23,"attackSpeed":4,"attackLevel":19,"strengthLevel":20,"defenceLevel":26,"rangeLevel":1,"magicLevel":1},"4820":{"name":"Mudskipper","hitpoints":20,"combatLevel":30,"attackSpeed":4,"attackLevel":29,"strengthLevel":29,"defenceLevel":26,"rangeLevel":1,"magicLevel":1},"4821":{"name":"Mudskipper","hitpoints":20,"combatLevel":31,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":29,"rangeLevel":1,"magicLevel":1},"4822":{"name":"Crab","hitpoints":18,"combatLevel":21,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":22,"rangeLevel":1,"magicLevel":1},"4849":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4863":{"name":"Jubbly bird","hitpoints":21,"combatLevel":9},"4864":{"name":"Jubbly bird","hitpoints":21,"combatLevel":9},"4872":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4873":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4874":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4875":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4876":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4877":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4878":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4879":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"4880":{"name":"Agrith-Na-Na","hitpoints":200,"combatLevel":146,"attackSpeed":4,"attackLevel":83,"strengthLevel":150,"defenceLevel":82,"rangeLevel":100,"magicLevel":100,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":100,"magicDef":100,"bonusAttack":100},"4881":{"name":"Flambeed","hitpoints":210,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":100},"4882":{"name":"Karamel","hitpoints":250,"combatLevel":136,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":100,"magicLevel":1,"range":134,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":150,"magicDef":150,"bonusRangeStrength":55},"4883":{"name":"Dessourt","hitpoints":130,"combatLevel":121,"attackSpeed":3,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"4884":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"4885":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"4886":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"4887":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"4888":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"4889":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"4919":{"name":"Grip","hitpoints":25,"combatLevel":22,"attackSpeed":4,"attackLevel":18,"strengthLevel":17,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":16,"slashDef":27,"crushDef":22,"bonusAttack":5,"bonusStrength":7},"4922":{"name":"Ice Queen","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":94,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":40,"crushDef":20,"rangeDef":30,"magicDef":10},"4926":{"name":"Pirate Guard","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":18,"strengthLevel":16,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":39,"crushDef":30,"bonusAttack":20,"bonusStrength":16},"4927":{"name":"Entrana firebird","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"4930":{"name":"Solus Dellagar","hitpoints":40,"attackSpeed":3},"4933":{"name":"Solus Dellagar","hitpoints":40,"attackSpeed":3},"4934":{"name":"Black Knight","hitpoints":42,"combatLevel":32,"attackSpeed":5},"4959":{"name":"Black Knight","hitpoints":42,"combatLevel":32,"attackSpeed":5},"4960":{"name":"Black Knight","hitpoints":42,"combatLevel":32,"attackSpeed":5},"4962":{"name":"Solus Dellagar","hitpoints":40,"attackSpeed":3},"4969":{"name":"Khazard trooper","hitpoints":22,"combatLevel":19,"attackSpeed":5,"attackLevel":17,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":22,"bonusAttack":9,"bonusStrength":5},"4970":{"name":"Khazard trooper","hitpoints":22,"combatLevel":19,"attackSpeed":5,"attackLevel":17,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":22,"bonusAttack":9,"bonusStrength":5},"4972":{"name":"Khazard commander","hitpoints":22,"combatLevel":48,"attackSpeed":4,"attackLevel":50,"strengthLevel":45,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"4973":{"name":"Gnome troop","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":5,"rangeDef":4,"magicDef":2,"bonusAttack":2,"bonusStrength":3,"bonusRangeStrength":2},"4974":{"name":"Gnome troop","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":5,"rangeDef":4,"magicDef":2,"bonusAttack":2,"bonusStrength":3,"bonusRangeStrength":2},"4987":{"name":"Chronozon","hitpoints":60,"combatLevel":170,"attackSpeed":4,"attackLevel":173,"strengthLevel":172,"defenceLevel":173,"rangeLevel":1,"magicLevel":1},"5007":{"name":"Imp","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5043":{"name":"Suit of armour","hitpoints":29,"combatLevel":19,"attackSpeed":5,"attackLevel":16,"strengthLevel":14,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":46,"slashDef":50,"crushDef":45,"rangeDef":45,"bonusAttack":8,"bonusStrength":10},"5054":{"name":"Skeleton Hellhound","hitpoints":55,"combatLevel":97,"attackSpeed":4,"attackLevel":70,"strengthLevel":110,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"5079":{"name":"Delrith","hitpoints":10,"combatLevel":27,"attackSpeed":6,"attackLevel":11,"strengthLevel":10,"defenceLevel":5,"rangeLevel":1,"magicLevel":1,"slashDef":2},"5086":{"name":"Dark wizard","hitpoints":12,"combatLevel":7,"attackSpeed":4,"attackLevel":5,"strengthLevel":2,"defenceLevel":5,"rangeLevel":1,"magicLevel":6,"magicDef":3},"5087":{"name":"Dark wizard","hitpoints":12,"combatLevel":7,"attackSpeed":4,"attackLevel":5,"strengthLevel":2,"defenceLevel":5,"rangeLevel":1,"magicLevel":6,"magicDef":3},"5088":{"name":"Dark wizard","hitpoints":24,"combatLevel":20,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":14,"rangeLevel":1,"magicLevel":22,"magicDef":3},"5089":{"name":"Dark wizard","hitpoints":24,"combatLevel":20,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":14,"rangeLevel":1,"magicLevel":22,"magicDef":3},"5126":{"name":"Experiment No.2","hitpoints":95,"combatLevel":109,"attackSpeed":4,"attackLevel":104,"strengthLevel":92,"defenceLevel":90,"rangeLevel":100,"magicLevel":1,"stabDef":45,"slashDef":70,"crushDef":60,"rangeDef":55,"magicDef":60},"5127":{"name":"Mouse","hitpoints":70,"combatLevel":95,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":86,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":55,"crushDef":65,"rangeDef":60,"magicDef":50},"5128":{"name":"Mouse","hitpoints":70,"combatLevel":95,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":86,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":55,"crushDef":65,"rangeDef":60,"magicDef":50},"5129":{"name":"Glod","hitpoints":160,"combatLevel":138,"attackSpeed":4,"attackLevel":115,"strengthLevel":120,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":105,"slashDef":110,"crushDef":130,"rangeDef":100,"magicDef":125},"5139":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5142":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5143":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5144":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5145":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5146":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5157":{"name":"H.A.M. Archer","hitpoints":35,"combatLevel":30,"attackSpeed":10,"defenceLevel":30,"rangeLevel":30,"stabDef":5,"slashDef":5,"rangeDef":5},"5158":{"name":"H.A.M. Mage","hitpoints":35,"combatLevel":30,"attackSpeed":6,"defenceLevel":30,"rangeLevel":1,"magicLevel":30,"magic":8,"stabDef":5,"slashDef":5,"rangeDef":5},"5185":{"name":"Guard","hitpoints":16,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"5186":{"name":"Guard","hitpoints":16,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"5187":{"name":"Guard","hitpoints":16,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"5188":{"name":"Guard","hitpoints":16,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"5189":{"name":"Guard","hitpoints":16,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":9,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":4,"rangeDef":3,"magicDef":2,"bonusAttack":5,"bonusStrength":7},"5192":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5193":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5194":{"name":"Baby green dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"5195":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5196":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5197":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5198":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5199":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5200":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5201":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5202":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5203":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5204":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5205":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5206":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5207":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5208":{"name":"Goblin","hitpoints":5,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5211":{"name":"Weaponsmaster","hitpoints":20,"combatLevel":23,"attackSpeed":5,"attackLevel":21,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":21,"slashDef":23,"crushDef":21,"rangeDef":20,"bonusAttack":8,"bonusStrength":10},"5213":{"name":"Jonny the beard","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5217":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5218":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5219":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5220":{"name":"Thief","hitpoints":17,"combatLevel":16,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5223":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5237":{"name":"Skeleton","hitpoints":110,"combatLevel":142,"attackSpeed":4,"attackLevel":180,"strengthLevel":90,"defenceLevel":110,"rangeLevel":1,"magicLevel":110,"bonusAttack":50},"5238":{"name":"Spider","hitpoints":2,"combatLevel":1,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"slashDef":1,"magicDef":1,"bonusAttack":1,"bonusStrength":1},"5239":{"name":"Spider","hitpoints":2,"combatLevel":1,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"slashDef":1,"magicDef":1,"bonusAttack":1,"bonusStrength":1},"5240":{"name":"Bird","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":10},"5241":{"name":"Bird","hitpoints":5,"combatLevel":5,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":5},"5242":{"name":"Scorpion","hitpoints":15,"combatLevel":38,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":10,"rangeLevel":1,"magicLevel":10,"bonusAttack":100,"bonusStrength":100},"5243":{"name":"Jungle spider","hitpoints":35,"combatLevel":37,"attackSpeed":4,"attackLevel":50,"strengthLevel":30,"defenceLevel":10,"rangeLevel":1,"magicLevel":10,"bonusAttack":100,"bonusStrength":100},"5244":{"name":"Snake","hitpoints":36,"combatLevel":24,"attackSpeed":4,"attackLevel":15,"strengthLevel":25,"defenceLevel":10,"rangeLevel":1,"magicLevel":10,"bonusAttack":100,"bonusStrength":100},"5259":{"name":"Oipuis","combatLevel":149},"5260":{"name":"Uyoro","combatLevel":149},"5261":{"name":"Ouhai","combatLevel":149},"5262":{"name":"Uodai","combatLevel":149},"5263":{"name":"Padulah","hitpoints":130,"combatLevel":149,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":130,"bonusAttack":50},"5271":{"name":"Monkey Guard","hitpoints":130,"combatLevel":149,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":130},"5272":{"name":"Monkey Archer","hitpoints":50,"combatLevel":86,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":110,"magicLevel":1},"5273":{"name":"Monkey Archer","hitpoints":50,"combatLevel":86,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":110,"magicLevel":1},"5274":{"name":"Monkey Archer","hitpoints":50,"combatLevel":86,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":110,"magicLevel":1},"5275":{"name":"Monkey Guard","hitpoints":130,"combatLevel":167,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":200,"rangeLevel":1,"magicLevel":130,"bonusAttack":50},"5276":{"name":"Monkey Guard","hitpoints":130,"combatLevel":167,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":200,"rangeLevel":1,"magicLevel":130,"bonusAttack":50},"5281":{"name":"Monkey Zombie","hitpoints":60,"combatLevel":98,"attackSpeed":4,"attackLevel":150,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":60,"bonusAttack":35},"5282":{"name":"Monkey Zombie","hitpoints":90,"combatLevel":129,"attackSpeed":4,"attackLevel":150,"strengthLevel":110,"defenceLevel":90,"rangeLevel":1,"magicLevel":90,"bonusAttack":35},"5283":{"name":"Monkey Zombie","hitpoints":60,"combatLevel":82,"attackSpeed":4,"attackLevel":100,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":60,"bonusAttack":15},"5293":{"name":"Elf Warrior","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5294":{"name":"Elf Warrior","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5295":{"name":"Elf Archer","hitpoints":105,"combatLevel":90,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":80,"rangeLevel":90,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5296":{"name":"Elf Archer","hitpoints":105,"combatLevel":90,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":80,"rangeLevel":90,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5329":{"name":"Sigmund","hitpoints":70,"combatLevel":64,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"magic":8,"poisonImmune":true,"venomImmune":true},"5330":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5331":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5332":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5333":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5334":{"name":"Cave goblin guard","hitpoints":26,"combatLevel":26,"attackSpeed":6,"attackLevel":25,"strengthLevel":18,"defenceLevel":25,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":16,"crushDef":19,"rangeDef":12},"5335":{"name":"Cave goblin guard","hitpoints":26,"combatLevel":24,"attackSpeed":6,"attackLevel":22,"strengthLevel":16,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"slashDef":15,"crushDef":19,"rangeDef":12},"5336":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5337":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5338":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5339":{"name":"Cave goblin miner","hitpoints":10,"combatLevel":11,"attackSpeed":4,"attackLevel":10,"strengthLevel":12,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5342":{"name":"Undead one","hitpoints":47,"combatLevel":68,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":63},"5343":{"name":"Undead one","hitpoints":47,"combatLevel":68,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":63},"5344":{"name":"Undead one","hitpoints":47,"combatLevel":61,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":55,"stabDef":5,"slashDef":5,"crushDef":4,"rangeDef":4,"magicDef":1,"bonusAttack":7,"bonusStrength":5},"5345":{"name":"Undead one","hitpoints":47,"combatLevel":61,"attackSpeed":4,"attackLevel":55,"strengthLevel":55,"defenceLevel":55,"stabDef":5,"slashDef":5,"crushDef":4,"rangeDef":4,"magicDef":1,"bonusAttack":7,"bonusStrength":5},"5346":{"name":"Undead one","hitpoints":47,"combatLevel":68,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":63},"5347":{"name":"Undead one","hitpoints":47,"combatLevel":68,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":63},"5348":{"name":"Undead one","hitpoints":47,"combatLevel":68,"attackSpeed":4,"attackLevel":63,"strengthLevel":63,"defenceLevel":63},"5349":{"name":"Undead one","hitpoints":59,"combatLevel":73,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"bonusAttack":4,"bonusStrength":5},"5350":{"name":"Undead one","hitpoints":59,"combatLevel":73,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"bonusAttack":4,"bonusStrength":5},"5351":{"name":"Undead one","hitpoints":59,"combatLevel":73,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"bonusAttack":4,"bonusStrength":5},"5353":{"name":"Nazastarool","hitpoints":70,"combatLevel":91,"attackSpeed":4,"attackLevel":85,"strengthLevel":80,"defenceLevel":80},"5354":{"name":"Nazastarool","hitpoints":70,"combatLevel":68,"attackSpeed":4,"attackLevel":58,"strengthLevel":54,"defenceLevel":58,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"magicDef":5,"bonusAttack":15,"bonusStrength":15},"5355":{"name":"Nazastarool","hitpoints":80,"combatLevel":93,"attackSpeed":4,"attackLevel":85,"strengthLevel":80,"defenceLevel":80},"5369":{"name":"Goblin guard","hitpoints":43,"combatLevel":42,"attackSpeed":4,"attackLevel":32,"strengthLevel":37,"defenceLevel":37,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":6,"bonusAttack":8,"bonusStrength":5},"5370":{"name":"Ghost","hitpoints":20,"combatLevel":24,"attackSpeed":4,"attackLevel":23,"strengthLevel":23,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"5372":{"name":"Grave scorpion","hitpoints":7,"combatLevel":12,"attackSpeed":4,"attackLevel":11,"strengthLevel":12,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":3},"5373":{"name":"Poison spider","hitpoints":25,"combatLevel":31,"attackSpeed":4,"attackLevel":28,"strengthLevel":28,"defenceLevel":28,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":17,"crushDef":10,"rangeDef":14,"magicDef":14},"5418":{"name":"Guard","hitpoints":22,"combatLevel":20,"attackSpeed":5,"attackLevel":17,"strengthLevel":18,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":9,"bonusStrength":7},"5420":{"name":"Watchman","hitpoints":22,"combatLevel":33,"attackSpeed":6,"attackLevel":31,"strengthLevel":31,"defenceLevel":31,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16},"5421":{"name":"Soldier","hitpoints":22,"combatLevel":28,"attackSpeed":5,"attackLevel":26,"strengthLevel":25,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16,"bonusAttack":8,"bonusStrength":10},"5457":{"name":"Shipyard worker","hitpoints":10,"combatLevel":11,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"bonusAttack":6,"bonusStrength":6},"5512":{"name":"Gardener","hitpoints":7,"combatLevel":4,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":7,"rangeLevel":1,"magicLevel":1},"5534":{"name":"Whirlpool","slayerLevel":87,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"rangeDef":300,"magicDef":130},"5535":{"name":"Enormous Tentacle","hitpoints":120,"combatLevel":112,"slayerLevel":87,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":150,"magicLevel":1,"rangeDef":270},"5563":{"name":"Angry barbarian spirit","hitpoints":190,"combatLevel":166,"attackSpeed":4,"attackLevel":150,"strengthLevel":140,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"5564":{"name":"Enraged barbarian spirit","hitpoints":190,"combatLevel":166,"attackSpeed":4,"attackLevel":150,"strengthLevel":140,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"5565":{"name":"Berserk barbarian spirit","hitpoints":190,"combatLevel":166,"attackSpeed":4,"attackLevel":150,"strengthLevel":140,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"rangeDef":2,"bonusAttack":9,"bonusStrength":15},"5566":{"name":"Ferocious barbarian spirit","hitpoints":190,"combatLevel":166,"attackSpeed":4,"attackLevel":150,"strengthLevel":140,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"slashDef":73,"crushDef":72,"rangeDef":72,"bonusAttack":9,"bonusStrength":15},"5615":{"name":"Swamp snake","hitpoints":120,"combatLevel":80,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":70,"rangeLevel":30,"magicLevel":30},"5616":{"name":"Swamp snake","hitpoints":125,"combatLevel":109,"attackSpeed":4,"attackLevel":70,"strengthLevel":110,"defenceLevel":80,"rangeLevel":30,"magicLevel":30},"5617":{"name":"Swamp snake","hitpoints":130,"combatLevel":139,"attackSpeed":4,"attackLevel":90,"strengthLevel":170,"defenceLevel":90,"rangeLevel":30,"magicLevel":30},"5625":{"name":"Ghast","hitpoints":115,"combatLevel":79,"attackSpeed":8,"attackLevel":80,"strengthLevel":30,"defenceLevel":60,"rangeLevel":30,"magicLevel":30},"5626":{"name":"Ghast","hitpoints":135,"combatLevel":109,"attackSpeed":8,"attackLevel":110,"strengthLevel":70,"defenceLevel":70,"rangeLevel":30,"magicLevel":30},"5627":{"name":"Ghast","hitpoints":160,"combatLevel":139,"attackSpeed":8,"attackLevel":135,"strengthLevel":110,"defenceLevel":80,"rangeLevel":30,"magicLevel":30},"5628":{"name":"Giant snail","hitpoints":125,"combatLevel":80,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":60,"rangeLevel":70,"magicLevel":30},"5629":{"name":"Giant snail","hitpoints":150,"combatLevel":109,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":75,"rangeLevel":110,"magicLevel":30},"5630":{"name":"Giant snail","hitpoints":160,"combatLevel":139,"attackSpeed":4,"attackLevel":30,"strengthLevel":30,"defenceLevel":86,"rangeLevel":160,"magicLevel":30},"5631":{"name":"Riyl shadow","hitpoints":76,"combatLevel":80,"attackSpeed":4,"attackLevel":87,"strengthLevel":56,"defenceLevel":60,"rangeLevel":50,"magicLevel":50},"5632":{"name":"Asyn shadow","hitpoints":94,"combatLevel":110,"attackSpeed":4,"attackLevel":110,"strengthLevel":96,"defenceLevel":80,"rangeLevel":50,"magicLevel":50},"5633":{"name":"Shade","hitpoints":115,"combatLevel":140,"attackSpeed":4,"attackLevel":130,"strengthLevel":136,"defenceLevel":100,"rangeLevel":50,"magicLevel":50},"5634":{"name":"Vampyre Juvinate","hitpoints":50,"combatLevel":59,"attackSpeed":4,"attackLevel":95,"strengthLevel":16,"defenceLevel":45,"rangeLevel":50,"magicLevel":50},"5635":{"name":"Vampyre Juvinate","hitpoints":100,"combatLevel":90,"attackSpeed":4,"attackLevel":113,"strengthLevel":46,"defenceLevel":55,"rangeLevel":50,"magicLevel":50},"5636":{"name":"Vampyre Juvinate","hitpoints":150,"combatLevel":119,"attackSpeed":4,"attackLevel":127,"strengthLevel":76,"defenceLevel":65,"rangeLevel":50,"magicLevel":50},"5637":{"name":"Vampyre Juvinate","hitpoints":50,"combatLevel":59,"attackSpeed":4,"attackLevel":95,"strengthLevel":16,"defenceLevel":45,"rangeLevel":50,"magicLevel":50},"5638":{"name":"Vampyre Juvinate","hitpoints":100,"combatLevel":90,"attackSpeed":4,"attackLevel":113,"strengthLevel":46,"defenceLevel":55,"rangeLevel":50,"magicLevel":50},"5639":{"name":"Vampyre Juvinate","hitpoints":150,"combatLevel":119,"attackSpeed":4,"attackLevel":127,"strengthLevel":76,"defenceLevel":65,"rangeLevel":50,"magicLevel":50},"5640":{"name":"Feral Vampyre","hitpoints":75,"combatLevel":70,"attackSpeed":4,"attackLevel":90,"strengthLevel":46,"defenceLevel":30},"5641":{"name":"Feral Vampyre","hitpoints":135,"combatLevel":100,"attackSpeed":4,"attackLevel":105,"strengthLevel":76,"defenceLevel":30},"5642":{"name":"Feral Vampyre","hitpoints":185,"combatLevel":130,"attackSpeed":4,"attackLevel":120,"strengthLevel":116,"defenceLevel":30},"5643":{"name":"Tentacle","hitpoints":75,"combatLevel":99,"attackSpeed":6,"attackLevel":115,"strengthLevel":76,"defenceLevel":75,"rangeLevel":115,"magicLevel":50},"5644":{"name":"Head","hitpoints":150,"combatLevel":140,"attackSpeed":10,"attackLevel":73,"strengthLevel":186,"defenceLevel":75,"rangeLevel":50,"magicLevel":50},"5645":{"name":"Head","hitpoints":150,"combatLevel":140,"attackSpeed":10,"attackLevel":73,"strengthLevel":186,"defenceLevel":75,"rangeLevel":50,"magicLevel":50},"5646":{"name":"Tentacle","hitpoints":75,"combatLevel":99,"attackSpeed":6,"attackLevel":115,"strengthLevel":76,"defenceLevel":75,"rangeLevel":115,"magicLevel":50},"5647":{"name":"Zombie","hitpoints":20,"combatLevel":23,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"5648":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5649":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5650":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5651":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5652":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5653":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5654":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5655":{"name":"Undead Lumberjack","hitpoints":10,"combatLevel":30,"attackSpeed":4,"attackLevel":73,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"5656":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5657":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5658":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5659":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5660":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5661":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5662":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5663":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":35,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5665":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5666":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5667":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5668":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5669":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5670":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5671":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5672":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":40,"attackSpeed":4,"attackLevel":101,"strengthLevel":6,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"5673":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5674":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5675":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5676":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5677":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5678":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5679":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5680":{"name":"Undead Lumberjack","hitpoints":13,"combatLevel":45,"attackSpeed":4,"attackLevel":105,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"5681":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5682":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5683":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5684":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5685":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5686":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5687":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5688":{"name":"Undead Lumberjack","hitpoints":14,"combatLevel":50,"attackSpeed":4,"attackLevel":117,"strengthLevel":16,"defenceLevel":14,"rangeLevel":1,"magicLevel":1},"5689":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5690":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5691":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5692":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5693":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5694":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5695":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5696":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":55,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5697":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5698":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5699":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5700":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5701":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5702":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5703":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5704":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":60,"attackSpeed":4,"attackLevel":137,"strengthLevel":26,"defenceLevel":16,"rangeLevel":1,"magicLevel":1},"5705":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5706":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5707":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5708":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5709":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5710":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5711":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5712":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":64,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5713":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5714":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5715":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5716":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5717":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5718":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5719":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5720":{"name":"Undead Lumberjack","hitpoints":12,"combatLevel":70,"attackSpeed":4,"rangeLevel":1,"magicLevel":1},"5729":{"name":"Shipyard worker","hitpoints":10,"combatLevel":11,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"bonusAttack":6,"bonusStrength":6},"5732":{"name":"Market Guard","hitpoints":22,"combatLevel":20,"attackSpeed":5,"attackLevel":17,"strengthLevel":18,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16,"magicDef":4,"bonusAttack":9,"bonusStrength":7},"5739":{"name":"Penance Fighter","hitpoints":28,"combatLevel":30,"attackSpeed":4,"defenceLevel":25},"5740":{"name":"Penance Fighter","hitpoints":29,"combatLevel":32,"attackSpeed":4,"defenceLevel":27},"5741":{"name":"Penance Fighter","hitpoints":32,"combatLevel":37,"attackSpeed":4,"defenceLevel":34},"5742":{"name":"Penance Fighter","hitpoints":37,"combatLevel":42,"attackSpeed":4,"defenceLevel":37},"5743":{"name":"Penance Fighter","hitpoints":38,"combatLevel":47,"attackSpeed":4,"defenceLevel":44},"5744":{"name":"Penance Fighter","hitpoints":49,"combatLevel":56,"attackSpeed":4,"defenceLevel":48},"5745":{"name":"Penance Fighter","hitpoints":50,"combatLevel":61,"attackSpeed":4,"defenceLevel":52},"5746":{"name":"Penance Fighter","hitpoints":55,"combatLevel":68,"attackSpeed":4,"defenceLevel":62},"5747":{"name":"Penance Fighter","hitpoints":56,"combatLevel":77,"attackSpeed":4,"defenceLevel":66},"5757":{"name":"Penance Ranger","hitpoints":20,"combatLevel":21,"attackSpeed":4,"defenceLevel":21},"5758":{"name":"Penance Ranger","hitpoints":29,"combatLevel":25,"attackSpeed":4,"defenceLevel":29},"5759":{"name":"Penance Ranger","hitpoints":32,"combatLevel":32,"attackSpeed":4,"defenceLevel":33},"5760":{"name":"Penance Ranger","hitpoints":34,"combatLevel":38,"attackSpeed":4,"defenceLevel":42},"5761":{"name":"Penance Ranger","hitpoints":41,"combatLevel":43,"attackSpeed":4,"defenceLevel":46},"5762":{"name":"Penance Ranger","hitpoints":50,"combatLevel":51,"attackSpeed":4,"defenceLevel":54},"5763":{"name":"Penance Ranger","hitpoints":50,"combatLevel":57,"attackSpeed":4,"defenceLevel":61},"5764":{"name":"Penance Ranger","hitpoints":55,"combatLevel":64,"attackSpeed":4,"defenceLevel":68},"5765":{"name":"Penance Ranger","hitpoints":58,"combatLevel":72,"attackSpeed":4,"defenceLevel":80},"5775":{"name":"Penance Queen","hitpoints":250,"combatLevel":209,"attackSpeed":4,"attackLevel":260,"strengthLevel":92,"defenceLevel":132,"rangeLevel":116,"magicLevel":1},"5776":{"name":"Queen spawn","hitpoints":45,"combatLevel":63,"attackSpeed":4,"attackLevel":60,"strengthLevel":62,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"venomImmune":true},"5779":{"name":"Giant Mole","hitpoints":200,"combatLevel":230,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":200,"stabDef":60,"slashDef":80,"crushDef":100,"rangeDef":60,"magicDef":80},"5816":{"name":"Yak","hitpoints":50,"combatLevel":22,"attackSpeed":4,"attackLevel":20,"strengthLevel":10,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"5822":{"name":"Ice Troll King","hitpoints":150,"combatLevel":122,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":45,"rangeDef":2000,"magicDef":2000,"bonusAttack":60,"bonusStrength":60},"5823":{"name":"Ice troll runt","hitpoints":60,"combatLevel":74,"attackSpeed":4,"attackLevel":60,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5824":{"name":"Ice troll male","hitpoints":80,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5825":{"name":"Ice troll female","hitpoints":80,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5826":{"name":"Ice troll grunt","hitpoints":80,"combatLevel":102,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5828":{"name":"Ice troll runt","hitpoints":60,"combatLevel":74,"attackSpeed":4,"attackLevel":60,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5829":{"name":"Ice troll male","hitpoints":80,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5830":{"name":"Ice troll female","hitpoints":80,"combatLevel":82,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5831":{"name":"Ice troll grunt","hitpoints":80,"combatLevel":102,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":60,"crushDef":30,"bonusAttack":60,"bonusStrength":60},"5842":{"name":"Cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"5848":{"name":"Tanglefoot","hitpoints":102,"combatLevel":111,"attackSpeed":4,"attackLevel":97,"strengthLevel":97,"defenceLevel":91,"rangeLevel":1,"magicLevel":1},"5853":{"name":"Baby tanglefoot","hitpoints":40,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":38,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"5854":{"name":"Baby tanglefoot","hitpoints":40,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":38,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"5862":{"name":"Cerberus","hitpoints":600,"combatLevel":318,"slayerLevel":91,"attackSpeed":6,"attackLevel":220,"strengthLevel":220,"defenceLevel":100,"rangeLevel":220,"magicLevel":220,"range":50,"magic":50,"stabDef":50,"slashDef":100,"crushDef":25,"rangeDef":100,"magicDef":100,"bonusAttack":50,"poisonImmune":true,"venomImmune":true},"5863":{"name":"Cerberus","hitpoints":600,"combatLevel":318,"slayerLevel":91,"attackSpeed":6,"attackLevel":220,"strengthLevel":220,"defenceLevel":100,"rangeLevel":220,"magicLevel":220,"range":50,"magic":50,"stabDef":50,"slashDef":100,"crushDef":25,"rangeDef":100,"magicDef":100,"bonusAttack":50,"poisonImmune":true,"venomImmune":true},"5866":{"name":"Cerberus","hitpoints":600,"combatLevel":318,"slayerLevel":91,"attackSpeed":6,"attackLevel":220,"strengthLevel":220,"defenceLevel":100,"rangeLevel":220,"magicLevel":220,"range":50,"magic":50,"stabDef":50,"slashDef":100,"crushDef":25,"rangeDef":100,"magicDef":100,"bonusAttack":50,"poisonImmune":true,"venomImmune":true},"5872":{"name":"Baby green dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"5873":{"name":"Baby green dragon","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"5874":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"5875":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"5876":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"5877":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"5878":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5879":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5880":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5881":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5882":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"5886":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5887":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5888":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5889":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5890":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5891":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5908":{"name":"Abyssal Sire","hitpoints":400,"combatLevel":350,"slayerLevel":85,"attackSpeed":7,"attackLevel":180,"strengthLevel":136,"defenceLevel":250,"rangeLevel":1,"magicLevel":200,"stabDef":40,"slashDef":60,"crushDef":50,"rangeDef":60,"magicDef":20,"bonusAttack":65},"5914":{"name":"Respiratory system","hitpoints":50,"defenceLevel":80},"5916":{"name":"Spawn","hitpoints":15,"combatLevel":60,"attackSpeed":4,"attackLevel":120,"strengthLevel":30,"defenceLevel":30,"rangeLevel":50,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"5917":{"name":"Spawn","hitpoints":15,"combatLevel":60,"attackSpeed":4,"attackLevel":120,"strengthLevel":30,"defenceLevel":30,"rangeLevel":50,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"5918":{"name":"Scion","hitpoints":50,"combatLevel":100,"attackSpeed":4,"attackLevel":120,"strengthLevel":90,"defenceLevel":80,"rangeLevel":100,"magicLevel":1,"range":30,"bonusAttack":30,"bonusRangeStrength":30,"poisonImmune":true,"venomImmune":true},"5935":{"name":"Sand Crab","hitpoints":60,"combatLevel":15,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5936":{"name":"Sandy rocks","hitpoints":60,"combatLevel":15,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"5938":{"name":"Wallasalki","hitpoints":120,"combatLevel":98,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":80,"rangeLevel":1,"magicLevel":100,"stabDef":100,"slashDef":150,"crushDef":175,"magicDef":250},"5939":{"name":"Wallasalki","hitpoints":120,"combatLevel":98,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":80,"rangeLevel":1,"magicLevel":100,"stabDef":100,"slashDef":150,"crushDef":175,"magicDef":250},"5940":{"name":"Giant Rock Crab","hitpoints":180,"combatLevel":137,"attackSpeed":4,"attackLevel":50,"strengthLevel":80,"defenceLevel":200,"rangeLevel":1,"magicLevel":1,"stabDef":225,"slashDef":200,"crushDef":175,"rangeDef":250},"5942":{"name":"Dagannoth","hitpoints":85,"combatLevel":88,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":75,"rangeLevel":100,"magicLevel":1,"slashDef":10,"crushDef":90,"rangeDef":150,"magicDef":200},"5943":{"name":"Dagannoth","hitpoints":95,"combatLevel":90,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50,"bonusAttack":50,"bonusStrength":50},"5944":{"name":"Rock lobster","hitpoints":150,"combatLevel":127,"attackSpeed":2,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":50,"rangeDef":150,"magicDef":50},"5946":{"name":"Suspicious water","hitpoints":100,"combatLevel":34},"5947":{"name":"Spinolyp","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":100,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":50,"magicDef":50},"5948":{"name":"Suspicious water","hitpoints":100,"combatLevel":34},"5961":{"name":"Spinolyp","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":100,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":50,"magicDef":50},"5962":{"name":"Suspicious water","hitpoints":100,"combatLevel":34},"5963":{"name":"Spinolyp","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":100,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":50,"magicDef":50},"5964":{"name":"Khazard trooper","hitpoints":22,"combatLevel":19,"attackSpeed":5,"attackLevel":17,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":22,"bonusAttack":9,"bonusStrength":5},"5965":{"name":"Khazard trooper","hitpoints":22,"combatLevel":19,"attackSpeed":5,"attackLevel":17,"strengthLevel":16,"defenceLevel":13,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":22,"bonusAttack":9,"bonusStrength":5},"5966":{"name":"Gnome troop","hitpoints":3,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":5,"rangeDef":4,"magicDef":2,"bonusAttack":2,"bonusStrength":3,"bonusRangeStrength":2},"5967":{"name":"Gnome troop","hitpoints":3,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1,"stabDef":3,"slashDef":4,"crushDef":5,"rangeDef":4,"magicDef":2,"bonusAttack":2,"bonusStrength":3,"bonusRangeStrength":2},"5968":{"name":"Gnome","hitpoints":3,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1,"bonusRangeStrength":2},"5969":{"name":"Gnome","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"bonusRangeStrength":2},"5970":{"name":"Gnome","hitpoints":3,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1,"bonusRangeStrength":2},"5971":{"name":"Mounted terrorbird gnome","hitpoints":55,"combatLevel":49,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":16,"slashDef":16,"crushDef":18,"rangeDef":10,"magicDef":15,"bonusRangeStrength":2},"5972":{"name":"Mounted terrorbird gnome","hitpoints":55,"combatLevel":49,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":16,"slashDef":16,"crushDef":18,"rangeDef":10,"magicDef":15,"bonusRangeStrength":2},"5973":{"name":"Mounted terrorbird gnome","hitpoints":55,"combatLevel":49,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":16,"slashDef":16,"crushDef":18,"rangeDef":10,"magicDef":15,"bonusRangeStrength":2},"5977":{"name":"Justiciar Zachariah","hitpoints":320,"combatLevel":348,"attackSpeed":6,"attackLevel":500,"strengthLevel":250,"defenceLevel":100,"rangeLevel":1,"magicLevel":180,"magic":80,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"bonusAttack":200,"bonusMagicDamage":80,"poisonImmune":true,"venomImmune":true},"6046":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"6047":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"6048":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"6049":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"6050":{"name":"Black Guard Berserker","hitpoints":50,"combatLevel":66,"attackSpeed":3,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"bonusAttack":55,"bonusStrength":55},"6051":{"name":"Black Guard Berserker","hitpoints":50,"combatLevel":66,"attackSpeed":3,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"bonusAttack":55,"bonusStrength":55},"6052":{"name":"Black Guard Berserker","hitpoints":50,"combatLevel":66,"attackSpeed":3,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"bonusAttack":55,"bonusStrength":55},"6056":{"name":"Guard","hitpoints":50,"combatLevel":37,"attackSpeed":6,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":40,"magicLevel":1,"stabDef":18,"slashDef":23,"crushDef":27,"rangeDef":19,"magicDef":10,"bonusAttack":19,"bonusStrength":8,"bonusRangeStrength":8},"6065":{"name":"Tower Archer","hitpoints":30,"combatLevel":19,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":20,"magicLevel":1,"rangeDef":12,"magicDef":6,"bonusRangeStrength":16},"6066":{"name":"Tower Archer","hitpoints":50,"combatLevel":34,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":40,"magicLevel":1,"rangeDef":40,"magicDef":24,"bonusRangeStrength":22},"6067":{"name":"Tower Archer","hitpoints":70,"combatLevel":49,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":60,"magicLevel":1,"rangeDef":45,"magicDef":20,"bonusRangeStrength":31},"6068":{"name":"Tower Archer","hitpoints":90,"combatLevel":64,"attackSpeed":6,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":80,"magicLevel":1,"rangeDef":66,"magicDef":34,"bonusRangeStrength":49},"6075":{"name":"Tortoise","hitpoints":100,"combatLevel":79,"attackSpeed":4,"attackLevel":15,"strengthLevel":90,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"6076":{"name":"Tortoise","hitpoints":120,"combatLevel":92,"attackSpeed":4,"attackLevel":30,"strengthLevel":100,"defenceLevel":80,"rangeLevel":50,"magicLevel":50},"6077":{"name":"Gnome child","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6078":{"name":"Gnome child","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6079":{"name":"Gnome child","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6081":{"name":"Gnome guard","hitpoints":31,"combatLevel":23,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"bonusAttack":8,"bonusStrength":13},"6082":{"name":"Gnome guard","hitpoints":31,"combatLevel":23,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"bonusAttack":8,"bonusStrength":13},"6086":{"name":"Gnome woman","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6087":{"name":"Gnome woman","hitpoints":2,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6094":{"name":"Gnome","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"bonusRangeStrength":2},"6095":{"name":"Gnome","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"bonusRangeStrength":2},"6096":{"name":"Gnome","hitpoints":3,"combatLevel":1,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"bonusRangeStrength":2},"6097":{"name":"Gnome Archer","hitpoints":10,"combatLevel":5,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":6,"magicLevel":1},"6098":{"name":"Gnome Driver","hitpoints":10,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":5,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"6099":{"name":"Gnome Mage","hitpoints":10,"combatLevel":5,"attackSpeed":6,"attackLevel":3,"strengthLevel":1,"defenceLevel":1,"rangeLevel":5,"magicLevel":1},"6102":{"name":"Bush snake","hitpoints":25,"combatLevel":35,"attackSpeed":3,"attackLevel":25,"strengthLevel":25,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"6103":{"name":"Bush snake","hitpoints":25,"combatLevel":35,"attackSpeed":3,"attackLevel":25,"strengthLevel":25,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"6118":{"name":"Elvarg (hard)","hitpoints":240,"combatLevel":214,"attackSpeed":4,"attackLevel":210,"strengthLevel":210,"defenceLevel":70,"rangeLevel":1,"magicLevel":210,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"6119":{"name":"The Inadequacy (hard)","hitpoints":255,"combatLevel":600,"attackSpeed":4,"attackLevel":1128,"strengthLevel":340,"defenceLevel":240,"rangeLevel":340,"magicLevel":1},"6120":{"name":"The Everlasting (hard)","hitpoints":255,"combatLevel":365,"attackSpeed":6,"attackLevel":374,"strengthLevel":462,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"6121":{"name":"The Untouchable (hard)","hitpoints":180,"combatLevel":440,"attackSpeed":6,"attackLevel":374,"strengthLevel":510,"defenceLevel":434,"rangeLevel":1,"magicLevel":1},"6177":{"name":"Scion","hitpoints":50,"combatLevel":100,"attackSpeed":4,"attackLevel":120,"strengthLevel":90,"defenceLevel":80,"rangeLevel":100,"magicLevel":1,"range":30,"bonusAttack":30,"bonusRangeStrength":30,"poisonImmune":true,"venomImmune":true},"6267":{"name":"Jungle spider","hitpoints":50,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":37,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":10,"rangeDef":20,"magicDef":17},"6271":{"name":"Jungle spider","hitpoints":50,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":37,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":10,"rangeDef":20,"magicDef":17},"6272":{"name":"Large mosquito","hitpoints":3,"combatLevel":13,"attackSpeed":4,"attackLevel":5,"strengthLevel":1,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":2,"crushDef":5,"magicDef":2},"6273":{"name":"Mosquito swarm","hitpoints":9,"combatLevel":17,"attackSpeed":2,"attackLevel":10,"strengthLevel":1,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":2,"crushDef":5,"magicDef":2},"6291":{"name":"Tanglefoot (hard)","hitpoints":204,"combatLevel":199,"attackSpeed":4,"attackLevel":194,"strengthLevel":194,"defenceLevel":91,"rangeLevel":1,"magicLevel":1},"6292":{"name":"Chronozon (hard)","hitpoints":120,"combatLevel":297,"attackSpeed":4,"attackLevel":346,"strengthLevel":344,"defenceLevel":173,"rangeLevel":1,"magicLevel":1},"6293":{"name":"Bouncer (hard)","hitpoints":232,"combatLevel":244,"attackSpeed":4,"attackLevel":240,"strengthLevel":240,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"6294":{"name":"Ice Troll King (hard)","hitpoints":255,"combatLevel":213,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":45,"rangeDef":2000,"magicDef":2000,"bonusAttack":60,"bonusStrength":60},"6295":{"name":"Black demon (hard)","hitpoints":255,"combatLevel":292,"attackSpeed":4,"attackLevel":290,"strengthLevel":296,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"6297":{"name":"Glod (hard)","hitpoints":255,"combatLevel":276,"attackSpeed":4,"attackLevel":230,"strengthLevel":240,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":105,"slashDef":110,"crushDef":130,"rangeDef":100,"magicDef":125},"6298":{"name":"Treus Dayth (hard)","hitpoints":240,"combatLevel":194,"attackSpeed":4,"attackLevel":168,"strengthLevel":168,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"6299":{"name":"Black Knight Titan (hard)","hitpoints":255,"combatLevel":210,"attackSpeed":7,"attackLevel":182,"strengthLevel":200,"defenceLevel":91,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":27,"crushDef":18,"rangeDef":1000,"magicDef":1000,"bonusAttack":27,"bonusStrength":22},"6300":{"name":"Dagannoth mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6301":{"name":"Dagannoth mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6302":{"name":"Dagannoth mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6303":{"name":"Dagannoth mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6304":{"name":"Dagannoth mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6305":{"name":"Dagannoth mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6306":{"name":"Evil Chicken (hard)","hitpoints":240,"combatLevel":286,"attackSpeed":4,"defenceLevel":126,"magicLevel":400},"6307":{"name":"Culinaromancer (hard)","hitpoints":255,"combatLevel":209,"attackSpeed":4,"attackLevel":40,"strengthLevel":400,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"6308":{"name":"Agrith-Na-Na (hard)","hitpoints":255,"combatLevel":235,"attackSpeed":4,"attackLevel":166,"strengthLevel":300,"defenceLevel":82,"rangeLevel":200,"magicLevel":200,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":100,"magicDef":100,"bonusAttack":100},"6309":{"name":"Flambeed (hard)","hitpoints":255,"combatLevel":238,"attackSpeed":4,"attackLevel":240,"strengthLevel":240,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":100},"6310":{"name":"Karamel (hard)","hitpoints":255,"combatLevel":186,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":200,"magicLevel":1,"range":268,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":150,"magicDef":150,"bonusRangeStrength":110},"6311":{"name":"Dessourt (hard)","hitpoints":255,"combatLevel":217,"attackSpeed":3,"attackLevel":198,"strengthLevel":198,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"6312":{"name":"Gelatinnoth Mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6313":{"name":"Gelatinnoth Mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6314":{"name":"Gelatinnoth Mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6315":{"name":"Gelatinnoth Mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6316":{"name":"Gelatinnoth Mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6317":{"name":"Gelatinnoth Mother (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":81,"rangeLevel":120,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6318":{"name":"Nezikchened (hard)","hitpoints":150,"combatLevel":295,"attackSpeed":5,"attackLevel":330,"strengthLevel":336,"defenceLevel":167,"rangeLevel":320,"magicLevel":320},"6319":{"name":"Tree spirit (hard)","hitpoints":187,"combatLevel":199,"attackSpeed":4,"attackLevel":200,"strengthLevel":209,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"6320":{"name":"Me (hard)","hitpoints":135,"combatLevel":201,"attackSpeed":4,"attackLevel":225,"strengthLevel":234,"defenceLevel":74,"rangeLevel":1,"magicLevel":1},"6321":{"name":"Jungle Demon (hard)","hitpoints":255,"combatLevel":327,"attackSpeed":6,"attackLevel":340,"strengthLevel":340,"defenceLevel":170,"rangeLevel":1,"magicLevel":340,"slashDef":50,"magicDef":50,"bonusAttack":50,"bonusStrength":50},"6322":{"name":"The Kendal (hard)","hitpoints":150,"combatLevel":210,"attackSpeed":4,"attackLevel":195,"strengthLevel":195,"defenceLevel":60,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"bonusAttack":5,"bonusStrength":10},"6323":{"name":"Giant Roc (hard)","hitpoints":255,"combatLevel":257,"attackSpeed":6,"attackLevel":260,"strengthLevel":260,"defenceLevel":100,"rangeLevel":260,"magicLevel":1,"magicDef":150},"6324":{"name":"Slagilith (hard)","hitpoints":150,"combatLevel":202,"attackSpeed":4,"attackLevel":150,"strengthLevel":300,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":10},"6325":{"name":"Moss Guardian (hard)","hitpoints":240,"combatLevel":182,"attackSpeed":6,"attackLevel":165,"strengthLevel":165,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"bonusAttack":66,"bonusStrength":62},"6326":{"name":"Skeleton Hellhound (hard)","hitpoints":132,"combatLevel":198,"attackSpeed":4,"attackLevel":168,"strengthLevel":264,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"6327":{"name":"Agrith Naar (hard)","hitpoints":209,"combatLevel":196,"attackSpeed":4,"attackLevel":182,"strengthLevel":198,"defenceLevel":82,"rangeLevel":220,"magicLevel":220},"6329":{"name":"Khazard warlord (hard)","hitpoints":255,"combatLevel":192,"attackSpeed":4,"attackLevel":165,"strengthLevel":170,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"6330":{"name":"Dad (hard)","hitpoints":240,"combatLevel":201,"attackSpeed":8,"attackLevel":132,"strengthLevel":264,"defenceLevel":50,"stabDef":25,"slashDef":25,"crushDef":40,"rangeDef":200,"magicDef":200,"bonusAttack":40,"bonusStrength":70},"6331":{"name":"Arrg (hard)","hitpoints":255,"combatLevel":210,"attackSpeed":4,"attackLevel":140,"strengthLevel":280,"defenceLevel":40,"rangeLevel":140,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"6332":{"name":"Count Draynor (hard)","hitpoints":210,"combatLevel":177,"attackSpeed":4,"attackLevel":198,"strengthLevel":165,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":1,"crushDef":3},"6333":{"name":"Witch's experiment (hard)","hitpoints":63,"combatLevel":47,"attackSpeed":4,"attackLevel":54,"strengthLevel":30,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"6334":{"name":"Witch's experiment (second form) (hard)","hitpoints":93,"combatLevel":77,"attackSpeed":4,"attackLevel":84,"strengthLevel":60,"defenceLevel":29,"rangeLevel":1,"magicLevel":1},"6335":{"name":"Witch's experiment (third form) (hard)","hitpoints":103,"combatLevel":90,"attackSpeed":4,"attackLevel":95,"strengthLevel":75,"defenceLevel":39,"rangeLevel":1,"magicLevel":1},"6336":{"name":"Witch's experiment (fourth form) (hard)","hitpoints":113,"combatLevel":103,"attackSpeed":4,"attackLevel":105,"strengthLevel":88,"defenceLevel":49,"rangeLevel":1,"magicLevel":1},"6337":{"name":"Nazastarool (hard)","hitpoints":154,"combatLevel":176,"attackSpeed":4,"attackLevel":187,"strengthLevel":176,"defenceLevel":80},"6338":{"name":"Nazastarool (hard)","hitpoints":180,"combatLevel":153,"attackSpeed":4,"attackLevel":150,"strengthLevel":140,"defenceLevel":58,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"magicDef":5,"bonusAttack":15,"bonusStrength":15},"6339":{"name":"Nazastarool (hard)","hitpoints":176,"combatLevel":181,"attackSpeed":4,"attackLevel":187,"strengthLevel":176,"defenceLevel":80},"6340":{"name":"Cow (hard)","hitpoints":160,"combatLevel":170,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6342":{"name":"Barrelchest","hitpoints":134,"combatLevel":190,"attackSpeed":5,"attackLevel":170,"strengthLevel":145,"defenceLevel":140,"rangeLevel":1,"magicLevel":90,"bonusAttack":80,"bonusStrength":80},"6343":{"name":"Giant scarab","hitpoints":130,"combatLevel":191,"attackSpeed":4,"attackLevel":169,"strengthLevel":190,"defenceLevel":169,"rangeLevel":190,"magicLevel":1,"stabDef":70,"slashDef":99,"crushDef":99,"rangeDef":149,"magicDef":159},"6344":{"name":"Dessous","hitpoints":200,"combatLevel":139,"attackSpeed":3,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"6345":{"name":"Kamil","hitpoints":130,"combatLevel":154,"attackSpeed":4,"attackLevel":190,"strengthLevel":80,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":60,"crushDef":35,"bonusAttack":60,"bonusStrength":100},"6346":{"name":"Damis","hitpoints":90,"combatLevel":103,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":80},"6347":{"name":"Damis","hitpoints":200,"combatLevel":174,"attackSpeed":3,"attackLevel":160,"strengthLevel":100,"defenceLevel":160,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":120,"magicDef":80,"bonusStrength":100},"6348":{"name":"Fareed","hitpoints":130,"combatLevel":167,"attackSpeed":4,"attackLevel":190,"strengthLevel":120,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":100,"slashDef":100,"crushDef":100,"bonusStrength":120},"6349":{"name":"Elvarg","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":70,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"6350":{"name":"The Inadequacy","hitpoints":180,"combatLevel":343,"attackSpeed":4,"attackLevel":564,"strengthLevel":170,"defenceLevel":240,"rangeLevel":170,"magicLevel":1},"6351":{"name":"The Everlasting","hitpoints":230,"combatLevel":223,"attackSpeed":6,"attackLevel":187,"strengthLevel":231,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"6352":{"name":"The Untouchable","hitpoints":90,"combatLevel":274,"attackSpeed":6,"attackLevel":187,"strengthLevel":255,"defenceLevel":434,"rangeLevel":1,"magicLevel":1},"6353":{"name":"Tanglefoot","hitpoints":102,"combatLevel":111,"attackSpeed":4,"attackLevel":97,"strengthLevel":97,"defenceLevel":91,"rangeLevel":1,"magicLevel":1},"6354":{"name":"Chronozon","hitpoints":60,"combatLevel":170,"attackSpeed":4,"attackLevel":173,"strengthLevel":172,"defenceLevel":173,"rangeLevel":1,"magicLevel":1},"6355":{"name":"Bouncer","hitpoints":116,"combatLevel":137,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"6356":{"name":"Ice Troll King","hitpoints":150,"combatLevel":122,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":45,"slashDef":45,"crushDef":45,"rangeDef":2000,"magicDef":2000,"bonusAttack":60,"bonusStrength":60},"6357":{"name":"Black demon","hitpoints":157,"combatLevel":172,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"6358":{"name":"Glod","hitpoints":160,"combatLevel":138,"attackSpeed":4,"attackLevel":115,"strengthLevel":120,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":105,"slashDef":110,"crushDef":130,"rangeDef":100,"magicDef":125},"6359":{"name":"Treus Dayth","hitpoints":100,"combatLevel":95,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"6360":{"name":"Black Knight Titan","hitpoints":142,"combatLevel":120,"attackSpeed":7,"attackLevel":91,"strengthLevel":100,"defenceLevel":91,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":27,"crushDef":18,"rangeDef":1000,"magicDef":1000,"bonusAttack":27,"bonusStrength":22},"6361":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6362":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6363":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6364":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6365":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6366":{"name":"Dagannoth mother","hitpoints":120,"combatLevel":100,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6367":{"name":"Evil Chicken","hitpoints":120,"combatLevel":159,"attackSpeed":4,"defenceLevel":126,"magicLevel":200},"6368":{"name":"Culinaromancer","hitpoints":150,"combatLevel":75,"attackSpeed":4,"attackLevel":10,"strengthLevel":100,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"6369":{"name":"Agrith-Na-Na","hitpoints":200,"combatLevel":146,"attackSpeed":4,"attackLevel":83,"strengthLevel":150,"defenceLevel":82,"rangeLevel":100,"magicLevel":100,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":100,"magicDef":100,"bonusAttack":100},"6370":{"name":"Flambeed","hitpoints":210,"combatLevel":149,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":100},"6371":{"name":"Karamel","hitpoints":250,"combatLevel":136,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":100,"magicLevel":1,"range":134,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":150,"magicDef":150,"bonusRangeStrength":55},"6372":{"name":"Dessourt","hitpoints":130,"combatLevel":121,"attackSpeed":3,"attackLevel":99,"strengthLevel":99,"defenceLevel":99,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":150,"crushDef":150,"bonusAttack":50,"bonusStrength":50},"6373":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6374":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6375":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6376":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6377":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6378":{"name":"Gelatinnoth Mother","hitpoints":240,"combatLevel":130,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":50,"magicDef":50},"6379":{"name":"Nezikchened","hitpoints":150,"combatLevel":187,"attackSpeed":5,"attackLevel":165,"strengthLevel":168,"defenceLevel":167,"rangeLevel":160,"magicLevel":160},"6380":{"name":"Tree spirit","hitpoints":85,"combatLevel":101,"attackSpeed":4,"attackLevel":90,"strengthLevel":95,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"6381":{"name":"Me","hitpoints":45,"combatLevel":79,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":74,"rangeLevel":1,"magicLevel":1},"6382":{"name":"Jungle Demon","hitpoints":170,"combatLevel":195,"attackSpeed":6,"attackLevel":170,"strengthLevel":170,"defenceLevel":170,"rangeLevel":1,"magicLevel":170,"slashDef":50,"magicDef":50,"bonusAttack":50,"bonusStrength":50},"6383":{"name":"The Kendal","hitpoints":50,"combatLevel":70,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":60,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":20,"bonusAttack":5,"bonusStrength":10},"6384":{"name":"Giant Roc","hitpoints":250,"combatLevel":172,"attackSpeed":6,"attackLevel":130,"strengthLevel":130,"defenceLevel":100,"rangeLevel":130,"magicLevel":1,"magicDef":150},"6385":{"name":"Slagilith","hitpoints":60,"combatLevel":92,"attackSpeed":4,"attackLevel":60,"strengthLevel":120,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":5,"rangeDef":50,"magicDef":5,"bonusAttack":10},"6386":{"name":"Moss Guardian","hitpoints":120,"combatLevel":84,"attackSpeed":6,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"bonusAttack":66,"bonusStrength":62},"6387":{"name":"Skeleton Hellhound","hitpoints":55,"combatLevel":97,"attackSpeed":4,"attackLevel":70,"strengthLevel":110,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"6388":{"name":"Agrith Naar","hitpoints":95,"combatLevel":100,"attackSpeed":4,"attackLevel":83,"strengthLevel":90,"defenceLevel":82,"rangeLevel":100,"magicLevel":100},"6390":{"name":"Khazard warlord","hitpoints":170,"combatLevel":112,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"6391":{"name":"Dad","hitpoints":120,"combatLevel":101,"attackSpeed":8,"attackLevel":60,"strengthLevel":120,"defenceLevel":50,"stabDef":25,"slashDef":25,"crushDef":40,"rangeDef":200,"magicDef":200,"bonusAttack":40,"bonusStrength":70},"6392":{"name":"Arrg","hitpoints":140,"combatLevel":113,"attackSpeed":4,"attackLevel":70,"strengthLevel":140,"defenceLevel":40,"rangeLevel":70,"stabDef":35,"slashDef":60,"crushDef":35,"rangeDef":200,"magicDef":200,"bonusAttack":60,"bonusStrength":100},"6393":{"name":"Count Draynor","hitpoints":35,"combatLevel":34,"attackSpeed":4,"attackLevel":30,"strengthLevel":25,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":1,"crushDef":3},"6394":{"name":"Witch's experiment","hitpoints":21,"combatLevel":19,"attackSpeed":4,"attackLevel":18,"strengthLevel":10,"defenceLevel":19,"rangeLevel":1,"magicLevel":1},"6395":{"name":"Witch's experiment (second form)","hitpoints":31,"combatLevel":30,"attackSpeed":4,"attackLevel":28,"strengthLevel":20,"defenceLevel":29,"rangeLevel":1,"magicLevel":1},"6396":{"name":"Witch's experiment (third form)","hitpoints":41,"combatLevel":42,"attackSpeed":4,"attackLevel":38,"strengthLevel":30,"defenceLevel":39,"rangeLevel":1,"magicLevel":1},"6397":{"name":"Witch's experiment (fourth form)","hitpoints":51,"combatLevel":53,"attackSpeed":4,"attackLevel":48,"strengthLevel":40,"defenceLevel":49,"rangeLevel":1,"magicLevel":1},"6398":{"name":"Nazastarool","hitpoints":70,"combatLevel":91,"attackSpeed":4,"attackLevel":85,"strengthLevel":80,"defenceLevel":80},"6399":{"name":"Nazastarool","hitpoints":70,"combatLevel":68,"attackSpeed":4,"attackLevel":58,"strengthLevel":54,"defenceLevel":58,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5,"magicDef":5,"bonusAttack":15,"bonusStrength":15},"6400":{"name":"Nazastarool","hitpoints":80,"combatLevel":93,"attackSpeed":4,"attackLevel":85,"strengthLevel":80,"defenceLevel":80},"6401":{"name":"Cow","hitpoints":8,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"6402":{"name":"Mosquito swarm","hitpoints":15,"combatLevel":20,"attackSpeed":2,"attackLevel":15,"strengthLevel":1,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":2,"slashDef":2,"crushDef":5,"magicDef":2},"6406":{"name":"Tribesman","hitpoints":39,"combatLevel":32,"attackSpeed":4,"attackLevel":23,"strengthLevel":27,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":6,"bonusAttack":8,"bonusStrength":5},"6407":{"name":"Tribesman","hitpoints":39,"combatLevel":32,"attackSpeed":4,"attackLevel":23,"strengthLevel":27,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":6,"crushDef":6,"bonusAttack":8,"bonusStrength":5},"6408":{"name":"Broodoo victim","hitpoints":100,"combatLevel":60,"attackSpeed":6,"defenceLevel":26,"rangeLevel":1,"magicLevel":60,"magic":25,"stabDef":20,"slashDef":20,"crushDef":20},"6409":{"name":"Broodoo victim","hitpoints":100,"combatLevel":60,"attackSpeed":6,"defenceLevel":26,"rangeLevel":1,"magicLevel":60,"magic":25,"stabDef":20,"slashDef":20,"crushDef":20},"6410":{"name":"Broodoo victim","hitpoints":100,"combatLevel":60,"attackSpeed":6,"defenceLevel":26,"rangeLevel":1,"magicLevel":60,"magic":25,"stabDef":20,"slashDef":20,"crushDef":20},"6411":{"name":"Broodoo victim","hitpoints":100,"combatLevel":60,"attackSpeed":6,"defenceLevel":26,"rangeLevel":1,"magicLevel":60,"magic":25,"stabDef":20,"slashDef":20,"crushDef":20},"6412":{"name":"Broodoo victim","hitpoints":100,"combatLevel":60,"attackSpeed":6,"defenceLevel":26,"rangeLevel":1,"magicLevel":60,"magic":25,"stabDef":20,"slashDef":20,"crushDef":20},"6413":{"name":"Broodoo victim","hitpoints":100,"combatLevel":60,"attackSpeed":6,"defenceLevel":26,"rangeLevel":1,"magicLevel":60,"magic":25,"stabDef":20,"slashDef":20,"crushDef":20},"6434":{"name":"Cave goblin","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6435":{"name":"Cave goblin","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6436":{"name":"Cave goblin","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6437":{"name":"Cave goblin","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6438":{"name":"Animated steel armour","hitpoints":50,"combatLevel":53,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":2,"rangeLevel":2,"magicLevel":2,"stabDef":50,"slashDef":25,"crushDef":25,"rangeDef":400,"magicDef":400,"bonusAttack":4,"bonusStrength":5,"poisonImmune":true,"venomImmune":true},"6440":{"name":"Giant skeleton","hitpoints":110,"combatLevel":100,"attackSpeed":6,"attackLevel":84,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40,"bonusStrength":15},"6441":{"name":"Skeleton","hitpoints":85,"combatLevel":94,"attackSpeed":6,"attackLevel":70,"strengthLevel":80,"defenceLevel":60,"rangeLevel":1,"magicLevel":110,"stabDef":50,"slashDef":40,"rangeDef":40,"bonusStrength":5},"6442":{"name":"Skeleton","hitpoints":80,"combatLevel":77,"attackSpeed":4,"attackLevel":72,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40,"bonusStrength":5},"6443":{"name":"Skeleton","hitpoints":71,"combatLevel":81,"attackSpeed":4,"attackLevel":72,"strengthLevel":64,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"rangeDef":40},"6444":{"name":"Skeleton","hitpoints":40,"combatLevel":42,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40},"6445":{"name":"Skeleton","hitpoints":53,"combatLevel":59,"attackSpeed":4,"attackLevel":50,"strengthLevel":46,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40},"6446":{"name":"Skeleton","hitpoints":42,"combatLevel":42,"attackSpeed":4,"attackLevel":36,"strengthLevel":38,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40},"6447":{"name":"Skeleton","hitpoints":58,"combatLevel":63,"attackSpeed":4,"attackLevel":56,"strengthLevel":61,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40},"6448":{"name":"Skeleton","hitpoints":26,"combatLevel":40,"attackSpeed":4,"attackLevel":33,"strengthLevel":31,"defenceLevel":52,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40},"6449":{"name":"Zombie","hitpoints":38,"combatLevel":40,"attackSpeed":4,"attackLevel":36,"strengthLevel":35,"defenceLevel":33,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6450":{"name":"Zombie","hitpoints":40,"combatLevel":42,"attackSpeed":4,"attackLevel":39,"strengthLevel":35,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6451":{"name":"Zombie","hitpoints":42,"combatLevel":47,"attackSpeed":4,"attackLevel":42,"strengthLevel":39,"defenceLevel":42,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6452":{"name":"Zombie","hitpoints":48,"combatLevel":50,"attackSpeed":4,"attackLevel":44,"strengthLevel":42,"defenceLevel":44,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6453":{"name":"Zombie","hitpoints":50,"combatLevel":56,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":47,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6454":{"name":"Zombie","hitpoints":57,"combatLevel":61,"attackSpeed":4,"attackLevel":53,"strengthLevel":55,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6455":{"name":"Zombie","hitpoints":63,"combatLevel":67,"attackSpeed":4,"attackLevel":56,"strengthLevel":58,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6456":{"name":"Zombie","hitpoints":65,"combatLevel":70,"attackSpeed":4,"attackLevel":61,"strengthLevel":60,"defenceLevel":61,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6457":{"name":"Zombie","hitpoints":68,"combatLevel":72,"attackSpeed":4,"attackLevel":61,"strengthLevel":63,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6458":{"name":"Zombie","hitpoints":71,"combatLevel":76,"attackSpeed":4,"attackLevel":69,"strengthLevel":65,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6459":{"name":"Zombie","hitpoints":73,"combatLevel":80,"attackSpeed":4,"attackLevel":72,"strengthLevel":73,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6460":{"name":"Zombie","hitpoints":75,"combatLevel":85,"attackSpeed":4,"attackLevel":71,"strengthLevel":81,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6461":{"name":"Zombie","hitpoints":76,"combatLevel":86,"attackSpeed":4,"attackLevel":74,"strengthLevel":80,"defenceLevel":71,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6462":{"name":"Zombie","hitpoints":81,"combatLevel":90,"attackSpeed":4,"attackLevel":82,"strengthLevel":80,"defenceLevel":72,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6463":{"name":"Zombie","hitpoints":92,"combatLevel":95,"attackSpeed":4,"attackLevel":85,"strengthLevel":80,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6464":{"name":"Zombie","hitpoints":96,"combatLevel":98,"attackSpeed":4,"attackLevel":85,"strengthLevel":83,"defenceLevel":81,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6465":{"name":"Zombie","hitpoints":102,"combatLevel":100,"attackSpeed":4,"attackLevel":86,"strengthLevel":83,"defenceLevel":81,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6466":{"name":"Zombie","hitpoints":76,"combatLevel":81,"attackSpeed":4,"attackLevel":69,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"6467":{"name":"Skeleton","hitpoints":69,"combatLevel":72,"attackSpeed":4,"attackLevel":75,"strengthLevel":61,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40,"bonusStrength":15},"6468":{"name":"Skeleton","hitpoints":92,"combatLevel":87,"attackSpeed":4,"attackLevel":75,"strengthLevel":80,"defenceLevel":55,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":40,"rangeDef":40,"bonusStrength":15},"6469":{"name":"Possessed pickaxe","hitpoints":40,"combatLevel":50,"attackSpeed":4,"attackLevel":40,"strengthLevel":55,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"6470":{"name":"Animated spade","hitpoints":40,"combatLevel":50,"attackSpeed":4,"attackLevel":40,"strengthLevel":55,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"6473":{"name":"Terror dog","hitpoints":87,"combatLevel":110,"slayerLevel":40,"attackSpeed":4,"attackLevel":110,"strengthLevel":104,"defenceLevel":78,"rangeLevel":1,"magicLevel":1,"bonusStrength":20},"6474":{"name":"Terror dog","hitpoints":82,"combatLevel":100,"slayerLevel":40,"attackSpeed":4,"attackLevel":100,"strengthLevel":90,"defenceLevel":74,"rangeLevel":1,"magicLevel":1,"bonusStrength":10},"6476":{"name":"Tarn","hitpoints":80,"combatLevel":69,"slayerLevel":40,"attackSpeed":4,"attackLevel":40,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50},"6477":{"name":"Mutant tarn","hitpoints":80,"combatLevel":69,"slayerLevel":40,"attackSpeed":4,"attackLevel":40,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50},"6492":{"name":"Kree'arra","hitpoints":255,"combatLevel":580,"attackSpeed":3,"attackLevel":300,"strengthLevel":200,"defenceLevel":260,"rangeLevel":380,"magicLevel":200,"range":120,"stabDef":180,"slashDef":180,"crushDef":180,"rangeDef":200,"magicDef":200,"bonusAttack":136,"bonusStrength":12,"bonusRangeStrength":50},"6493":{"name":"Commander Zilyana","hitpoints":255,"combatLevel":596,"attackSpeed":2,"attackLevel":280,"strengthLevel":196,"defenceLevel":300,"rangeLevel":250,"magicLevel":300,"magic":200,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":100,"magicDef":100,"bonusAttack":195,"bonusStrength":20,"poisonImmune":true,"venomImmune":true},"6494":{"name":"General Graardor","hitpoints":255,"combatLevel":624,"attackSpeed":6,"attackLevel":280,"strengthLevel":350,"defenceLevel":250,"rangeLevel":350,"magicLevel":80,"range":100,"stabDef":90,"slashDef":90,"crushDef":90,"rangeDef":90,"magicDef":298,"bonusAttack":120,"bonusStrength":43,"bonusRangeStrength":40,"poisonImmune":true,"venomImmune":true},"6495":{"name":"K'ril Tsutsaroth","hitpoints":255,"combatLevel":650,"attackSpeed":6,"attackLevel":340,"strengthLevel":300,"defenceLevel":270,"rangeLevel":1,"magicLevel":200,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":80,"magicDef":130,"bonusAttack":160,"bonusStrength":31},"6496":{"name":"Dagannoth Supreme","hitpoints":255,"combatLevel":303,"attackSpeed":4,"attackLevel":255,"strengthLevel":255,"defenceLevel":128,"rangeLevel":255,"magicLevel":255,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":550,"magicDef":255},"6497":{"name":"Dagannoth Prime","hitpoints":255,"combatLevel":303,"attackSpeed":4,"attackLevel":255,"strengthLevel":255,"defenceLevel":255,"magicLevel":255,"stabDef":255,"slashDef":255,"crushDef":255,"rangeDef":10,"magicDef":255},"6498":{"name":"Dagannoth Rex","hitpoints":255,"combatLevel":303,"attackSpeed":4,"attackLevel":255,"strengthLevel":255,"defenceLevel":255,"rangeLevel":255,"stabDef":255,"slashDef":255,"crushDef":255,"rangeDef":255,"magicDef":10},"6499":{"name":"Giant Mole","hitpoints":200,"combatLevel":230,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":200,"stabDef":60,"slashDef":80,"crushDef":100,"rangeDef":60,"magicDef":80},"6500":{"name":"Kalphite Queen","hitpoints":255,"combatLevel":333,"attackSpeed":4,"attackLevel":300,"strengthLevel":300,"defenceLevel":300,"rangeLevel":1,"magicLevel":150,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":100,"magicDef":100},"6501":{"name":"Kalphite Queen","hitpoints":255,"combatLevel":333,"attackSpeed":4,"attackLevel":300,"strengthLevel":300,"defenceLevel":300,"rangeLevel":1,"magicLevel":150,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":10,"magicDef":10},"6503":{"name":"Callisto","hitpoints":255,"combatLevel":470,"attackSpeed":4,"attackLevel":350,"strengthLevel":370,"defenceLevel":440,"rangeLevel":1,"magicLevel":1,"stabDef":135,"slashDef":104,"crushDef":175,"rangeDef":230,"magicDef":900,"poisonImmune":true,"venomImmune":true},"6504":{"name":"Venenatis","hitpoints":255,"combatLevel":464,"attackLevel":470,"strengthLevel":490,"defenceLevel":490,"rangeLevel":1,"magicLevel":150,"stabDef":260,"slashDef":260,"crushDef":260,"rangeDef":100,"magicDef":850,"poisonImmune":true,"venomImmune":true},"6505":{"name":"Chaos Elemental","hitpoints":250,"combatLevel":305,"attackSpeed":5,"attackLevel":270,"strengthLevel":270,"defenceLevel":270,"rangeLevel":270,"magicLevel":270,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":70,"magicDef":70},"6506":{"name":"TzTok-Jad","hitpoints":250,"combatLevel":702,"attackSpeed":8,"attackLevel":640,"strengthLevel":960,"defenceLevel":480,"rangeLevel":960,"magicLevel":480,"magic":60},"6574":{"name":"Gnome guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6575":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6576":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6579":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6580":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6581":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6582":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6583":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6587":{"name":"Armadylian guard","hitpoints":132,"combatLevel":97,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":82,"rangeLevel":90,"magicLevel":80,"magicDef":3},"6588":{"name":"Bandosian guard","hitpoints":130,"combatLevel":125,"attackSpeed":5,"attackLevel":110,"strengthLevel":115,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":12,"slashDef":14,"crushDef":13,"rangeDef":13,"magicDef":5,"bonusAttack":19,"bonusStrength":4,"poisonImmune":true},"6593":{"name":"Lava dragon","hitpoints":230,"combatLevel":252,"attackSpeed":4,"attackLevel":240,"strengthLevel":220,"defenceLevel":220,"rangeLevel":1,"magicLevel":1,"stabDef":70,"slashDef":90,"crushDef":90,"rangeDef":70,"magicDef":80},"6594":{"name":"Ent","hitpoints":105,"combatLevel":101,"attackSpeed":4,"attackLevel":80,"strengthLevel":95,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":30,"magicDef":40},"6596":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"6597":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"6598":{"name":"Zombie","hitpoints":24,"combatLevel":18,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1},"6600":{"name":"Runite Golem","hitpoints":170,"combatLevel":178,"attackSpeed":4,"attackLevel":140,"strengthLevel":150,"defenceLevel":165,"rangeLevel":1,"magicLevel":1},"6603":{"name":"Rogue","hitpoints":125,"combatLevel":135,"attackSpeed":4,"attackLevel":140,"strengthLevel":140,"defenceLevel":150,"rangeLevel":1,"magicLevel":1},"6604":{"name":"Mammoth","hitpoints":130,"combatLevel":80,"attackSpeed":4,"attackLevel":55,"strengthLevel":60,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"6605":{"name":"Bandit","hitpoints":155,"combatLevel":130,"attackSpeed":4,"attackLevel":57,"strengthLevel":57,"defenceLevel":57,"rangeLevel":1,"magicLevel":1,"slashDef":23,"crushDef":22,"bonusAttack":57,"bonusStrength":52},"6606":{"name":"Dark warrior","hitpoints":165,"combatLevel":145,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":55,"rangeLevel":1,"magicLevel":1,"stabDef":106,"slashDef":109,"crushDef":139,"bonusAttack":80,"bonusStrength":76},"6607":{"name":"Elder Chaos druid","hitpoints":150,"combatLevel":129,"attackSpeed":4,"attackLevel":98,"strengthLevel":98,"defenceLevel":65,"rangeLevel":1,"magicLevel":110},"6608":{"name":"Ankou","hitpoints":70,"combatLevel":86,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"6609":{"name":"Callisto","hitpoints":255,"combatLevel":470,"attackSpeed":4,"attackLevel":350,"strengthLevel":370,"defenceLevel":440,"rangeLevel":1,"magicLevel":1,"stabDef":135,"slashDef":104,"crushDef":175,"rangeDef":230,"magicDef":900,"poisonImmune":true,"venomImmune":true},"6610":{"name":"Venenatis","hitpoints":255,"combatLevel":464,"attackLevel":470,"strengthLevel":490,"defenceLevel":490,"rangeLevel":1,"magicLevel":150,"stabDef":260,"slashDef":260,"crushDef":260,"rangeDef":100,"magicDef":850,"poisonImmune":true,"venomImmune":true},"6611":{"name":"Vet'ion","hitpoints":255,"combatLevel":454,"attackSpeed":4,"attackLevel":430,"strengthLevel":430,"defenceLevel":395,"rangeLevel":1,"magicLevel":300,"stabDef":201,"slashDef":200,"rangeDef":270,"magicDef":250},"6612":{"name":"Vet'ion Reborn","hitpoints":255,"combatLevel":454,"attackSpeed":4,"attackLevel":430,"strengthLevel":430,"defenceLevel":395,"rangeLevel":1,"magicLevel":300,"stabDef":201,"slashDef":200,"rangeDef":270,"magicDef":250},"6613":{"name":"Skeleton Hellhound","hitpoints":110,"combatLevel":214,"attackSpeed":4,"attackLevel":210,"strengthLevel":250,"defenceLevel":150,"rangeLevel":1,"magicLevel":1,"stabDef":101,"slashDef":103,"crushDef":10,"rangeDef":266,"magicDef":180},"6614":{"name":"Greater Skeleton Hellhound","hitpoints":190,"combatLevel":281,"attackSpeed":4,"attackLevel":240,"strengthLevel":310,"defenceLevel":220,"rangeLevel":1,"magicLevel":1,"stabDef":150,"slashDef":163,"crushDef":20,"rangeDef":275,"magicDef":210},"6615":{"name":"Scorpia","hitpoints":200,"combatLevel":225,"attackSpeed":4,"attackLevel":250,"strengthLevel":150,"defenceLevel":180,"rangeLevel":1,"magicLevel":1,"stabDef":246,"slashDef":284,"crushDef":284,"rangeDef":284,"magicDef":44,"bonusAttack":60},"6616":{"name":"Scorpia's offspring","hitpoints":2,"combatLevel":15,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":30,"magicLevel":1,"range":900},"6617":{"name":"Scorpia's guardian","hitpoints":70,"combatLevel":47,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":30,"magicLevel":30},"6618":{"name":"Crazy archaeologist","hitpoints":225,"combatLevel":204,"attackSpeed":3,"attackLevel":160,"strengthLevel":90,"defenceLevel":240,"rangeLevel":180,"magicLevel":1,"range":75,"stabDef":5,"slashDef":5,"crushDef":30,"rangeDef":250,"bonusAttack":250,"bonusStrength":25},"6619":{"name":"Chaos Fanatic","hitpoints":225,"combatLevel":202,"attackSpeed":2,"attackLevel":1,"strengthLevel":1,"defenceLevel":220,"rangeLevel":1,"magicLevel":200,"range":75,"stabDef":260,"slashDef":260,"crushDef":250,"rangeDef":80,"magicDef":280},"6624":{"name":"Energy sprite","hitpoints":62},"6698":{"name":"Ghost guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6699":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6700":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6701":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6702":{"name":"Guard","hitpoints":800,"combatLevel":1337,"attackSpeed":4,"attackLevel":800,"strengthLevel":400,"defenceLevel":300,"rangeLevel":1,"magicLevel":1,"stabDef":8,"slashDef":9,"crushDef":7,"rangeDef":8,"bonusAttack":60,"bonusStrength":7},"6716":{"name":"Chaotic death spawn","hitpoints":50,"combatLevel":215,"attackSpeed":3,"attackLevel":380,"strengthLevel":1,"defenceLevel":70,"rangeLevel":380,"magicLevel":380},"6723":{"name":"Chaotic death spawn","hitpoints":50,"combatLevel":215,"attackSpeed":3,"attackLevel":380,"strengthLevel":1,"defenceLevel":70,"rangeLevel":380,"magicLevel":380},"6725":{"name":"Rock Golem","combatLevel":14,"attackSpeed":4},"6726":{"name":"Rock Golem","combatLevel":29,"attackSpeed":4},"6727":{"name":"Rock Golem","combatLevel":49,"attackSpeed":4},"6728":{"name":"Rock Golem","hitpoints":86,"combatLevel":79,"attackSpeed":4},"6729":{"name":"Rock Golem","hitpoints":120,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":75,"magicLevel":1},"6730":{"name":"Rock Golem","hitpoints":166,"combatLevel":159,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":80,"magicLevel":1},"6732":{"name":"River troll","combatLevel":14,"attackSpeed":4,"attackLevel":17,"strengthLevel":17,"defenceLevel":17,"rangeLevel":1,"magicLevel":1},"6733":{"name":"River troll","combatLevel":29,"attackSpeed":4,"attackLevel":32,"strengthLevel":32,"defenceLevel":32,"rangeLevel":1,"magicLevel":1},"6734":{"name":"River troll","combatLevel":49,"attackSpeed":4,"attackLevel":48,"strengthLevel":48,"defenceLevel":48,"rangeLevel":1,"magicLevel":1},"6735":{"name":"River troll","hitpoints":85,"combatLevel":79,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1},"6736":{"name":"River troll","hitpoints":120,"combatLevel":120,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"6737":{"name":"River troll","hitpoints":170,"combatLevel":159,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1},"6739":{"name":"Evil Chicken","hitpoints":120,"combatLevel":159,"attackSpeed":4,"defenceLevel":126,"magicLevel":200},"6740":{"name":"Shade","hitpoints":170,"combatLevel":159,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1},"6741":{"name":"Zombie","hitpoints":170,"combatLevel":159,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1},"6762":{"name":"Pyrelord","hitpoints":80,"combatLevel":60,"slayerLevel":30,"attackSpeed":4,"attackLevel":60,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"6766":{"name":"Lizardman shaman","hitpoints":150,"combatLevel":150,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":140,"rangeLevel":120,"magicLevel":130,"range":45,"slashDef":40,"crushDef":30,"magicDef":50,"bonusAttack":45,"bonusStrength":38,"bonusRangeStrength":38},"6767":{"name":"Lizardman shaman","hitpoints":150,"combatLevel":150,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":140,"rangeLevel":120,"magicLevel":130,"range":45,"slashDef":40,"crushDef":30,"magicDef":50,"bonusAttack":45,"bonusStrength":38,"bonusRangeStrength":38},"6794":{"name":"Monkey Archer","hitpoints":50,"combatLevel":86,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":110,"magicLevel":1},"6795":{"name":"Pyrelord","hitpoints":80,"combatLevel":60,"slayerLevel":30,"attackSpeed":4,"attackLevel":60,"strengthLevel":40,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":10,"rangeDef":10},"6805":{"name":"Kruk","hitpoints":210,"combatLevel":149,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":150,"rangeLevel":170,"magicLevel":130,"range":150,"stabDef":100,"slashDef":100,"crushDef":100,"rangeDef":100,"magicDef":250,"bonusAttack":160,"bonusStrength":90,"bonusRangeStrength":70},"6813":{"name":"Monkey Archer","hitpoints":50,"combatLevel":86,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":110,"magicLevel":1},"6815":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6818":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6824":{"name":"Giant bat","hitpoints":32,"combatLevel":27,"attackSpeed":4,"attackLevel":22,"strengthLevel":22,"defenceLevel":22,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":12,"rangeDef":8,"magicDef":10},"6896":{"name":"Gangster","hitpoints":40,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"crushDef":10,"bonusAttack":20,"bonusStrength":30},"6897":{"name":"Gangster","hitpoints":40,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"crushDef":10,"bonusAttack":20,"bonusStrength":30},"6898":{"name":"Gangster","hitpoints":50,"combatLevel":50,"attackSpeed":5,"attackLevel":50,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"crushDef":10,"bonusAttack":35,"bonusStrength":15},"6899":{"name":"Gangster","hitpoints":50,"combatLevel":50,"attackSpeed":5,"attackLevel":50,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"crushDef":10,"bonusAttack":35,"bonusStrength":15},"6900":{"name":"Gang boss","hitpoints":80,"combatLevel":83,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":80,"magicLevel":1,"range":50,"crushDef":10},"6901":{"name":"Gang boss","hitpoints":80,"combatLevel":83,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":80,"magicLevel":1,"range":50,"crushDef":10},"6902":{"name":"Gang boss","hitpoints":80,"combatLevel":76,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":80,"magicLevel":1,"range":50,"crushDef":10},"6903":{"name":"Gang boss","hitpoints":80,"combatLevel":76,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":70,"rangeLevel":80,"magicLevel":1,"range":50,"crushDef":10},"6904":{"name":"Soldier (tier 1)","hitpoints":50,"combatLevel":39,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":11,"poisonImmune":true,"venomImmune":true},"6905":{"name":"Soldier (tier 1)","hitpoints":50,"combatLevel":39,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":12,"bonusStrength":11,"poisonImmune":true,"venomImmune":true},"6906":{"name":"Soldier (tier 2)","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusAttack":25,"bonusStrength":30,"poisonImmune":true,"venomImmune":true},"6907":{"name":"Soldier (tier 2)","hitpoints":50,"combatLevel":48,"attackSpeed":4,"attackLevel":40,"strengthLevel":40,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"bonusAttack":25,"bonusStrength":30,"poisonImmune":true,"venomImmune":true},"6908":{"name":"Soldier (tier 3)","hitpoints":55,"combatLevel":58,"attackSpeed":5,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"bonusAttack":20,"bonusStrength":8,"poisonImmune":true,"venomImmune":true},"6909":{"name":"Soldier (tier 3)","hitpoints":55,"combatLevel":58,"attackSpeed":5,"attackLevel":50,"strengthLevel":50,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"bonusAttack":20,"bonusStrength":8,"poisonImmune":true,"venomImmune":true},"6910":{"name":"Soldier (tier 4)","hitpoints":65,"combatLevel":70,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"bonusAttack":10,"bonusStrength":8,"poisonImmune":true,"venomImmune":true},"6911":{"name":"Soldier (tier 4)","hitpoints":65,"combatLevel":70,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"stabDef":1,"slashDef":1,"bonusAttack":10,"bonusStrength":8,"poisonImmune":true,"venomImmune":true},"6912":{"name":"Soldier (tier 5)","hitpoints":90,"combatLevel":99,"attackSpeed":4,"attackLevel":85,"strengthLevel":85,"defenceLevel":86,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":5,"poisonImmune":true,"venomImmune":true},"6913":{"name":"Soldier (tier 5)","hitpoints":90,"combatLevel":99,"attackSpeed":4,"attackLevel":85,"strengthLevel":85,"defenceLevel":86,"rangeLevel":1,"magicLevel":1,"bonusAttack":5,"bonusStrength":5,"poisonImmune":true,"venomImmune":true},"6914":{"name":"Lizardman","hitpoints":60,"combatLevel":53,"attackSpeed":4,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":43,"magicLevel":1,"range":22,"slashDef":25,"bonusAttack":22,"bonusStrength":20,"bonusRangeStrength":20},"6915":{"name":"Lizardman","hitpoints":60,"combatLevel":53,"attackSpeed":4,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":43,"magicLevel":1,"range":22,"slashDef":25,"bonusAttack":22,"bonusStrength":20,"bonusRangeStrength":20},"6916":{"name":"Lizardman","hitpoints":60,"combatLevel":62,"attackSpeed":4,"attackLevel":53,"strengthLevel":53,"defenceLevel":52,"rangeLevel":1,"magicLevel":1,"range":28,"slashDef":20,"crushDef":5,"bonusAttack":28,"bonusStrength":25,"bonusRangeStrength":25},"6917":{"name":"Lizardman","hitpoints":60,"combatLevel":62,"attackSpeed":4,"attackLevel":53,"strengthLevel":53,"defenceLevel":52,"rangeLevel":1,"magicLevel":1,"range":28,"slashDef":20,"crushDef":5,"bonusAttack":28,"bonusStrength":25,"bonusRangeStrength":25},"6918":{"name":"Lizardman brute","hitpoints":60,"combatLevel":73,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"range":34,"slashDef":30,"crushDef":10,"bonusAttack":34,"bonusStrength":30,"bonusRangeStrength":30},"6919":{"name":"Lizardman brute","hitpoints":60,"combatLevel":73,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"range":34,"slashDef":30,"crushDef":10,"bonusAttack":34,"bonusStrength":30,"bonusRangeStrength":30},"6956":{"name":"Monk","hitpoints":15,"combatLevel":5,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"6987":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6988":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6989":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6990":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6991":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6992":{"name":"Woman","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"6993":{"name":"Pirate","hitpoints":20,"combatLevel":23,"attackSpeed":5,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"slashDef":1,"bonusAttack":9,"bonusStrength":10},"6994":{"name":"Pirate","hitpoints":20,"combatLevel":23,"attackSpeed":5,"attackLevel":21,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":2,"bonusAttack":8,"bonusStrength":10},"6995":{"name":"Pirate","hitpoints":20,"combatLevel":23,"attackSpeed":5,"attackLevel":21,"strengthLevel":21,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":2,"bonusAttack":8,"bonusStrength":10},"6996":{"name":"Mugger","hitpoints":8,"combatLevel":6,"attackSpeed":4,"attackLevel":5,"strengthLevel":5,"defenceLevel":5,"rangeLevel":1,"magicLevel":1},"7016":{"name":"Kourend guard","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":25,"crushDef":19,"rangeDef":20,"bonusAttack":9,"bonusStrength":7},"7017":{"name":"Kourend head guard","hitpoints":86,"combatLevel":84,"attackSpeed":4,"attackLevel":75,"strengthLevel":73,"defenceLevel":61,"rangeLevel":1,"magicLevel":1,"stabDef":16,"slashDef":16,"crushDef":18,"rangeDef":27,"bonusAttack":17,"bonusStrength":15},"7018":{"name":"Reanimated goblin","hitpoints":5,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"7019":{"name":"Reanimated monkey","hitpoints":5,"attackSpeed":4,"attackLevel":2,"strengthLevel":2,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"7020":{"name":"Reanimated imp","hitpoints":5,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"7021":{"name":"Reanimated minotaur","hitpoints":10,"attackSpeed":4,"attackLevel":12,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"7022":{"name":"Reanimated scorpion","hitpoints":15,"attackSpeed":4,"attackLevel":11,"strengthLevel":12,"defenceLevel":11,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"magicDef":5},"7023":{"name":"Reanimated bear","hitpoints":15,"attackSpeed":4,"attackLevel":17,"strengthLevel":18,"defenceLevel":15,"rangeLevel":1,"magicLevel":1},"7024":{"name":"Reanimated unicorn","hitpoints":15,"attackSpeed":4,"attackLevel":11,"strengthLevel":13,"defenceLevel":13,"rangeLevel":1,"magicLevel":1},"7025":{"name":"Reanimated dog","hitpoints":35,"attackSpeed":4,"attackLevel":53,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1},"7026":{"name":"Reanimated chaos druid","hitpoints":35,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":12,"rangeLevel":1,"magicLevel":10},"7027":{"name":"Reanimated giant","hitpoints":35,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"7028":{"name":"Reanimated ogre","hitpoints":35,"attackSpeed":6,"attackLevel":43,"strengthLevel":43,"defenceLevel":43,"rangeLevel":1,"magicLevel":1,"bonusAttack":22,"bonusStrength":20},"7029":{"name":"Reanimated elf","hitpoints":35,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":20,"crushDef":40,"rangeDef":50,"magicDef":60},"7030":{"name":"Reanimated troll","hitpoints":35,"attackSpeed":4,"attackLevel":40,"strengthLevel":75,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"crushDef":10,"rangeDef":200,"magicDef":200,"bonusAttack":20,"bonusStrength":20},"7031":{"name":"Reanimated horror","hitpoints":35,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":55,"rangeLevel":1,"magicLevel":1},"7032":{"name":"Reanimated kalphite","hitpoints":35,"attackSpeed":4,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":5,"rangeDef":50,"magicDef":50},"7033":{"name":"Reanimated dagannoth","hitpoints":35,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":81,"rangeLevel":50,"magicLevel":1},"7034":{"name":"Reanimated bloodveld","hitpoints":35,"slayerLevel":50,"attackSpeed":4,"attackLevel":75,"strengthLevel":45,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"7035":{"name":"Reanimated TzHaar","hitpoints":35,"attackSpeed":5,"attackLevel":120,"strengthLevel":140,"defenceLevel":120,"rangeLevel":1,"magicLevel":40},"7036":{"name":"Reanimated demon","hitpoints":35,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7037":{"name":"Reanimated aviansie","hitpoints":35,"attackSpeed":5,"attackLevel":71,"strengthLevel":1,"defenceLevel":70,"rangeLevel":1,"magicLevel":1},"7038":{"name":"Reanimated abyssal","hitpoints":35,"slayerLevel":85,"attackSpeed":4,"attackLevel":97,"strengthLevel":67,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"7039":{"name":"Reanimated dragon","hitpoints":35,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"7095":{"name":"Tortured gorilla","hitpoints":110,"combatLevel":142,"attackSpeed":4,"attackLevel":180,"strengthLevel":90,"defenceLevel":110,"rangeLevel":1,"magicLevel":110,"bonusAttack":90,"bonusStrength":123},"7096":{"name":"Tortured gorilla","hitpoints":110,"combatLevel":142,"attackSpeed":4,"attackLevel":180,"strengthLevel":90,"defenceLevel":110,"rangeLevel":1,"magicLevel":110,"bonusAttack":90,"bonusStrength":123},"7097":{"name":"Tortured gorilla","hitpoints":110,"combatLevel":142,"attackSpeed":4,"attackLevel":180,"strengthLevel":90,"defenceLevel":110,"rangeLevel":1,"magicLevel":110,"bonusAttack":90,"bonusStrength":123},"7101":{"name":"Glough","hitpoints":575,"combatLevel":378,"attackSpeed":6,"attackLevel":260,"strengthLevel":270,"defenceLevel":248,"rangeLevel":262,"magicLevel":250,"range":100,"magic":80,"bonusAttack":122,"bonusStrength":75,"bonusRangeStrength":40,"bonusMagicDamage":40},"7102":{"name":"Glough","hitpoints":575,"combatLevel":378,"attackSpeed":6,"attackLevel":260,"strengthLevel":270,"defenceLevel":248,"rangeLevel":262,"magicLevel":250,"range":100,"magic":80,"bonusAttack":122,"bonusStrength":75,"bonusRangeStrength":40,"bonusMagicDamage":40},"7103":{"name":"Glough","hitpoints":575,"combatLevel":378,"attackSpeed":6,"attackLevel":260,"strengthLevel":270,"defenceLevel":248,"rangeLevel":262,"magicLevel":250,"range":100,"magic":80,"bonusAttack":122,"bonusStrength":75,"bonusRangeStrength":40,"bonusMagicDamage":40},"7104":{"name":"Keef","hitpoints":180,"combatLevel":178,"attackSpeed":5,"attackLevel":165,"strengthLevel":120,"defenceLevel":165,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"bonusAttack":190,"bonusStrength":170},"7105":{"name":"Keef","hitpoints":180,"combatLevel":178,"attackSpeed":5,"attackLevel":165,"strengthLevel":120,"defenceLevel":165,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"bonusAttack":190,"bonusStrength":170},"7106":{"name":"Kob","hitpoints":200,"combatLevel":185,"attackSpeed":5,"attackLevel":180,"strengthLevel":175,"defenceLevel":80,"stabDef":85,"slashDef":85,"crushDef":90,"rangeDef":200,"magicDef":200,"bonusAttack":200,"bonusStrength":135,"poisonImmune":true,"venomImmune":true},"7107":{"name":"Kob","hitpoints":200,"combatLevel":185,"attackSpeed":5,"attackLevel":180,"strengthLevel":175,"defenceLevel":80,"stabDef":85,"slashDef":85,"crushDef":90,"rangeDef":200,"magicDef":200,"bonusAttack":200,"bonusStrength":135,"poisonImmune":true,"venomImmune":true},"7118":{"name":"Maniacal monkey","hitpoints":65,"combatLevel":140,"attackSpeed":4,"attackLevel":200,"strengthLevel":175,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"7119":{"name":"Maniacal Monkey Archer","hitpoints":60,"combatLevel":132,"attackSpeed":4,"attackLevel":120,"strengthLevel":80,"defenceLevel":80,"rangeLevel":200,"magicLevel":1},"7144":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7145":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7146":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7147":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7148":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7149":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7150":{"name":"Tortured gorilla","hitpoints":210,"combatLevel":141,"attackSpeed":5,"attackLevel":105,"strengthLevel":95,"defenceLevel":95,"rangeLevel":95,"magicLevel":95,"range":22,"magic":20,"magicDef":25,"bonusAttack":22,"bonusStrength":16,"bonusRangeStrength":16,"bonusMagicDamage":16},"7151":{"name":"Tortured gorilla","hitpoints":210,"combatLevel":141,"attackSpeed":5,"attackLevel":105,"strengthLevel":95,"defenceLevel":95,"rangeLevel":95,"magicLevel":95,"range":22,"magic":20,"magicDef":25,"bonusAttack":22,"bonusStrength":16,"bonusRangeStrength":16,"bonusMagicDamage":16},"7152":{"name":"Demonic gorilla","hitpoints":380,"combatLevel":275,"attackSpeed":5,"attackLevel":205,"strengthLevel":195,"defenceLevel":200,"rangeLevel":195,"magicLevel":195,"range":43,"magic":40,"magicDef":50,"bonusAttack":43,"bonusStrength":29,"bonusRangeStrength":29},"7153":{"name":"Tortured gorilla","hitpoints":210,"combatLevel":141,"attackSpeed":5,"attackLevel":105,"strengthLevel":95,"defenceLevel":95,"rangeLevel":95,"magicLevel":95,"range":22,"magic":20,"magicDef":25,"bonusAttack":22,"bonusStrength":16,"bonusRangeStrength":16,"bonusMagicDamage":16},"7206":{"name":"Sand Crab","hitpoints":60,"combatLevel":15,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"7207":{"name":"Sandy rocks","hitpoints":60,"combatLevel":15,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"7209":{"name":"Guard dog","hitpoints":49,"combatLevel":44,"attackSpeed":4,"attackLevel":35,"strengthLevel":36,"defenceLevel":37,"rangeLevel":1,"magicLevel":1},"7234":{"name":"Ent","hitpoints":75,"combatLevel":86,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":30,"magicDef":40,"bonusAttack":35,"bonusStrength":65},"7238":{"name":"Forester","hitpoints":17,"combatLevel":15,"attackSpeed":4,"attackLevel":14,"strengthLevel":13,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"stabDef":4,"slashDef":3,"crushDef":5},"7241":{"name":"Abyssal demon","hitpoints":150,"combatLevel":124,"slayerLevel":85,"attackSpeed":4,"attackLevel":97,"strengthLevel":67,"defenceLevel":135,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20},"7242":{"name":"Black demon","hitpoints":170,"combatLevel":184,"attackSpeed":4,"attackLevel":155,"strengthLevel":158,"defenceLevel":162,"rangeLevel":1,"magicLevel":1},"7243":{"name":"Black demon","hitpoints":160,"combatLevel":178,"attackSpeed":4,"attackLevel":145,"strengthLevel":148,"defenceLevel":175,"rangeLevel":1,"magicLevel":1},"7244":{"name":"Greater demon","hitpoints":120,"combatLevel":101,"attackSpeed":4,"attackLevel":90,"strengthLevel":90,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"7245":{"name":"Greater demon","hitpoints":115,"combatLevel":100,"attackSpeed":4,"attackLevel":90,"strengthLevel":70,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"7246":{"name":"Greater demon","hitpoints":130,"combatLevel":113,"attackSpeed":4,"attackLevel":120,"strengthLevel":90,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"7247":{"name":"Lesser demon","hitpoints":87,"combatLevel":87,"attackSpeed":4,"attackLevel":80,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7248":{"name":"Lesser demon","hitpoints":98,"combatLevel":94,"attackSpeed":4,"attackLevel":80,"strengthLevel":70,"defenceLevel":85,"rangeLevel":1,"magicLevel":1},"7249":{"name":"Dust devil","hitpoints":130,"combatLevel":110,"slayerLevel":65,"attackSpeed":4,"attackLevel":120,"strengthLevel":90,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"7250":{"name":"Dark beast","hitpoints":220,"combatLevel":182,"slayerLevel":90,"attackSpeed":4,"attackLevel":140,"strengthLevel":160,"defenceLevel":120,"rangeLevel":1,"magicLevel":160,"stabDef":30,"slashDef":40,"crushDef":100,"rangeDef":100,"magicDef":90},"7251":{"name":"Fire giant","hitpoints":150,"combatLevel":109,"attackSpeed":5,"attackLevel":90,"strengthLevel":80,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"7252":{"name":"Fire giant","hitpoints":130,"combatLevel":104,"attackSpeed":5,"attackLevel":65,"strengthLevel":65,"defenceLevel":120,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":10,"crushDef":10,"magicDef":50,"bonusAttack":50,"bonusStrength":20},"7253":{"name":"Bronze dragon","hitpoints":122,"combatLevel":143,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":112,"rangeLevel":1,"magicLevel":130,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"7254":{"name":"Iron dragon","hitpoints":195,"combatLevel":215,"attackSpeed":4,"attackLevel":185,"strengthLevel":185,"defenceLevel":185,"rangeLevel":1,"magicLevel":120,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"7255":{"name":"Steel dragon","hitpoints":250,"combatLevel":274,"attackSpeed":4,"attackLevel":235,"strengthLevel":235,"defenceLevel":235,"rangeLevel":1,"magicLevel":130,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"7256":{"name":"Hellhound","hitpoints":116,"combatLevel":122,"attackSpeed":4,"attackLevel":105,"strengthLevel":104,"defenceLevel":102,"rangeLevel":1,"magicLevel":1},"7257":{"name":"Ankou","hitpoints":60,"combatLevel":95,"attackSpeed":4,"attackLevel":100,"strengthLevel":70,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"7258":{"name":"Shade","hitpoints":115,"combatLevel":140,"attackSpeed":6,"attackLevel":130,"strengthLevel":136,"defenceLevel":100,"rangeLevel":50,"magicLevel":50},"7259":{"name":"Dagannoth","hitpoints":70,"combatLevel":74,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":50,"rangeLevel":1,"magicLevel":1},"7260":{"name":"Dagannoth","hitpoints":120,"combatLevel":92,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7261":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"7262":{"name":"Moss giant","hitpoints":60,"combatLevel":42,"attackSpeed":6,"attackLevel":30,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"bonusAttack":33,"bonusStrength":31},"7263":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"7264":{"name":"Ghost","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":13,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"crushDef":5,"rangeDef":5},"7265":{"name":"Skeleton","hitpoints":29,"combatLevel":22,"attackSpeed":4,"attackLevel":15,"strengthLevel":18,"defenceLevel":17,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":5,"rangeDef":5},"7266":{"name":"King Sand Crab","hitpoints":200,"combatLevel":107,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"7267":{"name":"Sandy Boulder","hitpoints":200,"combatLevel":107,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"7268":{"name":"Possessed pickaxe","hitpoints":40,"combatLevel":50,"attackSpeed":4,"attackLevel":40,"strengthLevel":55,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"7269":{"name":"Magic axe","hitpoints":44,"combatLevel":42,"attackSpeed":4,"attackLevel":38,"strengthLevel":38,"defenceLevel":29,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":5,"crushDef":15,"rangeDef":10,"magicDef":5},"7270":{"name":"Cyclops","hitpoints":100,"combatLevel":76,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"7271":{"name":"Cyclops","hitpoints":75,"combatLevel":56,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":35,"rangeLevel":1,"magicLevel":1},"7272":{"name":"Twisted Banshee","hitpoints":100,"combatLevel":89,"slayerLevel":15,"attackSpeed":4,"attackLevel":75,"strengthLevel":85,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":15,"crushDef":15,"rangeDef":15},"7273":{"name":"Brutal blue dragon","hitpoints":245,"combatLevel":271,"attackSpeed":4,"attackLevel":298,"strengthLevel":198,"defenceLevel":198,"magicLevel":198,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"poisonImmune":true,"venomImmune":true},"7274":{"name":"Brutal red dragon","hitpoints":285,"combatLevel":289,"attackSpeed":4,"attackLevel":310,"strengthLevel":210,"defenceLevel":198,"magicLevel":250,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"poisonImmune":true,"venomImmune":true},"7275":{"name":"Brutal black dragon","hitpoints":315,"combatLevel":318,"slayerLevel":77,"attackSpeed":4,"attackLevel":330,"strengthLevel":210,"defenceLevel":258,"magicLevel":250,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"bonusStrength":20,"poisonImmune":true,"venomImmune":true},"7276":{"name":"Mutated Bloodveld","hitpoints":170,"combatLevel":123,"slayerLevel":50,"attackSpeed":4,"attackLevel":110,"strengthLevel":115,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"7277":{"name":"Warped Jelly","hitpoints":140,"combatLevel":112,"slayerLevel":52,"attackSpeed":4,"attackLevel":95,"strengthLevel":90,"defenceLevel":70,"rangeLevel":1,"magicLevel":95},"7278":{"name":"Greater Nechryael","hitpoints":205,"combatLevel":200,"slayerLevel":80,"attackSpeed":4,"attackLevel":197,"strengthLevel":197,"defenceLevel":85,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50},"7279":{"name":"Deviant spectre","hitpoints":190,"combatLevel":169,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":90,"rangeLevel":1,"magicLevel":205,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":85},"7286":{"name":"Skotizo","hitpoints":450,"combatLevel":321,"attackSpeed":6,"attackLevel":240,"strengthLevel":250,"defenceLevel":200,"rangeLevel":1,"magicLevel":280,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":130,"magicDef":130,"bonusAttack":160,"bonusStrength":31,"poisonImmune":true,"venomImmune":true},"7287":{"name":"Reanimated demon spawn","hitpoints":85,"combatLevel":87,"attackSpeed":4,"attackLevel":80,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7288":{"name":"Awakened Altar","hitpoints":100},"7290":{"name":"Awakened Altar","hitpoints":100},"7292":{"name":"Awakened Altar","hitpoints":100},"7294":{"name":"Awakened Altar","hitpoints":100},"7296":{"name":"Dark Ankou","hitpoints":60,"combatLevel":95,"attackSpeed":4,"attackLevel":100,"strengthLevel":70,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"7307":{"name":"Ancient Wizard","hitpoints":80,"combatLevel":98,"attackSpeed":4,"attackLevel":50,"strengthLevel":30,"defenceLevel":20,"rangeLevel":1,"magicLevel":150},"7308":{"name":"Ancient Wizard","hitpoints":80,"combatLevel":98,"attackSpeed":4,"attackLevel":50,"strengthLevel":30,"defenceLevel":20,"rangeLevel":150,"magicLevel":80,"bonusRangeStrength":30},"7309":{"name":"Ancient Wizard","hitpoints":80,"combatLevel":112,"attackSpeed":4,"attackLevel":120,"strengthLevel":150,"defenceLevel":20,"rangeLevel":1,"magicLevel":80,"bonusAttack":80,"bonusStrength":80},"7310":{"name":"Brassican Mage","hitpoints":150,"combatLevel":140,"attackSpeed":3,"attackLevel":80,"strengthLevel":70,"defenceLevel":80,"rangeLevel":140,"magicLevel":170},"7312":{"name":"Double agent","hitpoints":160,"combatLevel":141,"attackSpeed":4,"attackLevel":130,"strengthLevel":105,"defenceLevel":100,"rangeLevel":1,"magicLevel":95,"stabDef":20,"slashDef":20,"crushDef":20,"bonusAttack":40,"bonusStrength":40},"7322":{"name":"Wild dog","hitpoints":62,"combatLevel":63,"attackSpeed":4,"attackLevel":53,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1},"7388":{"name":"Crushing hand","hitpoints":55,"combatLevel":45,"slayerLevel":5,"attackSpeed":4,"attackLevel":45,"strengthLevel":42,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"poisonImmune":true},"7389":{"name":"Chasm Crawler","hitpoints":64,"combatLevel":68,"slayerLevel":10,"attackSpeed":4,"attackLevel":22,"strengthLevel":18,"defenceLevel":18,"rangeLevel":1,"magicLevel":1,"stabDef":10,"slashDef":10,"crushDef":5,"rangeDef":10,"magicDef":5},"7390":{"name":"Screaming banshee","hitpoints":61,"combatLevel":70,"slayerLevel":15,"attackSpeed":4,"attackLevel":65,"strengthLevel":61,"defenceLevel":56,"rangeLevel":1,"magicLevel":1,"stabDef":13,"slashDef":13,"crushDef":13,"rangeDef":13,"poisonImmune":true,"venomImmune":true},"7391":{"name":"Screaming twisted banshee","hitpoints":220,"combatLevel":144,"slayerLevel":15,"attackSpeed":4,"attackLevel":103,"strengthLevel":110,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":25,"rangeDef":25,"poisonImmune":true,"venomImmune":true},"7392":{"name":"Giant rockslug","hitpoints":77,"combatLevel":86,"slayerLevel":20,"attackSpeed":6,"attackLevel":72,"strengthLevel":77,"defenceLevel":77,"rangeLevel":1,"magicLevel":1},"7393":{"name":"Cockathrice","hitpoints":95,"combatLevel":89,"slayerLevel":25,"attackSpeed":4,"attackLevel":63,"strengthLevel":78,"defenceLevel":78,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"magicDef":20,"poisonImmune":true,"venomImmune":true},"7394":{"name":"Flaming pyrelord","hitpoints":126,"combatLevel":97,"slayerLevel":30,"attackSpeed":4,"attackLevel":98,"strengthLevel":65,"defenceLevel":52,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":18,"crushDef":18,"rangeDef":18,"poisonImmune":true,"venomImmune":true},"7395":{"name":"Monstrous basilisk","hitpoints":170,"combatLevel":135,"slayerLevel":40,"attackSpeed":4,"attackLevel":88,"strengthLevel":98,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"magicDef":35,"poisonImmune":true,"venomImmune":true},"7396":{"name":"Malevolent Mage","hitpoints":175,"combatLevel":162,"slayerLevel":45,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":135,"rangeLevel":1,"magicLevel":175,"poisonImmune":true,"venomImmune":true},"7397":{"name":"Insatiable Bloodveld","hitpoints":380,"combatLevel":202,"slayerLevel":50,"attackSpeed":4,"attackLevel":190,"strengthLevel":145,"defenceLevel":85,"rangeLevel":1,"magicLevel":1,"venomImmune":true},"7398":{"name":"Insatiable mutated Bloodveld","hitpoints":410,"combatLevel":278,"slayerLevel":50,"attackSpeed":4,"attackLevel":250,"strengthLevel":190,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"venomImmune":true},"7399":{"name":"Vitreous Jelly","hitpoints":190,"combatLevel":206,"slayerLevel":52,"attackSpeed":4,"attackLevel":170,"strengthLevel":150,"defenceLevel":220,"rangeLevel":1,"magicLevel":150},"7400":{"name":"Vitreous warped Jelly","hitpoints":220,"combatLevel":241,"slayerLevel":52,"attackSpeed":4,"attackLevel":200,"strengthLevel":180,"defenceLevel":250,"rangeLevel":1,"magicLevel":180},"7401":{"name":"Cave abomination","hitpoints":130,"combatLevel":206,"slayerLevel":58,"attackLevel":230,"strengthLevel":195,"defenceLevel":142,"rangeLevel":1,"magicLevel":230},"7402":{"name":"Abhorrent spectre","hitpoints":250,"combatLevel":253,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":180,"rangeLevel":1,"magicLevel":300,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":30,"venomImmune":true},"7403":{"name":"Repugnant spectre","hitpoints":390,"combatLevel":335,"slayerLevel":60,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":220,"rangeLevel":1,"magicLevel":380,"stabDef":120,"slashDef":120,"crushDef":120,"rangeDef":115,"venomImmune":true},"7404":{"name":"Choke devil","hitpoints":300,"combatLevel":264,"slayerLevel":65,"attackSpeed":4,"attackLevel":260,"strengthLevel":230,"defenceLevel":120,"rangeLevel":1,"magicLevel":1},"7405":{"name":"King kurask","hitpoints":420,"combatLevel":295,"slayerLevel":70,"attackSpeed":4,"attackLevel":190,"strengthLevel":320,"defenceLevel":250,"rangeLevel":1,"magicLevel":1,"slashDef":50,"crushDef":50,"poisonImmune":true,"venomImmune":true},"7406":{"name":"Nuclear smoke devil","hitpoints":240,"combatLevel":280,"slayerLevel":93,"attackSpeed":4,"attackLevel":240,"strengthLevel":230,"defenceLevel":390,"rangeLevel":280,"magicLevel":1,"rangeDef":80,"magicDef":850,"poisonImmune":true,"venomImmune":true},"7407":{"name":"Marble gargoyle","hitpoints":270,"combatLevel":349,"slayerLevel":75,"attackSpeed":4,"attackLevel":230,"strengthLevel":250,"defenceLevel":190,"rangeLevel":220,"magicLevel":1,"stabDef":50,"slashDef":50,"rangeDef":50,"magicDef":50,"bonusRangeStrength":20,"venomImmune":true},"7408":{"name":"Marble gargoyle","hitpoints":270,"combatLevel":349,"slayerLevel":75,"attackSpeed":4,"attackLevel":230,"strengthLevel":250,"defenceLevel":190,"rangeLevel":220,"magicLevel":1,"stabDef":50,"slashDef":50,"rangeDef":50,"magicDef":50,"bonusRangeStrength":20,"venomImmune":true},"7409":{"name":"Night beast","hitpoints":550,"combatLevel":374,"slayerLevel":90,"attackSpeed":4,"attackLevel":270,"strengthLevel":290,"defenceLevel":220,"rangeLevel":1,"magicLevel":300,"stabDef":75,"slashDef":80,"crushDef":200,"rangeDef":200,"magicDef":190,"venomImmune":true},"7410":{"name":"Greater abyssal demon","hitpoints":400,"combatLevel":342,"slayerLevel":85,"attackSpeed":4,"attackLevel":300,"strengthLevel":260,"defenceLevel":240,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"venomImmune":true},"7411":{"name":"Nechryarch","hitpoints":320,"combatLevel":300,"slayerLevel":80,"attackSpeed":4,"attackLevel":310,"strengthLevel":260,"defenceLevel":140,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":30},"7416":{"name":"Obor","hitpoints":120,"combatLevel":106,"attackSpeed":6,"attackLevel":90,"strengthLevel":100,"defenceLevel":60,"rangeLevel":120,"magicLevel":1,"range":100,"stabDef":35,"slashDef":40,"crushDef":45,"rangeDef":20,"magicDef":20,"bonusAttack":100,"bonusStrength":68,"bonusRangeStrength":65},"7418":{"name":"Zamorak warrior","hitpoints":50,"combatLevel":84,"attackSpeed":4,"attackLevel":85,"strengthLevel":70,"defenceLevel":90,"rangeLevel":1,"magicLevel":1},"7419":{"name":"Zamorak warrior","hitpoints":50,"combatLevel":85,"attackSpeed":4,"attackLevel":90,"strengthLevel":70,"defenceLevel":90,"rangeLevel":1,"magicLevel":1},"7420":{"name":"Zamorak ranger","hitpoints":50,"combatLevel":81,"attackSpeed":4,"attackLevel":75,"strengthLevel":75,"defenceLevel":80,"rangeLevel":80,"magicLevel":1},"7421":{"name":"Zamorak ranger","hitpoints":50,"combatLevel":82,"attackSpeed":4,"attackLevel":75,"strengthLevel":80,"defenceLevel":80,"rangeLevel":80,"magicLevel":1},"7422":{"name":"Zamorak mage","hitpoints":50,"combatLevel":84,"attackSpeed":4,"attackLevel":78,"strengthLevel":81,"defenceLevel":82,"rangeLevel":1,"magicLevel":85},"7423":{"name":"Zamorak mage","hitpoints":50,"combatLevel":82,"attackSpeed":4,"attackLevel":75,"strengthLevel":80,"defenceLevel":80,"rangeLevel":1,"magicLevel":85},"7424":{"name":"Cave lizard","hitpoints":20,"combatLevel":37,"attackSpeed":4,"attackLevel":38,"strengthLevel":36,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"7426":{"name":"Zamorak crafter","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":15,"strengthLevel":15,"defenceLevel":12,"rangeLevel":1,"magicLevel":20},"7427":{"name":"Zamorak crafter","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":15,"strengthLevel":15,"defenceLevel":12,"rangeLevel":1,"magicLevel":20},"7485":{"name":"Zombie","hitpoints":65,"combatLevel":70,"attackSpeed":4,"attackLevel":61,"strengthLevel":60,"defenceLevel":61,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"7486":{"name":"Zombie","hitpoints":50,"combatLevel":56,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":47,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"7487":{"name":"Zombie","hitpoints":71,"combatLevel":76,"attackSpeed":4,"attackLevel":69,"strengthLevel":65,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"7488":{"name":"Zombie","hitpoints":68,"combatLevel":72,"attackSpeed":4,"attackLevel":61,"strengthLevel":63,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"rangeDef":10,"bonusStrength":10},"7513":{"name":"Derwen","hitpoints":320,"combatLevel":235,"attackSpeed":6,"attackLevel":250,"strengthLevel":150,"defenceLevel":100,"rangeLevel":1,"magicLevel":80,"magic":180,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"bonusMagicDamage":80,"poisonImmune":true,"venomImmune":true},"7515":{"name":"Porazdir","hitpoints":320,"combatLevel":235,"attackSpeed":6,"attackLevel":250,"strengthLevel":150,"defenceLevel":100,"rangeLevel":1,"magicLevel":180,"magic":80,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"bonusMagicDamage":80,"poisonImmune":true,"venomImmune":true},"7527":{"name":"Vanguard","hitpoints":280,"attackSpeed":4,"attackLevel":225,"strengthLevel":225,"defenceLevel":240,"rangeLevel":225,"magicLevel":225,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":400,"magicDef":20,"bonusAttack":20,"bonusStrength":10},"7528":{"name":"Vanguard","hitpoints":280,"attackSpeed":4,"attackLevel":225,"strengthLevel":225,"defenceLevel":240,"rangeLevel":225,"magicLevel":225,"range":40,"stabDef":55,"slashDef":60,"crushDef":100,"rangeDef":300,"magicDef":400,"bonusRangeStrength":25},"7529":{"name":"Vanguard","hitpoints":280,"attackSpeed":4,"attackLevel":225,"strengthLevel":225,"defenceLevel":240,"rangeLevel":225,"magicLevel":225,"magic":40,"stabDef":315,"slashDef":340,"crushDef":400,"rangeDef":50,"magicDef":110,"bonusMagicDamage":25},"7530":{"name":"Vespula","hitpoints":200,"attackSpeed":3,"rangeDef":60,"magicDef":70},"7531":{"name":"Vespula","hitpoints":200,"attackSpeed":3,"rangeDef":60,"magicDef":70},"7532":{"name":"Vespula","hitpoints":200,"attackSpeed":3,"rangeDef":60,"magicDef":70},"7533":{"name":"Abyssal portal","hitpoints":250,"attackSpeed":2,"attackLevel":1,"strengthLevel":1,"rangeLevel":1,"rangeDef":140,"magicDef":60},"7538":{"name":"Vespine soldier","hitpoints":100,"attackSpeed":4,"rangeLevel":1,"magicDef":30},"7539":{"name":"Vespine soldier","hitpoints":100,"attackSpeed":4,"rangeLevel":1,"magicDef":30},"7540":{"name":"Tekton","hitpoints":300,"attackSpeed":3,"attackLevel":390,"strengthLevel":390,"defenceLevel":205,"rangeLevel":1,"magicLevel":205,"stabDef":155,"slashDef":165,"crushDef":105,"bonusAttack":64,"bonusStrength":20},"7541":{"name":"Tekton","hitpoints":300,"attackSpeed":3,"attackLevel":390,"strengthLevel":390,"defenceLevel":205,"rangeLevel":1,"magicLevel":205,"stabDef":155,"slashDef":165,"crushDef":105,"bonusAttack":64,"bonusStrength":20},"7542":{"name":"Tekton","hitpoints":300,"attackSpeed":3,"attackLevel":390,"strengthLevel":390,"defenceLevel":205,"rangeLevel":1,"magicLevel":205,"stabDef":155,"slashDef":165,"crushDef":105,"bonusAttack":64,"bonusStrength":20},"7543":{"name":"Tekton (enraged)","hitpoints":300,"attackSpeed":3,"attackLevel":390,"strengthLevel":390,"defenceLevel":205,"rangeLevel":1,"magicLevel":205,"stabDef":280,"slashDef":290,"crushDef":180,"bonusAttack":64,"bonusStrength":30},"7544":{"name":"Tekton (enraged)","hitpoints":420,"attackSpeed":3,"attackLevel":585,"strengthLevel":585,"defenceLevel":246,"rangeLevel":1,"magicLevel":246,"stabDef":280,"slashDef":290,"crushDef":180,"bonusAttack":64,"bonusStrength":30},"7545":{"name":"Tekton","hitpoints":420,"attackSpeed":3,"attackLevel":585,"strengthLevel":585,"defenceLevel":246,"rangeLevel":1,"magicLevel":246,"stabDef":155,"slashDef":165,"crushDef":105,"bonusAttack":64,"bonusStrength":20},"7548":{"name":"Scavenger beast","hitpoints":30,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":45,"rangeLevel":1,"magicLevel":1},"7549":{"name":"Scavenger beast","hitpoints":45,"attackSpeed":4,"attackLevel":180,"strengthLevel":180,"defenceLevel":67,"rangeLevel":1,"magicLevel":1},"7550":{"name":"Great Olm (Right claw)","hitpoints":600,"combatLevel":549,"attackSpeed":4,"attackLevel":250,"strengthLevel":250,"defenceLevel":175,"rangeLevel":250,"magicLevel":87,"range":60,"magic":60,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"magicDef":50},"7551":{"name":"Great Olm","hitpoints":800,"combatLevel":1043,"attackSpeed":4,"attackLevel":250,"strengthLevel":250,"defenceLevel":150,"rangeLevel":250,"magicLevel":250,"range":60,"magic":60,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":50,"magicDef":200},"7552":{"name":"Great Olm (Left claw)","hitpoints":600,"combatLevel":750,"attackSpeed":4,"attackLevel":250,"strengthLevel":250,"defenceLevel":175,"rangeLevel":250,"magicLevel":175,"range":60,"magic":60,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50},"7553":{"name":"Great Olm (Right claw)","hitpoints":600,"combatLevel":549,"attackSpeed":4,"attackLevel":375,"strengthLevel":375,"defenceLevel":262,"rangeLevel":375,"magicLevel":130,"range":60,"magic":60,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"magicDef":50},"7554":{"name":"Great Olm","hitpoints":800,"combatLevel":1043,"attackSpeed":4,"attackLevel":375,"strengthLevel":375,"defenceLevel":225,"rangeLevel":375,"magicLevel":375,"range":60,"magic":60,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":50,"magicDef":200},"7555":{"name":"Great Olm (Left claw)","hitpoints":600,"combatLevel":750,"attackSpeed":4,"attackLevel":375,"strengthLevel":375,"defenceLevel":262,"rangeLevel":375,"magicLevel":262,"range":60,"magic":60,"stabDef":50,"slashDef":50,"crushDef":50,"rangeDef":50,"magicDef":50},"7559":{"name":"Deathly ranger","hitpoints":120,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"range":120,"bonusRangeStrength":70},"7560":{"name":"Deathly mage","hitpoints":120,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"rangeLevel":1,"magic":120},"7561":{"name":"Muttadile","hitpoints":250,"attackSpeed":4},"7562":{"name":"Muttadile","hitpoints":250,"attackSpeed":4},"7563":{"name":"Muttadile","hitpoints":250,"attackSpeed":4},"7566":{"name":"Vasa Nistirio","hitpoints":300,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"range":100,"stabDef":170,"slashDef":190,"crushDef":50,"rangeDef":60,"magicDef":400},"7567":{"name":"Vasa Nistirio","hitpoints":300,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"range":100,"stabDef":170,"slashDef":190,"crushDef":50,"rangeDef":60,"magicDef":400},"7568":{"name":"Glowing crystal","hitpoints":120,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"rangeLevel":1,"slashDef":180,"crushDef":180},"7569":{"name":"Guardian","hitpoints":250,"attackSpeed":4,"attackLevel":140,"strengthLevel":140,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":80,"slashDef":180,"bonusStrength":20},"7570":{"name":"Guardian","hitpoints":375,"attackSpeed":4,"attackLevel":210,"strengthLevel":210,"defenceLevel":150,"rangeLevel":1,"magicLevel":1,"stabDef":80,"slashDef":180,"bonusStrength":20},"7571":{"name":"Guardian","hitpoints":250,"attackSpeed":4,"attackLevel":140,"strengthLevel":140,"defenceLevel":100,"rangeLevel":1,"magicLevel":1,"stabDef":80,"slashDef":180,"bonusStrength":20},"7572":{"name":"Guardian","hitpoints":375,"attackSpeed":4,"attackLevel":210,"strengthLevel":210,"defenceLevel":150,"rangeLevel":1,"magicLevel":1,"stabDef":80,"slashDef":180,"bonusStrength":20},"7573":{"name":"Lizardman shaman","hitpoints":190,"attackSpeed":4,"range":56,"stabDef":102,"slashDef":160,"crushDef":150,"magicDef":160,"bonusAttack":58,"bonusStrength":52,"bonusRangeStrength":49},"7574":{"name":"Lizardman shaman","hitpoints":190,"attackSpeed":4,"range":56,"stabDef":102,"slashDef":160,"crushDef":150,"magicDef":160,"bonusAttack":58,"bonusStrength":52,"bonusRangeStrength":49},"7584":{"name":"Ice demon","hitpoints":140,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":160,"rangeLevel":390,"magicLevel":390,"stabDef":70,"slashDef":70,"crushDef":110,"rangeDef":140,"magicDef":60},"7585":{"name":"Ice demon","hitpoints":210,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":240,"rangeLevel":585,"magicLevel":585,"stabDef":70,"slashDef":70,"crushDef":110,"rangeDef":140,"magicDef":60},"7604":{"name":"Skeletal Mystic","hitpoints":160,"attackSpeed":4,"rangeLevel":1,"magic":40,"stabDef":155,"slashDef":155,"crushDef":115,"rangeDef":115,"magicDef":140,"bonusAttack":85,"bonusStrength":50,"bonusMagicDamage":38},"7605":{"name":"Skeletal Mystic","hitpoints":160,"attackSpeed":4,"rangeLevel":1,"magic":40,"stabDef":155,"slashDef":155,"crushDef":115,"rangeDef":115,"magicDef":140,"bonusAttack":85,"bonusStrength":50,"bonusMagicDamage":38},"7606":{"name":"Skeletal Mystic","hitpoints":160,"attackSpeed":4,"rangeLevel":1,"magic":40,"stabDef":155,"slashDef":155,"crushDef":115,"rangeDef":115,"magicDef":140,"bonusAttack":85,"bonusStrength":50,"bonusMagicDamage":38},"7620":{"name":"Temple guardian","hitpoints":45,"combatLevel":30,"attackSpeed":4,"attackLevel":20,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"7621":{"name":"Khazard warlord","hitpoints":170,"combatLevel":112,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"7622":{"name":"Khazard warlord","hitpoints":170,"combatLevel":112,"attackSpeed":4,"attackLevel":75,"strengthLevel":78,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"7649":{"name":"Chaotic death spawn","hitpoints":50,"combatLevel":215,"attackSpeed":3,"attackLevel":380,"strengthLevel":1,"defenceLevel":70,"rangeLevel":380,"magicLevel":380},"7656":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7657":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7658":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"7659":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"7660":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"7661":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"7662":{"name":"Mummy","hitpoints":90,"combatLevel":84,"attackSpeed":4,"attackLevel":90,"strengthLevel":30,"defenceLevel":90,"rangeLevel":1,"magicLevel":1,"stabDef":90,"slashDef":90,"crushDef":30,"magicDef":90,"bonusStrength":30},"7664":{"name":"Lesser demon","hitpoints":81,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7679":{"name":"TzHaar-Ket","hitpoints":200,"combatLevel":221,"attackSpeed":4,"attackLevel":200,"strengthLevel":180,"defenceLevel":190,"rangeLevel":1,"magicLevel":40},"7682":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"7683":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"7684":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"7685":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"7686":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"7687":{"name":"TzHaar-Hur","hitpoints":80,"combatLevel":74,"attackSpeed":5,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":80},"7691":{"name":"Jal-Nib","hitpoints":10,"combatLevel":32,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":15,"rangeLevel":1,"magicLevel":15},"7692":{"name":"Jal-MejRah","hitpoints":25,"combatLevel":85,"attackSpeed":3,"defenceLevel":55,"rangeLevel":120,"magicLevel":120,"range":30,"stabDef":30,"slashDef":30,"crushDef":30,"rangeDef":45,"bonusRangeStrength":30},"7693":{"name":"Jal-Ak","hitpoints":40,"combatLevel":165,"attackSpeed":6,"attackLevel":160,"strengthLevel":160,"defenceLevel":95,"rangeLevel":160,"magicLevel":160,"range":45,"magic":45,"stabDef":25,"slashDef":25,"crushDef":25,"rangeDef":25,"magicDef":25,"bonusStrength":45,"bonusRangeStrength":45,"bonusMagicDamage":45},"7694":{"name":"Jal-AkRek-Mej","hitpoints":15,"combatLevel":70,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":95,"rangeLevel":1,"magicLevel":120,"magic":25,"magicDef":25,"bonusMagicDamage":25},"7695":{"name":"Jal-AkRek-Xil","hitpoints":15,"combatLevel":70,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":95,"rangeLevel":120,"magicLevel":1,"range":25,"rangeDef":25,"bonusRangeStrength":25},"7696":{"name":"Jal-AkRek-Ket","hitpoints":15,"combatLevel":70,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":25,"crushDef":25,"bonusStrength":25},"7697":{"name":"Jal-ImKot","hitpoints":75,"combatLevel":240,"attackSpeed":4,"attackLevel":210,"strengthLevel":290,"defenceLevel":120,"rangeLevel":220,"magicLevel":120,"stabDef":65,"slashDef":65,"crushDef":65,"rangeDef":50,"magicDef":30,"bonusStrength":40},"7698":{"name":"Jal-Xil","hitpoints":130,"combatLevel":370,"attackSpeed":4,"attackLevel":140,"strengthLevel":180,"defenceLevel":60,"rangeLevel":250,"magicLevel":90,"range":40,"bonusRangeStrength":50},"7699":{"name":"Jal-Zek","hitpoints":220,"combatLevel":490,"attackSpeed":4,"attackLevel":370,"strengthLevel":510,"defenceLevel":260,"rangeLevel":510,"magicLevel":300,"magic":80},"7700":{"name":"JalTok-Jad","hitpoints":350,"combatLevel":900,"attackSpeed":8,"attackLevel":750,"strengthLevel":1020,"defenceLevel":480,"rangeLevel":1020,"magicLevel":510,"range":80,"magic":100,"bonusMagicDamage":75},"7701":{"name":"Yt-HurKot","hitpoints":90,"combatLevel":141,"attackSpeed":4,"attackLevel":165,"strengthLevel":125,"defenceLevel":100,"rangeLevel":150,"magicLevel":150,"range":80,"magic":100,"rangeDef":130,"magicDef":130},"7702":{"name":"Jal-Xil","hitpoints":130,"combatLevel":370,"attackSpeed":4,"attackLevel":140,"strengthLevel":180,"defenceLevel":60,"rangeLevel":250,"magicLevel":90,"range":40,"bonusRangeStrength":50},"7703":{"name":"Jal-Zek","hitpoints":220,"combatLevel":490,"attackSpeed":4,"attackLevel":370,"strengthLevel":510,"defenceLevel":260,"rangeLevel":510,"magicLevel":300,"magic":80},"7704":{"name":"JalTok-Jad","hitpoints":350,"combatLevel":900,"attackSpeed":8,"attackLevel":750,"strengthLevel":1020,"defenceLevel":480,"rangeLevel":1020,"magicLevel":510,"range":80,"magic":100,"bonusMagicDamage":75},"7705":{"name":"Yt-HurKot","hitpoints":90,"combatLevel":141,"attackSpeed":4,"attackLevel":165,"strengthLevel":125,"defenceLevel":100,"rangeLevel":150,"magicLevel":150,"range":80,"magic":100,"rangeDef":130,"magicDef":130},"7706":{"name":"TzKal-Zuk","hitpoints":1200,"combatLevel":1400,"attackLevel":350,"strengthLevel":600,"defenceLevel":260,"rangeLevel":400,"magicLevel":150,"range":550,"magic":550,"rangeDef":100,"magicDef":350,"bonusStrength":200,"bonusRangeStrength":200,"bonusMagicDamage":450},"7708":{"name":"Jal-MejJak","hitpoints":80,"combatLevel":250,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":1,"magicLevel":1},"7744":{"name":"Lizardman shaman","hitpoints":150,"combatLevel":150,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":140,"rangeLevel":120,"magicLevel":130,"range":45,"slashDef":40,"crushDef":30,"magicDef":50,"bonusAttack":45,"bonusStrength":38,"bonusRangeStrength":38},"7745":{"name":"Lizardman shaman","hitpoints":150,"combatLevel":150,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":140,"rangeLevel":120,"magicLevel":130,"range":45,"slashDef":40,"crushDef":30,"magicDef":50,"bonusAttack":45,"bonusStrength":38,"bonusRangeStrength":38},"7792":{"name":"Long-tailed Wyvern","hitpoints":200,"combatLevel":152,"slayerLevel":66,"attackSpeed":6,"attackLevel":125,"strengthLevel":120,"defenceLevel":90,"rangeLevel":90,"magicLevel":90,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":120,"magicDef":140},"7793":{"name":"Taloned Wyvern","hitpoints":200,"combatLevel":147,"slayerLevel":66,"attackSpeed":6,"attackLevel":120,"strengthLevel":110,"defenceLevel":90,"rangeLevel":90,"magicLevel":90,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":120,"magicDef":140},"7794":{"name":"Spitting Wyvern","hitpoints":200,"combatLevel":139,"slayerLevel":66,"attackSpeed":6,"attackLevel":125,"strengthLevel":80,"defenceLevel":90,"rangeLevel":125,"magicLevel":125,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":120,"magicDef":140},"7795":{"name":"Ancient Wyvern","hitpoints":300,"combatLevel":210,"slayerLevel":82,"attackSpeed":5,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":90,"magicLevel":90,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":120,"magicDef":170},"7796":{"name":"Lobstrosity","hitpoints":50,"combatLevel":68,"attackSpeed":4,"attackLevel":60,"strengthLevel":80,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":15,"rangeDef":70,"magicDef":99},"7797":{"name":"Ancient Zygomite","hitpoints":150,"combatLevel":109,"slayerLevel":57,"attackSpeed":4,"attackLevel":80,"strengthLevel":80,"defenceLevel":80,"rangeLevel":80,"magicLevel":80,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":30,"magicDef":30,"bonusAttack":50},"7799":{"name":"Ammonite Crab","hitpoints":100,"combatLevel":25,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"7802":{"name":"Hoop Snake","hitpoints":25,"combatLevel":19,"attackSpeed":4,"attackLevel":15,"strengthLevel":10,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"7804":{"name":"Tar Monster","hitpoints":200,"combatLevel":132,"attackSpeed":4,"attackLevel":120,"strengthLevel":80,"defenceLevel":70,"rangeLevel":100,"magicLevel":1},"7806":{"name":"Deranged archaeologist","hitpoints":200,"combatLevel":276,"attackSpeed":3,"attackLevel":280,"strengthLevel":160,"defenceLevel":280,"rangeLevel":320,"magicLevel":1,"range":90,"stabDef":20,"slashDef":20,"crushDef":50,"rangeDef":300,"magicDef":300,"bonusAttack":280,"bonusStrength":30,"bonusRangeStrength":90},"7817":{"name":"Lava beast","hitpoints":65,"attackSpeed":4},"7851":{"name":"Dusk","hitpoints":450,"combatLevel":248,"slayerLevel":75,"attackSpeed":6,"attackLevel":200,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":140},"7852":{"name":"Dawn","hitpoints":450,"combatLevel":228,"slayerLevel":75,"attackSpeed":6,"attackLevel":140,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":100,"magicDef":80,"poisonImmune":true,"venomImmune":true},"7853":{"name":"Dawn","hitpoints":450,"combatLevel":228,"slayerLevel":75,"attackSpeed":6,"attackLevel":140,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":100,"magicDef":80,"poisonImmune":true,"venomImmune":true},"7854":{"name":"Dusk","hitpoints":450,"combatLevel":248,"slayerLevel":75,"attackSpeed":6,"attackLevel":200,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":140},"7855":{"name":"Dusk","hitpoints":450,"combatLevel":248,"slayerLevel":75,"attackSpeed":6,"attackLevel":200,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":140},"7858":{"name":"Justiciar Zachariah","hitpoints":320,"combatLevel":348,"attackSpeed":6,"attackLevel":500,"strengthLevel":250,"defenceLevel":100,"rangeLevel":1,"magicLevel":180,"magic":80,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"bonusAttack":200,"bonusMagicDamage":80,"poisonImmune":true,"venomImmune":true},"7859":{"name":"Derwen","hitpoints":320,"combatLevel":235,"attackSpeed":6,"attackLevel":250,"strengthLevel":150,"defenceLevel":100,"rangeLevel":1,"magicLevel":80,"magic":180,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"bonusMagicDamage":80,"poisonImmune":true,"venomImmune":true},"7860":{"name":"Porazdir","hitpoints":320,"combatLevel":235,"attackSpeed":6,"attackLevel":250,"strengthLevel":150,"defenceLevel":100,"rangeLevel":1,"magicLevel":180,"magic":80,"stabDef":200,"slashDef":200,"crushDef":200,"rangeDef":200,"bonusMagicDamage":80,"poisonImmune":true,"venomImmune":true},"7861":{"name":"Black dragon","hitpoints":250,"combatLevel":247,"attackSpeed":4,"attackLevel":200,"strengthLevel":215,"defenceLevel":200,"rangeLevel":1,"magicLevel":150,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"7862":{"name":"Black dragon","hitpoints":250,"combatLevel":247,"attackSpeed":4,"attackLevel":200,"strengthLevel":215,"defenceLevel":200,"rangeLevel":1,"magicLevel":150,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"7863":{"name":"Black dragon","hitpoints":250,"combatLevel":247,"attackSpeed":4,"attackLevel":200,"strengthLevel":215,"defenceLevel":200,"rangeLevel":1,"magicLevel":150,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"7864":{"name":"Ankou","hitpoints":100,"combatLevel":98,"attackSpeed":4,"attackLevel":75,"strengthLevel":90,"defenceLevel":80,"rangeLevel":1,"magicLevel":1},"7865":{"name":"Lesser demon","hitpoints":110,"combatLevel":94,"attackSpeed":4,"attackLevel":68,"strengthLevel":85,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7866":{"name":"Lesser demon","hitpoints":110,"combatLevel":94,"attackSpeed":4,"attackLevel":68,"strengthLevel":85,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7867":{"name":"Lesser demon","hitpoints":110,"combatLevel":94,"attackSpeed":4,"attackLevel":68,"strengthLevel":85,"defenceLevel":71,"rangeLevel":1,"magicLevel":1},"7868":{"name":"Green dragon","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":68,"strengthLevel":75,"defenceLevel":68,"rangeLevel":1,"magicLevel":75,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"7869":{"name":"Green dragon","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":68,"strengthLevel":75,"defenceLevel":68,"rangeLevel":1,"magicLevel":75,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"7870":{"name":"Green dragon","hitpoints":100,"combatLevel":88,"attackSpeed":4,"attackLevel":68,"strengthLevel":75,"defenceLevel":68,"rangeLevel":1,"magicLevel":75,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"7871":{"name":"Greater demon","hitpoints":120,"combatLevel":104,"attackSpeed":4,"attackLevel":76,"strengthLevel":90,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"7872":{"name":"Greater demon","hitpoints":120,"combatLevel":104,"attackSpeed":4,"attackLevel":76,"strengthLevel":90,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"7873":{"name":"Greater demon","hitpoints":120,"combatLevel":104,"attackSpeed":4,"attackLevel":76,"strengthLevel":90,"defenceLevel":81,"rangeLevel":1,"magicLevel":1},"7874":{"name":"Black demon","hitpoints":200,"combatLevel":188,"attackSpeed":4,"attackLevel":145,"strengthLevel":165,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"7875":{"name":"Black demon","hitpoints":200,"combatLevel":188,"attackSpeed":4,"attackLevel":145,"strengthLevel":165,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"7876":{"name":"Black demon","hitpoints":200,"combatLevel":188,"attackSpeed":4,"attackLevel":145,"strengthLevel":165,"defenceLevel":152,"rangeLevel":1,"magicLevel":1},"7877":{"name":"Hellhound","hitpoints":150,"combatLevel":136,"attackSpeed":4,"attackLevel":105,"strengthLevel":120,"defenceLevel":102,"rangeLevel":1,"magicLevel":1},"7878":{"name":"Ice giant","hitpoints":100,"combatLevel":67,"attackSpeed":5,"attackLevel":40,"strengthLevel":60,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"7879":{"name":"Ice giant","hitpoints":100,"combatLevel":67,"attackSpeed":5,"attackLevel":40,"strengthLevel":60,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"7880":{"name":"Ice giant","hitpoints":100,"combatLevel":67,"attackSpeed":5,"attackLevel":40,"strengthLevel":60,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"slashDef":3,"crushDef":2,"bonusAttack":29,"bonusStrength":31},"7881":{"name":"Revenant imp","hitpoints":10,"combatLevel":7,"attackSpeed":5,"attackLevel":5,"strengthLevel":5,"defenceLevel":4,"rangeLevel":5,"magicLevel":9,"magic":5,"magicDef":5},"7882":{"name":"Dusk","hitpoints":450,"combatLevel":248,"slayerLevel":75,"attackSpeed":6,"attackLevel":200,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":140},"7883":{"name":"Dusk","hitpoints":450,"combatLevel":248,"slayerLevel":75,"attackSpeed":6,"attackLevel":200,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":140},"7884":{"name":"Dawn","hitpoints":450,"combatLevel":228,"slayerLevel":75,"attackSpeed":6,"attackLevel":140,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":100,"magicDef":80,"poisonImmune":true,"venomImmune":true},"7885":{"name":"Dawn","hitpoints":450,"combatLevel":228,"slayerLevel":75,"attackSpeed":6,"attackLevel":140,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":100,"magicDef":80,"poisonImmune":true,"venomImmune":true},"7886":{"name":"Dusk","hitpoints":450,"combatLevel":248,"slayerLevel":75,"attackSpeed":6,"attackLevel":200,"strengthLevel":140,"defenceLevel":100,"rangeLevel":140,"magicLevel":140},"7887":{"name":"Dusk","hitpoints":450,"combatLevel":328,"slayerLevel":75,"attackSpeed":6,"attackLevel":300,"strengthLevel":250,"defenceLevel":150,"rangeLevel":250,"magicLevel":250},"7888":{"name":"Dusk","hitpoints":450,"combatLevel":328,"slayerLevel":75,"attackSpeed":6,"attackLevel":300,"strengthLevel":250,"defenceLevel":150,"rangeLevel":250,"magicLevel":250},"7889":{"name":"Dusk","hitpoints":450,"combatLevel":328,"slayerLevel":75,"attackSpeed":6,"attackLevel":300,"strengthLevel":250,"defenceLevel":150,"rangeLevel":250,"magicLevel":250},"7894":{"name":"Sand Snake (hard)","hitpoints":180,"combatLevel":154,"attackSpeed":4,"attackLevel":180,"strengthLevel":140,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"7895":{"name":"Sand Snake","hitpoints":60,"combatLevel":36,"attackSpeed":4,"attackLevel":30,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"7903":{"name":"Sand Snake","hitpoints":60,"combatLevel":36,"attackSpeed":4,"attackLevel":30,"strengthLevel":20,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"7914":{"name":"Thief","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16,"magicDef":4,"bonusAttack":9,"bonusStrength":7},"7915":{"name":"Thief","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16,"magicDef":4,"bonusAttack":9,"bonusStrength":7},"7916":{"name":"Thief","hitpoints":22,"combatLevel":21,"attackSpeed":4,"attackLevel":19,"strengthLevel":18,"defenceLevel":14,"rangeLevel":1,"magicLevel":1,"stabDef":24,"slashDef":14,"crushDef":19,"rangeDef":16,"magicDef":4,"bonusAttack":9,"bonusStrength":7},"7931":{"name":"Revenant goblin","hitpoints":14,"combatLevel":15,"attackSpeed":5,"attackLevel":13,"strengthLevel":14,"defenceLevel":14,"rangeLevel":15,"magicLevel":12,"range":21,"magic":37,"stabDef":25,"slashDef":28,"crushDef":31,"rangeDef":31,"magicDef":1,"bonusAttack":6,"bonusStrength":8,"bonusRangeStrength":12,"bonusMagicDamage":12},"7932":{"name":"Revenant pyrefiend","hitpoints":48,"combatLevel":52,"attackSpeed":5,"attackLevel":60,"strengthLevel":40,"defenceLevel":33,"rangeLevel":40,"magicLevel":67,"stabDef":45,"slashDef":40,"crushDef":50,"rangeDef":10,"magicDef":15},"7933":{"name":"Revenant hobgoblin","hitpoints":72,"combatLevel":60,"attackSpeed":4,"attackLevel":50,"strengthLevel":50,"defenceLevel":41,"rangeLevel":60,"magicLevel":55,"range":25,"magic":5,"stabDef":65,"slashDef":60,"crushDef":68,"rangeDef":50,"magicDef":30,"bonusAttack":20,"bonusStrength":24},"7934":{"name":"Revenant cyclops","hitpoints":110,"combatLevel":82,"attackSpeed":6,"attackLevel":60,"strengthLevel":73,"defenceLevel":49,"rangeLevel":70,"magicLevel":65,"stabDef":140,"slashDef":130,"crushDef":135,"rangeDef":135,"magicDef":10,"bonusAttack":53,"bonusStrength":64},"7935":{"name":"Revenant hellhound","hitpoints":80,"combatLevel":90,"attackSpeed":5,"attackLevel":76,"strengthLevel":80,"defenceLevel":80,"rangeLevel":80,"magicLevel":104,"magic":30,"stabDef":138,"slashDef":140,"crushDef":142,"rangeDef":140,"magicDef":62,"bonusAttack":38,"bonusStrength":40},"7936":{"name":"Revenant demon","hitpoints":80,"combatLevel":98,"attackSpeed":5,"attackLevel":83,"strengthLevel":76,"defenceLevel":80,"rangeLevel":80,"magicLevel":120,"range":40,"magic":50,"stabDef":124,"slashDef":118,"crushDef":130,"rangeDef":90,"magicDef":85,"bonusAttack":30,"bonusStrength":50,"bonusRangeStrength":30,"bonusMagicDamage":24},"7937":{"name":"Revenant ork","hitpoints":105,"combatLevel":105,"attackSpeed":5,"attackLevel":99,"strengthLevel":100,"defenceLevel":60,"rangeLevel":130,"magicLevel":110,"range":50,"stabDef":148,"slashDef":150,"crushDef":146,"rangeDef":148,"magicDef":50,"bonusAttack":60,"bonusStrength":55},"7938":{"name":"Revenant dark beast","hitpoints":140,"combatLevel":120,"attackSpeed":5,"attackLevel":93,"strengthLevel":110,"defenceLevel":80,"rangeLevel":135,"magicLevel":130,"range":45,"stabDef":153,"slashDef":152,"crushDef":155,"rangeDef":158,"magicDef":70,"bonusAttack":65,"bonusStrength":60},"7939":{"name":"Revenant knight","hitpoints":143,"combatLevel":126,"attackSpeed":5,"attackLevel":100,"strengthLevel":119,"defenceLevel":80,"rangeLevel":146,"magicLevel":146,"range":55,"magic":55,"stabDef":195,"slashDef":200,"crushDef":180,"rangeDef":190,"magicDef":95,"bonusAttack":69,"bonusStrength":71,"bonusRangeStrength":45,"bonusMagicDamage":50},"7940":{"name":"Revenant dragon","hitpoints":155,"combatLevel":135,"attackSpeed":5,"attackLevel":106,"strengthLevel":126,"defenceLevel":87,"rangeLevel":151,"magicLevel":150,"range":60,"magic":61,"stabDef":201,"slashDef":206,"crushDef":188,"rangeDef":197,"magicDef":101,"bonusAttack":72,"bonusStrength":78,"bonusRangeStrength":51,"bonusMagicDamage":35},"7948":{"name":"Corsair Traitor (hard)","hitpoints":160,"combatLevel":103,"attackSpeed":3,"attackLevel":1,"strengthLevel":1,"defenceLevel":80,"rangeLevel":1,"magicLevel":90},"7949":{"name":"Corsair Traitor","hitpoints":55,"combatLevel":35,"attackSpeed":5,"attackLevel":1,"strengthLevel":1,"defenceLevel":30,"rangeLevel":1,"magicLevel":30},"7955":{"name":"Baby black dragon","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":30,"slashDef":50,"crushDef":50,"rangeDef":30,"magicDef":40},"7964":{"name":"Ithoi the Navigator","hitpoints":55,"combatLevel":35,"attackSpeed":5,"attackLevel":1,"strengthLevel":1,"defenceLevel":30,"rangeLevel":1,"magicLevel":30},"7989":{"name":"Ogress Warrior","hitpoints":82,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":82,"rangeLevel":1,"magicLevel":60,"stabDef":10,"slashDef":12,"crushDef":12,"rangeDef":16,"magicDef":14},"7990":{"name":"Ogress Warrior","hitpoints":82,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":70,"defenceLevel":82,"rangeLevel":1,"magicLevel":60,"stabDef":10,"slashDef":12,"crushDef":12,"rangeDef":16,"magicDef":14},"7991":{"name":"Ogress Shaman","hitpoints":82,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":20,"defenceLevel":82,"rangeLevel":1,"magicLevel":68,"stabDef":12,"slashDef":14,"crushDef":14,"rangeDef":8,"magicDef":16},"7992":{"name":"Ogress Shaman","hitpoints":82,"combatLevel":82,"attackSpeed":4,"attackLevel":68,"strengthLevel":20,"defenceLevel":82,"rangeLevel":1,"magicLevel":68,"stabDef":12,"slashDef":14,"crushDef":14,"rangeDef":8,"magicDef":16},"7996":{"name":"Corrupt Lizardman (hard)","hitpoints":150,"combatLevel":152,"attackSpeed":4,"attackLevel":162,"strengthLevel":162,"defenceLevel":38,"rangeLevel":162,"magicLevel":1,"range":22,"slashDef":25,"bonusAttack":22,"bonusStrength":20,"bonusRangeStrength":20},"7997":{"name":"Corrupt Lizardman","hitpoints":50,"combatLevel":46,"attackSpeed":4,"attackLevel":38,"strengthLevel":38,"defenceLevel":38,"rangeLevel":38,"magicLevel":1,"range":22,"slashDef":25,"bonusAttack":22,"bonusStrength":20,"bonusRangeStrength":20},"8000":{"name":"Corrupt Lizardman","hitpoints":50,"combatLevel":46,"attackSpeed":4,"attackLevel":38,"strengthLevel":38,"defenceLevel":38,"rangeLevel":38,"magicLevel":1,"range":22,"slashDef":25,"bonusAttack":22,"bonusStrength":20,"bonusRangeStrength":20},"8027":{"name":"Rune dragon","hitpoints":330,"combatLevel":380,"attackSpeed":4,"attackLevel":284,"strengthLevel":284,"defenceLevel":276,"rangeLevel":246,"magicLevel":196,"stabDef":30,"slashDef":115,"crushDef":90,"rangeDef":95,"magicDef":30,"bonusRangeStrength":14,"poisonImmune":true,"venomImmune":true},"8030":{"name":"Adamant dragon","hitpoints":295,"combatLevel":338,"attackSpeed":4,"attackLevel":280,"strengthLevel":280,"defenceLevel":272,"rangeLevel":186,"magicLevel":186,"stabDef":30,"slashDef":110,"crushDef":85,"rangeDef":95,"magicDef":30},"8031":{"name":"Rune dragon","hitpoints":330,"combatLevel":380,"attackSpeed":4,"attackLevel":284,"strengthLevel":284,"defenceLevel":276,"rangeLevel":246,"magicLevel":196,"stabDef":30,"slashDef":115,"crushDef":90,"rangeDef":95,"magicDef":30,"bonusRangeStrength":14},"8033":{"name":"Elvarg","hitpoints":80,"combatLevel":83,"attackSpeed":4,"attackLevel":70,"strengthLevel":70,"defenceLevel":70,"rangeLevel":1,"magicLevel":70,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"8056":{"name":"Spawn","hitpoints":80,"combatLevel":100,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"range":1,"magic":1,"stabDef":5,"slashDef":15,"crushDef":15,"rangeDef":5},"8057":{"name":"Robert the Strong","hitpoints":280,"combatLevel":194,"attackSpeed":4,"defenceLevel":140,"rangeLevel":328,"magicLevel":128,"range":126,"stabDef":140,"slashDef":180,"crushDef":60,"rangeDef":860,"magicDef":940,"poisonImmune":true,"venomImmune":true},"8058":{"name":"Vorkath","hitpoints":460,"combatLevel":392,"attackSpeed":5,"attackLevel":560,"strengthLevel":268,"defenceLevel":164,"rangeLevel":268,"magicLevel":148,"range":96,"magic":148,"stabDef":66,"slashDef":126,"crushDef":126,"rangeDef":80,"magicDef":204,"bonusAttack":14},"8059":{"name":"Vorkath","hitpoints":750,"combatLevel":732,"attackSpeed":5,"attackLevel":560,"strengthLevel":308,"defenceLevel":214,"rangeLevel":308,"magicLevel":150,"range":78,"magic":150,"stabDef":26,"slashDef":108,"crushDef":108,"rangeDef":26,"magicDef":240,"bonusAttack":16},"8060":{"name":"Vorkath","hitpoints":460,"combatLevel":392,"attackSpeed":5,"attackLevel":560,"strengthLevel":268,"defenceLevel":164,"rangeLevel":268,"magicLevel":148,"range":96,"magic":148,"stabDef":66,"slashDef":126,"crushDef":126,"rangeDef":80,"magicDef":204,"bonusAttack":14},"8061":{"name":"Vorkath","hitpoints":750,"combatLevel":732,"attackSpeed":5,"attackLevel":560,"strengthLevel":308,"defenceLevel":214,"rangeLevel":308,"magicLevel":150,"range":78,"magic":150,"stabDef":26,"slashDef":108,"crushDef":108,"rangeDef":26,"magicDef":240,"bonusAttack":16},"8062":{"name":"Zombified Spawn","hitpoints":8,"combatLevel":55,"attackLevel":80,"strengthLevel":80,"defenceLevel":4,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":3,"crushDef":3,"rangeDef":3},"8063":{"name":"Zombified Spawn","hitpoints":38,"combatLevel":64,"attackLevel":82,"strengthLevel":82,"defenceLevel":6,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":3,"crushDef":3,"rangeDef":3},"8064":{"name":"Stone Guardian","hitpoints":62,"combatLevel":124,"attackSpeed":5,"attackLevel":150,"strengthLevel":148,"defenceLevel":138,"rangeLevel":1,"magicLevel":118},"8065":{"name":"Stone Guardian","hitpoints":62,"combatLevel":124,"attackSpeed":5,"attackLevel":1,"strengthLevel":1,"defenceLevel":138,"rangeLevel":128,"magicLevel":118},"8066":{"name":"Stone Guardian","hitpoints":62,"combatLevel":124,"attackSpeed":5,"attackLevel":1,"strengthLevel":1,"defenceLevel":138,"rangeLevel":1,"magicLevel":128},"8067":{"name":"Zombie","hitpoints":54,"combatLevel":132,"attackSpeed":5,"attackLevel":160,"strengthLevel":180,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":20,"crushDef":25,"rangeDef":30,"magicDef":10,"bonusAttack":30},"8068":{"name":"Zombie","hitpoints":54,"combatLevel":132,"attackSpeed":5,"attackLevel":160,"strengthLevel":1,"defenceLevel":62,"rangeLevel":180,"magicLevel":1,"range":20,"stabDef":15,"slashDef":10,"crushDef":15,"rangeDef":20,"magicDef":30},"8069":{"name":"Zombie","hitpoints":54,"combatLevel":132,"attackSpeed":5,"attackLevel":160,"strengthLevel":1,"defenceLevel":62,"rangeLevel":1,"magicLevel":180,"magic":20,"stabDef":35,"slashDef":30,"crushDef":35,"rangeDef":10,"magicDef":20},"8070":{"name":"Skeleton","hitpoints":54,"combatLevel":132,"attackSpeed":5,"attackLevel":160,"strengthLevel":180,"defenceLevel":62,"rangeLevel":1,"magicLevel":1,"stabDef":25,"slashDef":20,"crushDef":25,"rangeDef":30,"magicDef":10,"bonusAttack":30},"8071":{"name":"Skeleton","hitpoints":54,"combatLevel":132,"attackSpeed":5,"attackLevel":160,"strengthLevel":1,"defenceLevel":62,"rangeLevel":180,"magicLevel":1,"range":20,"stabDef":15,"slashDef":10,"crushDef":15,"rangeDef":20,"magicDef":30},"8072":{"name":"Skeleton","hitpoints":54,"combatLevel":132,"attackSpeed":5,"attackLevel":160,"strengthLevel":1,"defenceLevel":62,"rangeLevel":1,"magicLevel":180,"magic":20,"stabDef":35,"slashDef":30,"crushDef":35,"rangeDef":10,"magicDef":20},"8073":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"8075":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8076":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"8078":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8079":{"name":"Red dragon","hitpoints":140,"combatLevel":152,"attackSpeed":4,"attackLevel":130,"strengthLevel":130,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8080":{"name":"Iron dragon","hitpoints":165,"combatLevel":189,"attackSpeed":4,"attackLevel":165,"strengthLevel":165,"defenceLevel":165,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"8081":{"name":"Brutal green dragon","hitpoints":175,"combatLevel":227,"attackSpeed":4,"attackLevel":268,"strengthLevel":168,"defenceLevel":168,"magicLevel":168,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"poisonImmune":true,"venomImmune":true},"8082":{"name":"Green dragon","hitpoints":75,"combatLevel":79,"attackSpeed":4,"attackLevel":68,"strengthLevel":68,"defenceLevel":68,"rangeLevel":1,"magicLevel":68,"stabDef":20,"slashDef":40,"crushDef":40,"rangeDef":20,"magicDef":30},"8083":{"name":"Blue dragon","hitpoints":105,"combatLevel":111,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":95,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8084":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8085":{"name":"Black dragon","hitpoints":190,"combatLevel":227,"attackSpeed":4,"attackLevel":200,"strengthLevel":200,"defenceLevel":200,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8086":{"name":"Steel dragon","hitpoints":210,"combatLevel":246,"attackSpeed":4,"attackLevel":215,"strengthLevel":215,"defenceLevel":215,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":90,"magicDef":30},"8087":{"name":"Brutal red dragon","hitpoints":285,"combatLevel":289,"attackSpeed":4,"attackLevel":310,"strengthLevel":210,"defenceLevel":198,"magicLevel":250,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"poisonImmune":true,"venomImmune":true},"8088":{"name":"Mithril dragon","hitpoints":254,"combatLevel":304,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":268,"rangeLevel":168,"magicLevel":168,"stabDef":50,"slashDef":100,"crushDef":70,"rangeDef":90,"magicDef":30,"poisonImmune":true,"venomImmune":true},"8089":{"name":"Mithril dragon","hitpoints":254,"combatLevel":304,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":268,"rangeLevel":168,"magicLevel":168,"stabDef":50,"slashDef":100,"crushDef":70,"rangeDef":90,"magicDef":30,"poisonImmune":true,"venomImmune":true},"8090":{"name":"Adamant dragon","hitpoints":295,"combatLevel":338,"attackSpeed":4,"attackLevel":280,"strengthLevel":280,"defenceLevel":272,"rangeLevel":186,"magicLevel":186,"stabDef":30,"slashDef":110,"crushDef":85,"rangeDef":95,"magicDef":30},"8091":{"name":"Rune dragon","hitpoints":330,"combatLevel":380,"attackSpeed":4,"attackLevel":284,"strengthLevel":284,"defenceLevel":276,"rangeLevel":246,"magicLevel":196,"stabDef":30,"slashDef":115,"crushDef":90,"rangeDef":95,"magicDef":30,"bonusRangeStrength":14},"8092":{"name":"Brutal black dragon","hitpoints":315,"combatLevel":318,"slayerLevel":77,"attackSpeed":4,"attackLevel":330,"strengthLevel":210,"defenceLevel":258,"magicLevel":250,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"bonusStrength":20,"poisonImmune":true,"venomImmune":true},"8093":{"name":"Brutal black dragon","hitpoints":315,"combatLevel":318,"slayerLevel":77,"attackSpeed":4,"attackLevel":330,"strengthLevel":210,"defenceLevel":258,"magicLevel":250,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60,"bonusStrength":20,"poisonImmune":true,"venomImmune":true},"8094":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8095":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8096":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8097":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8098":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8137":{"name":"Spider","hitpoints":4,"combatLevel":35,"attackSpeed":4,"attackLevel":45,"strengthLevel":47,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":10,"rangeDef":20,"magicDef":17},"8138":{"name":"Spider","hitpoints":4,"combatLevel":35,"attackSpeed":4,"attackLevel":45,"strengthLevel":47,"defenceLevel":20,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"crushDef":10,"rangeDef":20,"magicDef":17},"8149":{"name":"White Knight","hitpoints":52,"combatLevel":36,"attackSpeed":7,"attackLevel":27,"strengthLevel":29,"defenceLevel":21,"rangeLevel":1,"magicLevel":1,"stabDef":83,"slashDef":76,"crushDef":70,"rangeDef":74,"bonusAttack":30,"bonusStrength":31},"8150":{"name":"Paladin","hitpoints":66,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"8177":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8178":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8179":{"name":"Galvek","hitpoints":1200,"combatLevel":608,"attackSpeed":6,"attackLevel":632,"strengthLevel":268,"defenceLevel":188,"rangeLevel":246,"magicLevel":160,"range":180,"magic":160,"stabDef":80,"slashDef":140,"crushDef":140,"rangeDef":86,"magicDef":280,"bonusAttack":34,"bonusRangeStrength":6,"bonusMagicDamage":42},"8194":{"name":"Growthling","hitpoints":10,"combatLevel":37,"attackSpeed":4,"attackLevel":40,"strengthLevel":38,"defenceLevel":40,"rangeLevel":1,"magicLevel":1,"poisonImmune":true,"venomImmune":true},"8195":{"name":"Bryophyta","hitpoints":115,"combatLevel":128,"attackSpeed":6,"attackLevel":130,"strengthLevel":100,"defenceLevel":100,"rangeLevel":1,"magicLevel":90,"bonusAttack":33,"bonusStrength":31},"8239":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"8240":{"name":"Vanstrom Klause","hitpoints":155,"combatLevel":169,"attackSpeed":8,"attackLevel":150,"strengthLevel":136,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"poisonImmune":true,"venomImmune":true},"8242":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":5,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":34,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8243":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":5,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":34,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8244":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":5,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":34,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8245":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":5,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":34,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8246":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":5,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":34,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8247":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":5,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusStrength":34,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8248":{"name":"Ranis Drakan","hitpoints":400,"combatLevel":233,"attackSpeed":2,"attackLevel":120,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":120,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":60,"magicDef":60,"bonusAttack":160,"bonusMagicDamage":34,"poisonImmune":true,"venomImmune":true},"8251":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8252":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8253":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8254":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8255":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8256":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8257":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8258":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8259":{"name":"Vyrewatch","hitpoints":75,"combatLevel":87,"attackSpeed":8,"attackLevel":80,"strengthLevel":75,"defenceLevel":75,"rangeLevel":1,"magicLevel":75},"8260":{"name":"Abomination","hitpoints":200,"combatLevel":149,"attackSpeed":5,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":110,"magicLevel":110,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":180,"magicDef":20},"8261":{"name":"Abomination","hitpoints":200,"combatLevel":149,"attackSpeed":5,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":110,"magicLevel":110,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":180,"magicDef":20},"8262":{"name":"Abomination","hitpoints":200,"combatLevel":149,"attackSpeed":5,"attackLevel":110,"strengthLevel":110,"defenceLevel":110,"rangeLevel":110,"magicLevel":110,"stabDef":80,"slashDef":80,"crushDef":80,"rangeDef":180,"magicDef":20},"8297":{"name":"Swamp Crab","hitpoints":75,"combatLevel":55,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":50,"rangeLevel":1,"magicLevel":50,"stabDef":100,"slashDef":100,"crushDef":100,"magicDef":100},"8298":{"name":"Swamp Crab","hitpoints":75,"combatLevel":55,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":50,"rangeLevel":1,"magicLevel":50,"stabDef":100,"slashDef":100,"crushDef":100,"magicDef":100},"8299":{"name":"Swampy log","hitpoints":75,"combatLevel":55,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":50,"rangeLevel":1,"magicLevel":50,"stabDef":100,"slashDef":100,"crushDef":100,"magicDef":100},"8300":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8301":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8302":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8303":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8304":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8305":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8306":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8307":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"8340":{"name":"Xarpus","hitpoints":5080,"combatLevel":960,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":250,"rangeLevel":1,"magicLevel":220,"range":57,"rangeDef":160},"8341":{"name":"Xarpus","hitpoints":5080,"combatLevel":960,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":250,"rangeLevel":1,"magicLevel":220,"range":57,"rangeDef":160},"8342":{"name":"Nylocas Ischyros","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8343":{"name":"Nylocas Toxobolos","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8344":{"name":"Nylocas Hagios","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8345":{"name":"Nylocas Ischyros","hitpoints":22,"combatLevel":260,"attackSpeed":3,"attackLevel":250,"strengthLevel":230,"defenceLevel":20,"rangeLevel":250,"magicLevel":20,"magic":500,"bonusMagicDamage":500},"8346":{"name":"Nylocas Toxobolos","hitpoints":22,"combatLevel":260,"attackSpeed":3,"attackLevel":250,"strengthLevel":230,"defenceLevel":20,"rangeLevel":250,"magicLevel":20,"magic":500,"bonusMagicDamage":500},"8347":{"name":"Nylocas Hagios","hitpoints":22,"combatLevel":260,"attackSpeed":3,"attackLevel":250,"strengthLevel":230,"defenceLevel":20,"rangeLevel":250,"magicLevel":20,"magic":500,"bonusMagicDamage":500},"8348":{"name":"Nylocas Ischyros","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8349":{"name":"Nylocas Toxobolos","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8350":{"name":"Nylocas Hagios","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8351":{"name":"Nylocas Ischyros","hitpoints":22,"combatLevel":260,"attackSpeed":3,"attackLevel":250,"strengthLevel":230,"defenceLevel":20,"rangeLevel":250,"magicLevel":20,"magic":500,"bonusMagicDamage":500},"8352":{"name":"Nylocas Toxobolos","hitpoints":22,"combatLevel":260,"attackSpeed":3,"attackLevel":250,"strengthLevel":230,"defenceLevel":20,"rangeLevel":250,"magicLevel":20,"magic":500,"bonusMagicDamage":500},"8353":{"name":"Nylocas Hagios","hitpoints":22,"combatLevel":260,"attackSpeed":3,"attackLevel":250,"strengthLevel":230,"defenceLevel":20,"rangeLevel":250,"magicLevel":20,"magic":500,"bonusMagicDamage":500},"8355":{"name":"Nylocas Vasilias","hitpoints":2500,"combatLevel":800,"attackSpeed":4,"attackLevel":400,"strengthLevel":350,"defenceLevel":50,"rangeLevel":350,"magicLevel":50,"magic":600,"bonusStrength":60,"bonusRangeStrength":60,"bonusMagicDamage":600},"8356":{"name":"Nylocas Vasilias","hitpoints":2500,"combatLevel":800,"attackSpeed":4,"attackLevel":400,"strengthLevel":350,"defenceLevel":50,"rangeLevel":350,"magicLevel":50,"magic":600,"bonusStrength":60,"bonusRangeStrength":60,"bonusMagicDamage":600},"8357":{"name":"Nylocas Vasilias","hitpoints":2500,"combatLevel":800,"attackSpeed":4,"attackLevel":400,"strengthLevel":350,"defenceLevel":50,"rangeLevel":350,"magicLevel":50,"magic":600,"bonusStrength":60,"bonusRangeStrength":60,"bonusMagicDamage":600},"8359":{"name":"Pestilent Bloat","hitpoints":2000,"combatLevel":870,"attackSpeed":1,"attackLevel":250,"strengthLevel":340,"defenceLevel":100,"rangeLevel":180,"magicLevel":150,"range":180,"stabDef":40,"slashDef":20,"crushDef":40,"rangeDef":800,"magicDef":600,"bonusAttack":150,"bonusStrength":82,"bonusRangeStrength":4},"8360":{"name":"The Maiden of Sugadinti","hitpoints":3500,"combatLevel":940,"attackSpeed":10,"attackLevel":350,"strengthLevel":350,"defenceLevel":200,"rangeLevel":350,"magicLevel":350,"magic":300},"8361":{"name":"The Maiden of Sugadinti","hitpoints":3500,"combatLevel":940,"attackSpeed":10,"attackLevel":350,"strengthLevel":350,"defenceLevel":200,"rangeLevel":350,"magicLevel":350,"magic":300},"8362":{"name":"The Maiden of Sugadinti","hitpoints":3500,"combatLevel":940,"attackSpeed":10,"attackLevel":350,"strengthLevel":350,"defenceLevel":200,"rangeLevel":350,"magicLevel":350,"magic":300},"8363":{"name":"The Maiden of Sugadinti","hitpoints":3500,"combatLevel":940,"attackSpeed":10,"attackLevel":350,"strengthLevel":350,"defenceLevel":200,"rangeLevel":350,"magicLevel":350,"magic":300},"8366":{"name":"Nylocas Matomenos","hitpoints":200,"combatLevel":115,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":100,"magicLevel":100},"8367":{"name":"Blood spawn","hitpoints":120,"combatLevel":55,"attackLevel":1,"strengthLevel":1,"rangeLevel":1},"8369":{"name":"Verzik Vitur","hitpoints":2000,"combatLevel":1040,"attackSpeed":4,"attackLevel":400,"strengthLevel":400,"defenceLevel":20,"rangeLevel":400,"magicLevel":400,"range":80,"magic":80,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusRangeStrength":80,"bonusMagicDamage":150},"8370":{"name":"Verzik Vitur","hitpoints":2000,"combatLevel":1040,"attackSpeed":4,"attackLevel":400,"strengthLevel":400,"defenceLevel":20,"rangeLevel":400,"magicLevel":400,"range":80,"magic":80,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusRangeStrength":80,"bonusMagicDamage":150},"8371":{"name":"Verzik Vitur","hitpoints":2000,"combatLevel":1040,"attackSpeed":4,"attackLevel":400,"strengthLevel":400,"defenceLevel":20,"rangeLevel":400,"magicLevel":400,"range":80,"magic":80,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusRangeStrength":80,"bonusMagicDamage":150},"8372":{"name":"Verzik Vitur","hitpoints":3250,"combatLevel":1265,"attackSpeed":4,"attackLevel":400,"strengthLevel":400,"defenceLevel":200,"rangeLevel":400,"magicLevel":400,"range":80,"magic":80,"stabDef":100,"slashDef":60,"crushDef":100,"rangeDef":250,"magicDef":70,"bonusRangeStrength":80,"bonusMagicDamage":80},"8373":{"name":"Verzik Vitur","hitpoints":3250,"combatLevel":1265,"attackSpeed":4,"attackLevel":400,"strengthLevel":400,"defenceLevel":200,"rangeLevel":400,"magicLevel":400,"range":80,"magic":80,"stabDef":100,"slashDef":60,"crushDef":100,"rangeDef":250,"magicDef":70,"bonusRangeStrength":80,"bonusMagicDamage":80},"8374":{"name":"Verzik Vitur","hitpoints":3250,"combatLevel":1520,"attackSpeed":7,"attackLevel":400,"strengthLevel":400,"defenceLevel":150,"rangeLevel":300,"magicLevel":300,"range":80,"magic":80,"stabDef":70,"slashDef":30,"crushDef":70,"rangeDef":230,"magicDef":100,"bonusAttack":80,"bonusStrength":30,"bonusRangeStrength":5,"bonusMagicDamage":5},"8375":{"name":"Verzik Vitur","hitpoints":3250,"combatLevel":1520,"attackSpeed":7,"attackLevel":400,"strengthLevel":400,"defenceLevel":150,"rangeLevel":300,"magicLevel":300,"range":80,"magic":80,"stabDef":70,"slashDef":30,"crushDef":70,"rangeDef":230,"magicDef":100,"bonusAttack":80,"bonusStrength":30,"bonusRangeStrength":5,"bonusMagicDamage":5},"8381":{"name":"Nylocas Ischyros","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8382":{"name":"Nylocas Toxobolos","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8383":{"name":"Nylocas Hagios","hitpoints":11,"combatLevel":162,"attackSpeed":3,"attackLevel":200,"strengthLevel":160,"defenceLevel":1,"rangeLevel":200,"magicLevel":1,"magic":1073,"bonusMagicDamage":928},"8384":{"name":"Nylocas Athanatos","hitpoints":180,"combatLevel":350,"attackLevel":1,"strengthLevel":1,"defenceLevel":50,"rangeLevel":1,"magicLevel":50},"8385":{"name":"Nylocas Matomenos","hitpoints":200,"combatLevel":115,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":100,"magicLevel":100},"8387":{"name":"Sotetseg","hitpoints":4000,"combatLevel":995,"attackSpeed":5,"attackLevel":250,"strengthLevel":250,"defenceLevel":200,"magicLevel":250,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":150,"magicDef":30,"bonusStrength":48,"poisonImmune":true,"venomImmune":true},"8388":{"name":"Sotetseg","hitpoints":4000,"combatLevel":995,"attackSpeed":5,"attackLevel":250,"strengthLevel":250,"defenceLevel":200,"magicLevel":250,"stabDef":70,"slashDef":70,"crushDef":70,"rangeDef":150,"magicDef":30,"bonusStrength":48,"poisonImmune":true,"venomImmune":true},"8400":{"name":"Monk of Zamorak","hitpoints":20,"combatLevel":22,"attackSpeed":4,"attackLevel":18,"strengthLevel":18,"defenceLevel":22,"rangeLevel":1,"magicLevel":25},"8401":{"name":"Monk of Zamorak","hitpoints":10,"combatLevel":17,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":12,"rangeLevel":1,"magicLevel":25},"8439":{"name":"Don't Know What","hitpoints":220,"combatLevel":163,"attackSpeed":3,"attackLevel":120,"strengthLevel":90,"defenceLevel":160,"rangeLevel":115,"magicLevel":60,"magicDef":200,"bonusRangeStrength":40},"8474":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"8475":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"8476":{"name":"Black Guard","hitpoints":40,"combatLevel":48,"attackSpeed":3,"attackLevel":40,"strengthLevel":45,"defenceLevel":45,"rangeLevel":1,"magicLevel":1,"stabDef":40,"slashDef":40,"crushDef":40,"rangeDef":40,"bonusAttack":30,"bonusStrength":30},"8496":{"name":"Dwarf","hitpoints":18,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"rangeDef":10,"magicDef":5,"bonusAttack":5,"bonusStrength":7},"8512":{"name":"Tormented Soul","hitpoints":20,"combatLevel":16,"attackSpeed":4,"attackLevel":16,"strengthLevel":12,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"8513":{"name":"Tormented Soul","hitpoints":20,"combatLevel":16,"attackSpeed":4,"attackLevel":16,"strengthLevel":12,"defenceLevel":10,"rangeLevel":1,"magicLevel":1},"8514":{"name":"Trapped Soul","hitpoints":30,"combatLevel":30,"attackSpeed":4,"attackLevel":25,"strengthLevel":29,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"8528":{"name":"Trapped Soul","hitpoints":30,"combatLevel":30,"attackSpeed":4,"attackLevel":25,"strengthLevel":29,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"8529":{"name":"Trapped Soul (hard)","hitpoints":100,"combatLevel":101,"attackSpeed":4,"attackLevel":100,"strengthLevel":120,"defenceLevel":20,"rangeLevel":1,"magicLevel":1},"8563":{"name":"Lizardman","hitpoints":60,"combatLevel":62,"attackSpeed":4,"attackLevel":53,"strengthLevel":53,"defenceLevel":52,"rangeLevel":1,"magicLevel":1,"range":28,"slashDef":20,"crushDef":5,"bonusAttack":28,"bonusStrength":25,"bonusRangeStrength":25},"8564":{"name":"Lizardman brute","hitpoints":60,"combatLevel":73,"attackSpeed":4,"attackLevel":65,"strengthLevel":65,"defenceLevel":65,"rangeLevel":1,"magicLevel":1,"range":34,"slashDef":30,"crushDef":10,"bonusAttack":34,"bonusStrength":30,"bonusRangeStrength":30},"8565":{"name":"Lizardman shaman","hitpoints":150,"combatLevel":150,"attackSpeed":4,"attackLevel":120,"strengthLevel":120,"defenceLevel":140,"rangeLevel":120,"magicLevel":130,"range":45,"slashDef":40,"crushDef":30,"magicDef":50,"bonusAttack":45,"bonusStrength":38,"bonusRangeStrength":38},"8578":{"name":"Swamp frog","hitpoints":25,"combatLevel":24,"attackSpeed":4,"attackLevel":25,"strengthLevel":20,"defenceLevel":15,"rangeLevel":1,"magicLevel":1},"8583":{"name":"Hespori","hitpoints":300,"combatLevel":284,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":120,"rangeLevel":150,"magicLevel":126,"range":150,"magic":150,"stabDef":60,"slashDef":20,"crushDef":60,"rangeDef":80,"magicDef":80},"8584":{"name":"Flower","hitpoints":10},"8609":{"name":"Hydra","hitpoints":300,"combatLevel":194,"slayerLevel":95,"attackSpeed":6,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":210,"magicLevel":210,"stabDef":160,"slashDef":160,"crushDef":160,"magicDef":160},"8610":{"name":"Wyrm","hitpoints":130,"combatLevel":99,"slayerLevel":62,"attackSpeed":4,"attackLevel":85,"strengthLevel":60,"defenceLevel":80,"rangeLevel":80,"magicLevel":80,"stabDef":20,"slashDef":50,"crushDef":50,"rangeDef":20,"magicDef":50,"bonusStrength":30,"bonusMagicDamage":30},"8611":{"name":"Wyrm","hitpoints":130,"combatLevel":99,"slayerLevel":62,"attackSpeed":4,"attackLevel":85,"strengthLevel":60,"defenceLevel":80,"rangeLevel":80,"magicLevel":80,"stabDef":20,"slashDef":50,"crushDef":50,"rangeDef":20,"magicDef":50,"bonusStrength":30,"bonusMagicDamage":30},"8612":{"name":"Drake","hitpoints":250,"combatLevel":192,"slayerLevel":84,"attackSpeed":4,"attackLevel":140,"strengthLevel":118,"defenceLevel":120,"rangeLevel":140,"magicLevel":112,"range":40,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":100,"magicDef":20,"bonusAttack":40,"bonusStrength":12,"bonusRangeStrength":40},"8613":{"name":"Drake","hitpoints":250,"combatLevel":192,"slayerLevel":84,"attackSpeed":4,"attackLevel":140,"strengthLevel":118,"defenceLevel":120,"rangeLevel":140,"magicLevel":112,"range":40,"stabDef":60,"slashDef":60,"crushDef":60,"rangeDef":100,"magicDef":20,"bonusAttack":40,"bonusStrength":12,"bonusRangeStrength":40},"8614":{"name":"Sulphur Lizard","hitpoints":50,"combatLevel":50,"slayerLevel":44,"attackSpeed":4,"attackLevel":50,"strengthLevel":45,"defenceLevel":30,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":25,"crushDef":25,"rangeDef":15},"8615":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":6,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8616":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":6,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8617":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":6,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8618":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":6,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8619":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":6,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8620":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":6,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8621":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8622":{"name":"Alchemical Hydra","hitpoints":1100,"combatLevel":426,"slayerLevel":95,"attackSpeed":4,"attackLevel":100,"strengthLevel":100,"defenceLevel":100,"rangeLevel":260,"magicLevel":260,"range":45,"magic":45,"stabDef":75,"slashDef":150,"crushDef":150,"rangeDef":45,"magicDef":150,"bonusRangeStrength":20,"bonusMagicDamage":20},"8633":{"name":"The Mimic","hitpoints":230,"combatLevel":186,"attackSpeed":3,"attackLevel":185,"strengthLevel":120,"defenceLevel":120,"rangeLevel":1,"magicLevel":60,"magic":180,"stabDef":160,"slashDef":165,"crushDef":150,"rangeDef":145,"magicDef":30,"bonusAttack":135,"bonusStrength":48},"8635":{"name":"Third Age Warrior","hitpoints":40,"combatLevel":83,"attackSpeed":5,"attackLevel":90,"strengthLevel":75,"defenceLevel":80,"rangeLevel":1,"magicLevel":1,"stabDef":96,"slashDef":108,"crushDef":113,"rangeDef":97,"bonusAttack":105,"bonusStrength":75},"8636":{"name":"Third Age Ranger","hitpoints":40,"combatLevel":76,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":80,"rangeLevel":95,"magicLevel":1,"range":140,"stabDef":55,"slashDef":47,"crushDef":60,"rangeDef":55,"magicDef":60,"bonusRangeStrength":7},"8637":{"name":"Third Age Mage","hitpoints":40,"combatLevel":83,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":80,"rangeLevel":1,"magicLevel":110,"magic":145,"stabDef":5,"slashDef":6,"crushDef":5,"magicDef":65,"bonusMagicDamage":24},"8678":{"name":"Feral Vampyre","hitpoints":50,"combatLevel":72,"attackSpeed":4,"attackLevel":65,"strengthLevel":70,"defenceLevel":65,"rangeLevel":1,"magicLevel":50},"8698":{"name":"Monk of Zamorak","hitpoints":16,"combatLevel":23,"attackSpeed":4,"attackLevel":15,"strengthLevel":13,"defenceLevel":18,"rangeLevel":1,"magicLevel":30},"8700":{"name":"Giant frog","hitpoints":23,"combatLevel":13,"attackSpeed":4,"attackLevel":10,"strengthLevel":8,"defenceLevel":6,"rangeLevel":1,"magicLevel":1},"8701":{"name":"Big frog","hitpoints":18,"combatLevel":10,"attackSpeed":4,"attackLevel":8,"strengthLevel":6,"defenceLevel":4,"rangeLevel":1,"magicLevel":1},"8702":{"name":"Frog","hitpoints":8,"combatLevel":5,"attackSpeed":4,"attackLevel":5,"strengthLevel":4,"defenceLevel":3,"rangeLevel":1,"magicLevel":1},"8703":{"name":"Temple Spider","hitpoints":70,"combatLevel":75,"attackSpeed":4,"attackLevel":120,"strengthLevel":50,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":30,"crushDef":10,"rangeDef":20,"magicDef":25},"8709":{"name":"Shaeded Beast","hitpoints":210,"combatLevel":186,"attackSpeed":4,"attackLevel":50,"strengthLevel":80,"defenceLevel":50,"rangeLevel":1,"magicLevel":250,"stabDef":50,"slashDef":30,"crushDef":100,"rangeDef":50,"magicDef":150,"bonusMagicDamage":10},"8713":{"name":"Sarachnis","hitpoints":400,"combatLevel":318,"attackSpeed":4,"attackLevel":200,"strengthLevel":240,"defenceLevel":150,"rangeLevel":300,"magicLevel":150,"range":15,"stabDef":60,"slashDef":40,"crushDef":10,"rangeDef":300,"magicDef":150,"bonusAttack":30,"bonusRangeStrength":15},"8714":{"name":"Spawn of Sarachnis","hitpoints":30,"combatLevel":107,"attackSpeed":3,"attackLevel":150,"strengthLevel":120,"defenceLevel":50,"rangeLevel":1,"magicLevel":1,"stabDef":50,"slashDef":50,"crushDef":20,"rangeDef":150,"bonusAttack":50},"8715":{"name":"Spawn of Sarachnis","hitpoints":30,"combatLevel":68,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":50,"rangeLevel":1,"magicLevel":100,"stabDef":50,"slashDef":50,"crushDef":50,"magicDef":150},"8736":{"name":"Moss Giant","hitpoints":120,"combatLevel":84,"attackSpeed":6,"attackLevel":60,"strengthLevel":60,"defenceLevel":60,"rangeLevel":1,"magicLevel":1,"bonusAttack":66,"bonusStrength":62},"8759":{"name":"Iorwerth Warrior","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8760":{"name":"Iorwerth Archer","hitpoints":105,"combatLevel":90,"attackSpeed":5,"attackLevel":10,"strengthLevel":10,"defenceLevel":80,"rangeLevel":90,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"8847":{"name":"Essyllt","hitpoints":320,"combatLevel":236,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":104,"rangeLevel":342,"magicLevel":104,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusStrength":28,"bonusRangeStrength":28},"8858":{"name":"Man","hitpoints":7,"combatLevel":2,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"8871":{"name":"Essyllt","hitpoints":320,"combatLevel":236,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":104,"rangeLevel":342,"magicLevel":104,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusStrength":28,"bonusRangeStrength":28},"8872":{"name":"Essyllt","hitpoints":320,"combatLevel":236,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":104,"rangeLevel":342,"magicLevel":104,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusStrength":28,"bonusRangeStrength":28},"8917":{"name":"Fragment of Seren","hitpoints":1000,"combatLevel":494,"attackSpeed":7,"attackLevel":242,"strengthLevel":288,"defenceLevel":102,"rangeLevel":235,"magicLevel":102,"range":224,"stabDef":320,"slashDef":220,"crushDef":320,"rangeDef":480,"magicDef":10,"bonusAttack":236,"bonusStrength":44},"8918":{"name":"Fragment of Seren","hitpoints":1000,"combatLevel":494,"attackSpeed":7,"attackLevel":242,"strengthLevel":288,"defenceLevel":102,"rangeLevel":235,"magicLevel":102,"range":224,"stabDef":320,"slashDef":220,"crushDef":320,"rangeDef":480,"magicDef":10,"bonusAttack":236,"bonusStrength":44},"8919":{"name":"Fragment of Seren","hitpoints":1000,"combatLevel":494,"attackSpeed":7,"attackLevel":242,"strengthLevel":288,"defenceLevel":102,"rangeLevel":235,"magicLevel":102,"range":224,"stabDef":320,"slashDef":220,"crushDef":320,"rangeDef":480,"magicDef":10,"bonusAttack":236,"bonusStrength":44},"8920":{"name":"Fragment of Seren","hitpoints":1000,"combatLevel":494,"attackSpeed":7,"attackLevel":242,"strengthLevel":288,"defenceLevel":102,"rangeLevel":235,"magicLevel":102,"range":224,"stabDef":320,"slashDef":220,"crushDef":320,"rangeDef":480,"magicDef":10,"bonusAttack":236,"bonusStrength":44},"8950":{"name":"Essyllt","hitpoints":320,"combatLevel":236,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":104,"rangeLevel":342,"magicLevel":104,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusStrength":28,"bonusRangeStrength":28},"8978":{"name":"Blessed spider","hitpoints":32,"combatLevel":39,"attackSpeed":4,"attackLevel":35,"strengthLevel":35,"defenceLevel":35,"rangeLevel":1,"magicLevel":1,"stabDef":15,"slashDef":16,"crushDef":7,"rangeDef":16,"magicDef":12},"8987":{"name":"Sir Jerro","hitpoints":57,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"8988":{"name":"Sir Carl","hitpoints":57,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"8989":{"name":"Sir Harry","hitpoints":57,"combatLevel":62,"attackSpeed":5,"attackLevel":54,"strengthLevel":54,"defenceLevel":54,"rangeLevel":1,"magicLevel":1,"stabDef":87,"slashDef":84,"crushDef":76,"rangeDef":79,"bonusAttack":20,"bonusStrength":22},"8993":{"name":"Kalrag","hitpoints":78,"combatLevel":89,"attackSpeed":4,"attackLevel":78,"strengthLevel":78,"defenceLevel":78,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":17,"crushDef":12,"rangeDef":13,"magicDef":13},"8994":{"name":"Othainian","hitpoints":87,"combatLevel":91,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":77,"rangeLevel":1,"magicLevel":1},"8995":{"name":"Doomion","hitpoints":87,"combatLevel":91,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":77,"rangeLevel":1,"magicLevel":1},"8996":{"name":"Holthion","hitpoints":87,"combatLevel":91,"attackSpeed":4,"attackLevel":76,"strengthLevel":78,"defenceLevel":77,"rangeLevel":1,"magicLevel":1},"8997":{"name":"Disciple of Iban","hitpoints":20,"combatLevel":13,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":12,"rangeLevel":1,"magicLevel":1},"9008":{"name":"Mourner","hitpoints":19,"combatLevel":13,"attackSpeed":4,"attackLevel":10,"strengthLevel":10,"defenceLevel":10,"rangeLevel":1,"magicLevel":1,"stabDef":3,"slashDef":2,"crushDef":4},"9013":{"name":"Mourner","hitpoints":19,"combatLevel":11,"attackSpeed":4,"attackLevel":8,"strengthLevel":8,"defenceLevel":8,"rangeLevel":1,"magicLevel":1,"stabDef":6,"slashDef":6,"crushDef":9,"bonusAttack":6,"bonusStrength":2},"9016":{"name":"Essyllt","hitpoints":320,"combatLevel":236,"attackSpeed":4,"attackLevel":268,"strengthLevel":268,"defenceLevel":104,"rangeLevel":342,"magicLevel":104,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusStrength":28,"bonusRangeStrength":28},"9017":{"name":"Mourner","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9021":{"name":"Crystalline Hunllef","hitpoints":600,"combatLevel":674,"attackSpeed":5,"attackLevel":600,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":76,"magic":76,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":76,"bonusStrength":64,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9022":{"name":"Crystalline Hunllef","hitpoints":600,"combatLevel":674,"attackSpeed":5,"attackLevel":600,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":76,"magic":76,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":76,"bonusStrength":64,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9023":{"name":"Crystalline Hunllef","hitpoints":600,"combatLevel":674,"attackSpeed":5,"attackLevel":600,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":76,"magic":76,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":76,"bonusStrength":64,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9024":{"name":"Crystalline Hunllef","hitpoints":600,"combatLevel":674,"attackSpeed":5,"attackLevel":600,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":76,"magic":76,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":76,"bonusStrength":64,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9026":{"name":"Crystalline Rat","hitpoints":14,"combatLevel":24,"attackSpeed":4,"attackLevel":28,"strengthLevel":32,"defenceLevel":6,"magicLevel":6,"poisonImmune":true,"venomImmune":true},"9028":{"name":"Crystalline Bat","hitpoints":14,"combatLevel":33,"attackSpeed":4,"attackLevel":24,"strengthLevel":68,"defenceLevel":2,"magicLevel":2,"poisonImmune":true,"venomImmune":true},"9029":{"name":"Crystalline Unicorn","hitpoints":44,"combatLevel":48,"attackSpeed":4,"attackLevel":50,"strengthLevel":48,"defenceLevel":24,"magicLevel":24,"poisonImmune":true,"venomImmune":true},"9030":{"name":"Crystalline Scorpion","hitpoints":38,"combatLevel":64,"attackSpeed":4,"attackLevel":48,"strengthLevel":106,"defenceLevel":18,"magicLevel":18,"poisonImmune":true,"venomImmune":true},"9031":{"name":"Crystalline Wolf","hitpoints":52,"combatLevel":74,"attackSpeed":4,"attackLevel":106,"strengthLevel":66,"defenceLevel":22,"magicLevel":22,"poisonImmune":true,"venomImmune":true},"9032":{"name":"Crystalline Bear","hitpoints":100,"combatLevel":172,"attackSpeed":4,"attackLevel":98,"strengthLevel":98,"defenceLevel":98,"rangeLevel":98,"magicLevel":98,"bonusAttack":54,"bonusStrength":106,"poisonImmune":true,"venomImmune":true},"9033":{"name":"Crystalline Dragon","hitpoints":100,"combatLevel":172,"attackSpeed":4,"attackLevel":98,"strengthLevel":98,"defenceLevel":98,"rangeLevel":98,"magicLevel":98,"magic":54,"bonusMagicDamage":106,"poisonImmune":true,"venomImmune":true},"9034":{"name":"Crystalline Dark Beast","hitpoints":100,"combatLevel":172,"attackSpeed":4,"attackLevel":98,"strengthLevel":98,"defenceLevel":98,"rangeLevel":98,"magicLevel":98,"range":54,"bonusRangeStrength":106,"poisonImmune":true,"venomImmune":true},"9035":{"name":"Corrupted Hunllef","hitpoints":1000,"combatLevel":894,"attackSpeed":5,"attackLevel":240,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":90,"magic":90,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":90,"bonusStrength":112,"bonusRangeStrength":112,"bonusMagicDamage":112,"poisonImmune":true,"venomImmune":true},"9036":{"name":"Corrupted Hunllef","hitpoints":1000,"combatLevel":894,"attackSpeed":5,"attackLevel":240,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":90,"magic":90,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":90,"bonusStrength":112,"bonusRangeStrength":112,"bonusMagicDamage":112,"poisonImmune":true,"venomImmune":true},"9037":{"name":"Corrupted Hunllef","hitpoints":1000,"combatLevel":894,"attackSpeed":5,"attackLevel":240,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":90,"magic":90,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":90,"bonusStrength":112,"bonusRangeStrength":112,"bonusMagicDamage":112,"poisonImmune":true,"venomImmune":true},"9038":{"name":"Corrupted Hunllef","hitpoints":1000,"combatLevel":894,"attackSpeed":5,"attackLevel":240,"strengthLevel":240,"defenceLevel":240,"rangeLevel":240,"magicLevel":240,"range":90,"magic":90,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":20,"magicDef":20,"bonusAttack":90,"bonusStrength":112,"bonusRangeStrength":112,"bonusMagicDamage":112,"poisonImmune":true,"venomImmune":true},"9040":{"name":"Corrupted Rat","hitpoints":14,"combatLevel":34,"attackSpeed":4,"attackLevel":42,"strengthLevel":48,"defenceLevel":6,"magicLevel":6,"poisonImmune":true,"venomImmune":true},"9041":{"name":"Corrupted Spider","hitpoints":12,"combatLevel":32,"attackSpeed":4,"attackLevel":48,"strengthLevel":39,"defenceLevel":4,"magicLevel":4,"poisonImmune":true,"venomImmune":true},"9042":{"name":"Corrupted Bat","combatLevel":48,"attackSpeed":4,"attackLevel":36,"strengthLevel":102,"defenceLevel":2,"magicLevel":2,"poisonImmune":true,"venomImmune":true},"9043":{"name":"Corrupted Unicorn","hitpoints":44,"combatLevel":64,"attackSpeed":4,"attackLevel":75,"strengthLevel":72,"defenceLevel":24,"magicLevel":24,"poisonImmune":true,"venomImmune":true},"9044":{"name":"Corrupted Scorpion","hitpoints":38,"combatLevel":89,"attackSpeed":4,"attackLevel":72,"strengthLevel":159,"defenceLevel":18,"magicLevel":18,"poisonImmune":true,"venomImmune":true},"9045":{"name":"Corrupted Wolf","hitpoints":52,"combatLevel":102,"attackSpeed":4,"attackLevel":159,"strengthLevel":99,"defenceLevel":22,"magicLevel":22,"poisonImmune":true,"venomImmune":true},"9046":{"name":"Corrupted Bear","hitpoints":100,"combatLevel":258,"attackSpeed":4,"attackLevel":147,"strengthLevel":147,"defenceLevel":98,"rangeLevel":147,"magicLevel":98,"bonusAttack":81,"bonusStrength":132,"poisonImmune":true,"venomImmune":true},"9047":{"name":"Corrupted Dragon","hitpoints":100,"combatLevel":258,"attackSpeed":4,"attackLevel":147,"strengthLevel":147,"defenceLevel":98,"rangeLevel":147,"magicLevel":98,"magic":146,"bonusMagicDamage":226,"poisonImmune":true,"venomImmune":true},"9048":{"name":"Corrupted Dark Beast","hitpoints":100,"combatLevel":258,"attackSpeed":4,"attackLevel":147,"strengthLevel":147,"defenceLevel":98,"rangeLevel":147,"magicLevel":98,"range":81,"bonusRangeStrength":132,"poisonImmune":true,"venomImmune":true},"9049":{"name":"Zalcano","hitpoints":1000,"combatLevel":336,"attackSpeed":5,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"9051":{"name":"Golem","hitpoints":14,"combatLevel":64},"9118":{"name":"Rabbit","hitpoints":2000,"combatLevel":2,"attackSpeed":3,"attackLevel":600,"strengthLevel":200,"defenceLevel":450,"rangeLevel":1,"magicLevel":300,"stabDef":150,"slashDef":150,"crushDef":150,"rangeDef":200,"magicDef":150,"bonusAttack":80,"bonusStrength":60},"9181":{"name":"Dire Wolf","hitpoints":74,"combatLevel":72,"attackSpeed":4,"attackLevel":60,"strengthLevel":61,"defenceLevel":62,"rangeLevel":1,"magicLevel":1},"9182":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9183":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9184":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9185":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9186":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9187":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9188":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9189":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9190":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9191":{"name":"Guard","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":80,"defenceLevel":95,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9247":{"name":"Essyllt (hard)","hitpoints":320,"combatLevel":395,"attackSpeed":4,"attackLevel":408,"strengthLevel":408,"defenceLevel":104,"rangeLevel":512,"magicLevel":104,"range":168,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusAttack":14,"bonusStrength":28,"bonusRangeStrength":28},"9249":{"name":"Essyllt (hard)","hitpoints":320,"combatLevel":395,"attackSpeed":4,"attackLevel":408,"strengthLevel":408,"defenceLevel":104,"rangeLevel":512,"magicLevel":104,"range":168,"stabDef":40,"slashDef":40,"crushDef":20,"rangeDef":120,"magicDef":30,"bonusAttack":14,"bonusStrength":28,"bonusRangeStrength":28},"9258":{"name":"Basilisk Sentinel","hitpoints":520,"combatLevel":358,"slayerLevel":60,"attackSpeed":4,"attackLevel":274,"strengthLevel":274,"defenceLevel":274,"rangeLevel":1,"magicLevel":274,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":10,"magicDef":50,"bonusAttack":12,"bonusMagicDamage":12},"9282":{"name":"Basilisk Youngling","hitpoints":60,"combatLevel":57,"slayerLevel":40,"attackSpeed":4,"attackLevel":25,"strengthLevel":40,"defenceLevel":70,"rangeLevel":1,"magicLevel":1,"stabDef":20,"slashDef":20,"magicDef":20},"9287":{"name":"Monstrous Basilisk","hitpoints":170,"combatLevel":135,"slayerLevel":40,"attackSpeed":4,"attackLevel":88,"strengthLevel":98,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"magicDef":35,"poisonImmune":true,"venomImmune":true},"9288":{"name":"Monstrous Basilisk","hitpoints":170,"combatLevel":135,"slayerLevel":40,"attackSpeed":4,"attackLevel":88,"strengthLevel":98,"defenceLevel":130,"rangeLevel":1,"magicLevel":1,"stabDef":35,"slashDef":35,"magicDef":35,"poisonImmune":true,"venomImmune":true},"9289":{"name":"The Jormungand","hitpoints":600,"combatLevel":363,"slayerLevel":40,"attackSpeed":5,"attackLevel":214,"strengthLevel":214,"defenceLevel":214,"rangeLevel":214,"magicLevel":214,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":50,"magicDef":50},"9290":{"name":"The Jormungand","hitpoints":600,"combatLevel":363,"slayerLevel":40,"attackSpeed":5,"attackLevel":214,"strengthLevel":214,"defenceLevel":214,"rangeLevel":214,"magicLevel":214,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":50,"magicDef":50},"9291":{"name":"The Jormungand","hitpoints":600,"combatLevel":363,"slayerLevel":40,"attackSpeed":5,"attackLevel":214,"strengthLevel":214,"defenceLevel":214,"rangeLevel":214,"magicLevel":214,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":50,"magicDef":50},"9292":{"name":"The Jormungand","hitpoints":600,"combatLevel":363,"slayerLevel":40,"attackSpeed":5,"attackLevel":214,"strengthLevel":214,"defenceLevel":214,"rangeLevel":214,"magicLevel":214,"stabDef":50,"slashDef":50,"crushDef":10,"rangeDef":50,"magicDef":50},"9293":{"name":"Basilisk Knight","hitpoints":300,"combatLevel":204,"slayerLevel":60,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":186,"rangeLevel":1,"magicLevel":186,"stabDef":30,"slashDef":30,"magicDef":30},"9295":{"name":"Typhor","hitpoints":360,"combatLevel":218,"slayerLevel":40,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":186,"rangeLevel":1,"magicLevel":186,"stabDef":30,"slashDef":30,"magicDef":30,"poisonImmune":true,"venomImmune":true},"9296":{"name":"Typhor","hitpoints":360,"combatLevel":218,"slayerLevel":40,"attackSpeed":4,"attackLevel":186,"strengthLevel":186,"defenceLevel":186,"rangeLevel":1,"magicLevel":186,"stabDef":30,"slashDef":30,"magicDef":30,"poisonImmune":true,"venomImmune":true},"9415":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9416":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9417":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9418":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9419":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9420":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9421":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9422":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9423":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9424":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9425":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9426":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9427":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9428":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9429":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9430":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9431":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9432":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9433":{"name":"The Nightmare","hitpoints":2400,"combatLevel":814,"attackSpeed":6,"attackLevel":150,"strengthLevel":150,"defenceLevel":150,"rangeLevel":150,"magicLevel":150,"range":140,"magic":140,"stabDef":120,"slashDef":180,"crushDef":40,"rangeDef":600,"magicDef":600,"bonusAttack":140,"bonusStrength":128,"bonusRangeStrength":64,"bonusMagicDamage":64,"poisonImmune":true,"venomImmune":true},"9446":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1},"9447":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1},"9448":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1},"9449":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1},"9450":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1},"9451":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1},"9452":{"name":"Parasite","hitpoints":40,"combatLevel":86,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":40,"rangeLevel":60,"magicLevel":60,"range":12,"magic":12,"stabDef":50,"slashDef":70,"crushDef":10,"rangeDef":100,"magicDef":100,"bonusRangeStrength":26,"bonusMagicDamage":26},"9453":{"name":"Parasite","hitpoints":40,"combatLevel":57,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":40,"rangeLevel":60,"magicLevel":60,"range":12,"magic":12,"stabDef":50,"slashDef":70,"crushDef":10,"rangeDef":100,"magicDef":100,"bonusRangeStrength":26,"bonusMagicDamage":26},"9454":{"name":"Husk","hitpoints":20,"combatLevel":48,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":20,"rangeLevel":80,"magicLevel":80,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":80,"magicDef":80},"9455":{"name":"Husk","hitpoints":20,"combatLevel":48,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":20,"rangeLevel":80,"magicLevel":80,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":80,"magicDef":80},"9465":{"name":"Infernal pyrelord","hitpoints":184,"combatLevel":134,"slayerLevel":30,"attackSpeed":4,"attackLevel":124,"strengthLevel":96,"defenceLevel":68,"rangeLevel":1,"magicLevel":1,"stabDef":18,"slashDef":18,"crushDef":18,"rangeDef":18},"9466":{"name":"Husk","hitpoints":20,"combatLevel":104,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":20,"magicLevel":80,"magic":168,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":80,"magicDef":80,"bonusMagicDamage":82},"9467":{"name":"Husk","hitpoints":20,"combatLevel":104,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":20,"rangeLevel":80,"range":168,"stabDef":20,"slashDef":20,"crushDef":20,"rangeDef":80,"magicDef":80,"bonusRangeStrength":82},"9468":{"name":"Parasite","hitpoints":80,"combatLevel":129,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":60,"rangeLevel":60,"magicLevel":60,"range":85,"magic":84,"stabDef":60,"slashDef":80,"crushDef":20,"rangeDef":120,"magicDef":120,"bonusRangeStrength":102,"bonusMagicDamage":102},"9469":{"name":"Parasite","hitpoints":40,"combatLevel":91,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":40,"rangeLevel":60,"magicLevel":60,"range":44,"magic":44,"stabDef":50,"slashDef":70,"crushDef":10,"rangeDef":100,"magicDef":100,"bonusRangeStrength":62,"bonusMagicDamage":62},"9470":{"name":"Sleepwalker","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"poisonImmune":true,"venomImmune":true},"9502":{"name":"Iorwerth Warrior","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9503":{"name":"Iorwerth Warrior","hitpoints":105,"combatLevel":108,"attackSpeed":4,"attackLevel":95,"strengthLevel":95,"defenceLevel":80,"magicLevel":1,"stabDef":50,"slashDef":70,"crushDef":70,"rangeDef":50,"magicDef":60},"9573":{"name":"Acidic Bloodveld","hitpoints":5,"combatLevel":56,"attackSpeed":4,"attackLevel":80,"strengthLevel":46,"defenceLevel":2,"rangeLevel":1,"magicLevel":1},"9609":{"name":"Seagull","hitpoints":10,"combatLevel":3,"attackSpeed":4,"attackLevel":1,"strengthLevel":1,"defenceLevel":1,"rangeLevel":1,"magicLevel":1},"9727":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"9728":{"name":"Vampyre Juvinate","hitpoints":85,"combatLevel":54,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":40,"rangeLevel":1,"magicLevel":1},"9731":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"9732":{"name":"Vampyre Juvenile","hitpoints":60,"combatLevel":45,"attackSpeed":4,"attackLevel":40,"strengthLevel":30,"defenceLevel":30,"rangeLevel":1,"magicLevel":1},"9735":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9736":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9737":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9738":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9739":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9740":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9741":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9742":{"name":"Vyrewatch","hitpoints":90,"combatLevel":105,"attackSpeed":8,"attackLevel":105,"strengthLevel":86,"defenceLevel":85,"rangeLevel":105,"magicLevel":105},"9756":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9757":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9758":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9759":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9760":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9761":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9762":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"9763":{"name":"Vyrewatch Sentinel","hitpoints":150,"combatLevel":151,"slayerLevel":38,"attackSpeed":4,"attackLevel":120,"strengthLevel":160,"defenceLevel":180,"rangeLevel":120,"magicLevel":120,"bonusAttack":135,"bonusStrength":150},"10374":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"10375":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"10376":{"name":"Hill Giant","hitpoints":35,"combatLevel":28,"attackSpeed":6,"attackLevel":18,"strengthLevel":22,"defenceLevel":26,"rangeLevel":1,"magicLevel":1,"bonusAttack":18,"bonusStrength":16},"10397":{"name":"Spiked Turoth","hitpoints":195,"combatLevel":244,"slayerLevel":55,"attackSpeed":4,"attackLevel":204,"strengthLevel":280,"defenceLevel":154,"rangeLevel":1,"magicLevel":1,"slashDef":40,"crushDef":40},"10398":{"name":"Shadow Wyrm","hitpoints":320,"combatLevel":267,"slayerLevel":62,"attackSpeed":4,"attackLevel":300,"strengthLevel":180,"defenceLevel":125,"rangeLevel":125,"magicLevel":160,"stabDef":20,"slashDef":100,"crushDef":100,"rangeDef":20,"magicDef":50,"bonusStrength":30,"bonusMagicDamage":30},"10400":{"name":"Guardian Drake","hitpoints":630,"combatLevel":386,"slayerLevel":84,"attackLevel":270,"strengthLevel":280,"defenceLevel":200,"rangeLevel":240,"magicLevel":300,"range":40,"stabDef":60,"slashDef":100,"crushDef":100,"rangeDef":150,"magicDef":20,"bonusStrength":12},"10402":{"name":"Colossal Hydra","hitpoints":750,"combatLevel":309,"slayerLevel":95,"attackLevel":1,"strengthLevel":1,"defenceLevel":100,"rangeLevel":250,"magicLevel":250,"stabDef":100,"slashDef":200,"crushDef":200,"rangeDef":20,"magicDef":200},"10435":{"name":"Pig Thing","hitpoints":40,"combatLevel":37,"attackLevel":35,"strengthLevel":30,"defenceLevel":25,"rangeLevel":35,"magicLevel":25,"range":35,"magic":25,"slashDef":30,"crushDef":10,"magicDef":30,"bonusAttack":18,"bonusStrength":16,"bonusRangeStrength":20},"10436":{"name":"Pig Thing","hitpoints":40,"combatLevel":37,"attackLevel":35,"strengthLevel":30,"defenceLevel":25,"rangeLevel":35,"magicLevel":25,"range":35,"magic":25,"slashDef":30,"crushDef":10,"magicDef":30,"bonusAttack":18,"bonusStrength":16,"bonusRangeStrength":20},"10437":{"name":"Pig Thing","hitpoints":40,"combatLevel":37,"attackLevel":35,"strengthLevel":30,"defenceLevel":25,"rangeLevel":35,"magicLevel":25,"range":35,"magic":25,"slashDef":30,"crushDef":10,"magicDef":30,"bonusAttack":18,"bonusStrength":16,"bonusRangeStrength":20}} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/fork_and_knife.png b/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/fork_and_knife.png index f82589d6c3..6f11400816 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/fork_and_knife.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/fork_and_knife.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/spoon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/spoon.png new file mode 100644 index 0000000000..dc40dd32d4 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/spoon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/weary_face.png b/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/weary_face.png new file mode 100644 index 0000000000..f8b113ea2d Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/emojis/weary_face.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl new file mode 100644 index 0000000000..7f41acab49 --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/cl_types.cl @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, Adam + * 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. + */ + +struct uniform { + int cameraYaw; + int cameraPitch; + int centerX; + int centerY; + int zoom; + int cameraX; + int cameraY; + int cameraZ; + int4 sinCosTable[2048]; +}; + +struct shared_data { + int totalNum[12]; // number of faces with a given priority + int totalDistance[12]; // sum of distances to faces of a given priority + int totalMappedNum[18]; // number of faces with a given adjusted priority + int min10; // minimum distance to a face of priority 10 + int dfs[0]; // packed face id and distance, size 512 for small, 4096 for large +}; + +struct modelinfo { + int offset; // offset into buffer + int uvOffset; // offset into uv buffer + int size; // length in faces + int idx; // write idx in target buffer + int flags; // radius, orientation + int x; // scene position x + int y; // scene position y + int z; // scene position z +}; diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl new file mode 100644 index 0000000000..f499599cd2 --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/common.cl @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021, Adam + * 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. + */ + +#define PI 3.1415926535897932384626433832795f +#define UNIT PI / 1024.0f + +float3 toScreen(int4 vertex, int cameraYaw, int cameraPitch, int centerX, int centerY, int zoom) { + float yawSin = sin(cameraYaw * UNIT); + float yawCos = cos(cameraYaw * UNIT); + + float pitchSin = sin(cameraPitch * UNIT); + float pitchCos = cos(cameraPitch * UNIT); + + float rotatedX = (vertex.z * yawSin) + (vertex.x * yawCos); + float rotatedZ = (vertex.z * yawCos) - (vertex.x * yawSin); + + float var13 = (vertex.y * pitchCos) - (rotatedZ * pitchSin); + float var12 = (vertex.y * pitchSin) + (rotatedZ * pitchCos); + + float x = rotatedX * zoom / var12 + centerX; + float y = var13 * zoom / var12 + centerY; + float z = -var12; // in OpenGL depth is negative + + return (float3) (x, y, z); +} + +/* + * Rotate a vertex by a given orientation in JAU + */ +int4 rotate_vertex(__constant struct uniform *uni, int4 vertex, int orientation) { + int4 sinCos = uni->sinCosTable[orientation]; + int s = sinCos.x; + int c = sinCos.y; + int x = vertex.z * s + vertex.x * c >> 16; + int z = vertex.z * c - vertex.x * s >> 16; + return (int4)(x, vertex.y, z, vertex.w); +} + +/* + * Calculate the distance to a vertex given the camera angle + */ +int vertex_distance(int4 vertex, int cameraYaw, int cameraPitch) { + int yawSin = (int)(65536.0f * sin(cameraYaw * UNIT)); + int yawCos = (int)(65536.0f * cos(cameraYaw * UNIT)); + + int pitchSin = (int)(65536.0f * sin(cameraPitch * UNIT)); + int pitchCos = (int)(65536.0f * cos(cameraPitch * UNIT)); + + int j = vertex.z * yawCos - vertex.x * yawSin >> 16; + int l = vertex.y * pitchSin + j * pitchCos >> 16; + + return l; +} + +/* + * Calculate the distance to a face + */ +int face_distance(int4 vA, int4 vB, int4 vC, int cameraYaw, int cameraPitch) { + int dvA = vertex_distance(vA, cameraYaw, cameraPitch); + int dvB = vertex_distance(vB, cameraYaw, cameraPitch); + int dvC = vertex_distance(vC, cameraYaw, cameraPitch); + int faceDistance = (dvA + dvB + dvC) / 3; + return faceDistance; +} + +/* + * Test if a face is visible (not backward facing) + */ +bool face_visible(__constant struct uniform *uni, int4 vA, int4 vB, int4 vC, int4 position) { + // Move model to scene location, and account for camera offset + int4 cameraPos = (int4)(uni->cameraX, uni->cameraY, uni->cameraZ, 0); + vA += position - cameraPos; + vB += position - cameraPos; + vC += position - cameraPos; + + float3 sA = toScreen(vA, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom); + float3 sB = toScreen(vB, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom); + float3 sC = toScreen(vC, uni->cameraYaw, uni->cameraPitch, uni->centerX, uni->centerY, uni->zoom); + + return (sA.x - sB.x) * (sC.y - sB.y) - (sC.x - sB.x) * (sA.y - sB.y) > 0; +} + diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl new file mode 100644 index 0000000000..1212123f25 --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp.cl @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021, Adam + * 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. + */ + +#include FACE_COUNT + +#include cl_types.cl +#include to_screen.cl +#include common.cl +#include priority_render.cl + +__kernel +__attribute__((work_group_size_hint(256, 1, 1))) +void computeLarge( + __local struct shared_data *shared, + __global const struct modelinfo *ol, + __global const int4 *vb, + __global const int4 *tempvb, + __global const float4 *uv, + __global const float4 *tempuv, + __global int4 *vout, + __global float4 *uvout, + __constant struct uniform *uni) { + + size_t groupId = get_group_id(0); + size_t localId = get_local_id(0) * FACE_COUNT; + struct modelinfo minfo = ol[groupId]; + int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0); + + if (localId == 0) { + shared->min10 = 1600; + for (int i = 0; i < 12; ++i) { + shared->totalNum[i] = 0; + shared->totalDistance[i] = 0; + } + for (int i = 0; i < 18; ++i) { + shared->totalMappedNum[i] = 0; + } + } + + int prio[FACE_COUNT]; + int dis[FACE_COUNT]; + int4 v1[FACE_COUNT]; + int4 v2[FACE_COUNT]; + int4 v3[FACE_COUNT]; + + for (int i = 0; i < FACE_COUNT; i++) { + get_face(shared, uni, vb, tempvb, localId + i, minfo, uni->cameraYaw, uni->cameraPitch, &prio[i], &dis[i], &v1[i], &v2[i], &v3[i]); + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < FACE_COUNT; i++) { + add_face_prio_distance(shared, uni, localId + i, minfo, v1[i], v2[i], v3[i], prio[i], dis[i], pos); + } + + barrier(CLK_LOCAL_MEM_FENCE); + + int prioAdj[FACE_COUNT]; + int idx[FACE_COUNT]; + for (int i = 0; i < FACE_COUNT; i++) { + idx[i] = map_face_priority(shared, localId + i, minfo, prio[i], dis[i], &prioAdj[i]); + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < FACE_COUNT; i++) { + insert_dfs(shared, localId + i, minfo, prioAdj[i], dis[i], idx[i]); + } + + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = 0; i < FACE_COUNT; i++) { + sort_and_insert(shared, uv, tempuv, vout, uvout, localId + i, minfo, prioAdj[i], dis[i], v1[i], v2[i], v3[i]); + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl new file mode 100644 index 0000000000..436f9a7d72 --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/comp_unordered.cl @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021, Adam + * 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. + */ + +#include cl_types.cl + +__kernel +__attribute__((reqd_work_group_size(6, 1, 1))) +void computeUnordered(__global const struct modelinfo *ol, + __global const int4 *vb, + __global const int4 *tempvb, + __global const float4 *uv, + __global const float4 *tempuv, + __global int4 *vout, + __global float4 *uvout) { + size_t groupId = get_group_id(0); + size_t localId = get_local_id(0); + struct modelinfo minfo = ol[groupId]; + + int offset = minfo.offset; + int size = minfo.size; + int outOffset = minfo.idx; + int uvOffset = minfo.uvOffset; + int flags = minfo.flags; + int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0); + + if (localId >= size) { + return; + } + + uint ssboOffset = localId; + int4 thisA, thisB, thisC; + + // Grab triangle vertices from the correct buffer + if (flags < 0) { + thisA = vb[offset + ssboOffset * 3]; + thisB = vb[offset + ssboOffset * 3 + 1]; + thisC = vb[offset + ssboOffset * 3 + 2]; + } else { + thisA = tempvb[offset + ssboOffset * 3]; + thisB = tempvb[offset + ssboOffset * 3 + 1]; + thisC = tempvb[offset + ssboOffset * 3 + 2]; + } + + uint myOffset = localId; + + // position vertices in scene and write to out buffer + vout[outOffset + myOffset * 3] = pos + thisA; + vout[outOffset + myOffset * 3 + 1] = pos + thisB; + vout[outOffset + myOffset * 3 + 2] = pos + thisC; + + if (uvOffset < 0) { + uvout[outOffset + myOffset * 3] = (float4)(0.0f, 0.0f, 0.0f, 0.0f); + uvout[outOffset + myOffset * 3 + 1] = (float4)(0.0f, 0.0f, 0.0f, 0.0f); + uvout[outOffset + myOffset * 3 + 2] = (float4)(0.0f, 0.0f, 0.0f, 0.0f); + } else if (flags >= 0) { + uvout[outOffset + myOffset * 3] = tempuv[uvOffset + localId * 3]; + uvout[outOffset + myOffset * 3 + 1] = tempuv[uvOffset + localId * 3 + 1]; + uvout[outOffset + myOffset * 3 + 2] = tempuv[uvOffset + localId * 3 + 2]; + } else { + uvout[outOffset + myOffset * 3] = uv[uvOffset + localId * 3]; + uvout[outOffset + myOffset * 3 + 1] = uv[uvOffset + localId * 3 + 1]; + uvout[outOffset + myOffset * 3 + 2] = uv[uvOffset + localId * 3 + 2]; + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl new file mode 100644 index 0000000000..6f1a04470c --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/priority_render.cl @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2021, Adam + * 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. + */ + +// Calculate adjusted priority for a face with a given priority, distance, and +// model global min10 and face distance averages. This allows positioning faces +// with priorities 10/11 into the correct 'slots' resulting in 18 possible +// adjusted priorities +int priority_map(int p, int distance, int _min10, int avg1, int avg2, int avg3) { + // (10, 11) 0 1 2 (10, 11) 3 4 (10, 11) 5 6 7 8 9 (10, 11) + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + switch (p) { + case 0: return 2; + case 1: return 3; + case 2: return 4; + case 3: return 7; + case 4: return 8; + case 5: return 11; + case 6: return 12; + case 7: return 13; + case 8: return 14; + case 9: return 15; + case 10: + if (distance > avg1) { + return 0; + } else if (distance > avg2) { + return 5; + } else if (distance > avg3) { + return 9; + } else { + return 16; + } + case 11: + if (distance > avg1 && _min10 > avg1) { + return 1; + } else if (distance > avg2 && (_min10 > avg1 || _min10 > avg2)) { + return 6; + } else if (distance > avg3 && (_min10 > avg1 || _min10 > avg2 || _min10 > avg3)) { + return 10; + } else { + return 17; + } + default: + return -1; + } +} + +// calculate the number of faces with a lower adjusted priority than +// the given adjusted priority +int count_prio_offset(__local struct shared_data *shared, int priority) { + int total = 0; + switch (priority) { + case 17: + total += shared->totalMappedNum[16]; + case 16: + total += shared->totalMappedNum[15]; + case 15: + total += shared->totalMappedNum[14]; + case 14: + total += shared->totalMappedNum[13]; + case 13: + total += shared->totalMappedNum[12]; + case 12: + total += shared->totalMappedNum[11]; + case 11: + total += shared->totalMappedNum[10]; + case 10: + total += shared->totalMappedNum[9]; + case 9: + total += shared->totalMappedNum[8]; + case 8: + total += shared->totalMappedNum[7]; + case 7: + total += shared->totalMappedNum[6]; + case 6: + total += shared->totalMappedNum[5]; + case 5: + total += shared->totalMappedNum[4]; + case 4: + total += shared->totalMappedNum[3]; + case 3: + total += shared->totalMappedNum[2]; + case 2: + total += shared->totalMappedNum[1]; + case 1: + total += shared->totalMappedNum[0]; + case 0: + return total; + } +} + +void get_face( + __local struct shared_data *shared, + __constant struct uniform *uni, + __global const int4 *vb, + __global const int4 *tempvb, + uint localId, struct modelinfo minfo, int cameraYaw, int cameraPitch, + /* out */ int *prio, int *dis, int4 *o1, int4 *o2, int4 *o3) { + int size = minfo.size; + int offset = minfo.offset; + int flags = minfo.flags; + uint ssboOffset; + + if (localId < size) { + ssboOffset = localId; + } else { + ssboOffset = 0; + } + + int4 thisA; + int4 thisB; + int4 thisC; + + // Grab triangle vertices from the correct buffer + if (flags < 0) { + thisA = vb[offset + ssboOffset * 3]; + thisB = vb[offset + ssboOffset * 3 + 1]; + thisC = vb[offset + ssboOffset * 3 + 2]; + } else { + thisA = tempvb[offset + ssboOffset * 3]; + thisB = tempvb[offset + ssboOffset * 3 + 1]; + thisC = tempvb[offset + ssboOffset * 3 + 2]; + } + + if (localId < size) { + int radius = (flags & 0x7fffffff) >> 12; + int orientation = flags & 0x7ff; + + // rotate for model orientation + int4 thisrvA = rotate_vertex(uni, thisA, orientation); + int4 thisrvB = rotate_vertex(uni, thisB, orientation); + int4 thisrvC = rotate_vertex(uni, thisC, orientation); + + // calculate distance to face + int thisPriority = (thisA.w >> 16) & 0xff;// all vertices on the face have the same priority + int thisDistance; + if (radius == 0) { + thisDistance = 0; + } else { + thisDistance = face_distance(thisrvA, thisrvB, thisrvC, cameraYaw, cameraPitch) + radius; + } + + *o1 = thisrvA; + *o2 = thisrvB; + *o3 = thisrvC; + + *prio = thisPriority; + *dis = thisDistance; + } else { + *o1 = (int4)(0, 0, 0, 0); + *o2 = (int4)(0, 0, 0, 0); + *o3 = (int4)(0, 0, 0, 0); + *prio = 0; + *dis = 0; + } +} + +void add_face_prio_distance( + __local struct shared_data *shared, + __constant struct uniform *uni, + uint localId, struct modelinfo minfo, int4 thisrvA, int4 thisrvB, int4 thisrvC, int thisPriority, int thisDistance, int4 pos) { + if (localId < minfo.size) { + // if the face is not culled, it is calculated into priority distance averages + if (face_visible(uni, thisrvA, thisrvB, thisrvC, pos)) { + atomic_add(&shared->totalNum[thisPriority], 1); + atomic_add(&shared->totalDistance[thisPriority], thisDistance); + + // calculate minimum distance to any face of priority 10 for positioning the 11 faces later + if (thisPriority == 10) { + atomic_min(&shared->min10, thisDistance); + } + } + } +} + +int map_face_priority(__local struct shared_data *shared, uint localId, struct modelinfo minfo, int thisPriority, int thisDistance, int *prio) { + int size = minfo.size; + + // Compute average distances for 0/2, 3/4, and 6/8 + + if (localId < size) { + int avg1 = 0; + int avg2 = 0; + int avg3 = 0; + + if (shared->totalNum[1] > 0 || shared->totalNum[2] > 0) { + avg1 = (shared->totalDistance[1] + shared->totalDistance[2]) / (shared->totalNum[1] + shared->totalNum[2]); + } + + if (shared->totalNum[3] > 0 || shared->totalNum[4] > 0) { + avg2 = (shared->totalDistance[3] + shared->totalDistance[4]) / (shared->totalNum[3] + shared->totalNum[4]); + } + + if (shared->totalNum[6] > 0 || shared->totalNum[8] > 0) { + avg3 = (shared->totalDistance[6] + shared->totalDistance[8]) / (shared->totalNum[6] + shared->totalNum[8]); + } + + int adjPrio = priority_map(thisPriority, thisDistance, shared->min10, avg1, avg2, avg3); + int prioIdx = atomic_add(&shared->totalMappedNum[adjPrio], 1); + + *prio = adjPrio; + return prioIdx; + } + + *prio = 0; + return 0; +} + +void insert_dfs(__local struct shared_data *shared, uint localId, struct modelinfo minfo, int adjPrio, int distance, int prioIdx) { + int size = minfo.size; + + if (localId < size) { + // calculate base offset into dfs based on number of faces with a lower priority + int baseOff = count_prio_offset(shared, adjPrio); + // store into face array offset array by unique index + shared->dfs[baseOff + prioIdx] = ((int) localId << 16) | distance; + } +} + +void sort_and_insert( + __local struct shared_data *shared, + __global const float4 *uv, + __global const float4 *tempuv, + __global int4 *vout, + __global float4 *uvout, + uint localId, struct modelinfo minfo, int thisPriority, int thisDistance, int4 thisrvA, int4 thisrvB, int4 thisrvC) { + /* compute face distance */ + int size = minfo.size; + + if (localId < size) { + int outOffset = minfo.idx; + int uvOffset = minfo.uvOffset; + int flags = minfo.flags; + int4 pos = (int4)(minfo.x, minfo.y, minfo.z, 0); + + const int priorityOffset = count_prio_offset(shared, thisPriority); + const int numOfPriority = shared->totalMappedNum[thisPriority]; + int start = priorityOffset; // index of first face with this priority + int end = priorityOffset + numOfPriority; // index of last face with this priority + int myOffset = priorityOffset; + + // we only have to order faces against others of the same priority + // calculate position this face will be in + for (int i = start; i < end; ++i) { + int d1 = shared->dfs[i]; + int theirId = d1 >> 16; + int theirDistance = d1 & 0xffff; + + // the closest faces draw last, so have the highest index + // if two faces have the same distance, the one with the + // higher id draws last + if ((theirDistance > thisDistance) + || (theirDistance == thisDistance && theirId < localId)) { + ++myOffset; + } + } + + // position vertices in scene and write to out buffer + vout[outOffset + myOffset * 3] = pos + thisrvA; + vout[outOffset + myOffset * 3 + 1] = pos + thisrvB; + vout[outOffset + myOffset * 3 + 2] = pos + thisrvC; + + if (uvOffset < 0) { + uvout[outOffset + myOffset * 3] = (float4)(0, 0, 0, 0); + uvout[outOffset + myOffset * 3 + 1] = (float4)(0, 0, 0, 0); + uvout[outOffset + myOffset * 3 + 2] = (float4)(0, 0, 0, 0); + } else if (flags >= 0) { + uvout[outOffset + myOffset * 3] = tempuv[uvOffset + localId * 3]; + uvout[outOffset + myOffset * 3 + 1] = tempuv[uvOffset + localId * 3 + 1]; + uvout[outOffset + myOffset * 3 + 2] = tempuv[uvOffset + localId * 3 + 2]; + } else { + uvout[outOffset + myOffset * 3] = uv[uvOffset + localId * 3]; + uvout[outOffset + myOffset * 3 + 1] = uv[uvOffset + localId * 3 + 1]; + uvout[outOffset + myOffset * 3 + 2] = uv[uvOffset + localId * 3 + 2]; + } + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/abyssal_sire.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/abyssal_sire.png index 0209db1f97..c5b238a9d0 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/abyssal_sire.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/abyssal_sire.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/alchemical_hydra.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/alchemical_hydra.png index 3b708b7522..b34ed9c93e 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/alchemical_hydra.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/alchemical_hydra.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/barrows_chests.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/barrows_chests.png index 89ebe418dc..1560f1aaaa 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/barrows_chests.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/barrows_chests.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/bryophyta.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/bryophyta.png index e64276d9ca..68c4e2c904 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/bryophyta.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/bryophyta.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/callisto.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/callisto.png index 691ec6acf6..2248304cfe 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/callisto.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/callisto.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/cerberus.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/cerberus.png index 8d08024783..f7dc1f8047 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/cerberus.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/cerberus.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric.png index 58c4625bf1..71bfdd30cf 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric_challenge_mode.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric_challenge_mode.png index a35a701cee..31cc4b9cca 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric_challenge_mode.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chambers_of_xeric_challenge_mode.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_elemental.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_elemental.png index fb104c32d4..92ed8353fe 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_elemental.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_elemental.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_fanatic.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_fanatic.png index b27a010935..b712e9b70a 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_fanatic.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/chaos_fanatic.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/commander_zilyana.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/commander_zilyana.png index bec91f5bc1..9f1d6d20cc 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/commander_zilyana.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/commander_zilyana.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/corporeal_beast.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/corporeal_beast.png index 5656d06d7f..26c69b2e5c 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/corporeal_beast.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/corporeal_beast.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/crazy_archaeologist.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/crazy_archaeologist.png index 9a5f9ce8ac..d7cd6eaf1a 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/crazy_archaeologist.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/crazy_archaeologist.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_prime.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_prime.png index 18e004e18b..bc6612dbf4 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_prime.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_prime.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_rex.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_rex.png index 1b73c2d416..a18662f702 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_rex.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_rex.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_supreme.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_supreme.png index 721bd215e4..4cb52e4156 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_supreme.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/dagannoth_supreme.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/deranged_archaeologist.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/deranged_archaeologist.png index daf19da7a6..10ee3e8fb0 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/deranged_archaeologist.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/deranged_archaeologist.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/general_graardor.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/general_graardor.png index f26ca05eca..cefbc3c19b 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/general_graardor.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/general_graardor.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/giant_mole.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/giant_mole.png index 5fb3f328e8..48619319dc 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/giant_mole.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/giant_mole.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/grotesque_guardians.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/grotesque_guardians.png index ec6a4238d2..cf0bd826d5 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/grotesque_guardians.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/grotesque_guardians.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/hespori.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/hespori.png index 0c104674db..a85a83ce3c 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/hespori.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/hespori.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kalphite_queen.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kalphite_queen.png index 956e3a502d..c88f0bb448 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kalphite_queen.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kalphite_queen.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/king_black_dragon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/king_black_dragon.png index 7efce3fe35..c162208002 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/king_black_dragon.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/king_black_dragon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kraken.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kraken.png index a7b00aaae7..b45dd224fb 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kraken.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kraken.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kreearra.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kreearra.png index 0ef3398abf..2bc9631476 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kreearra.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kreearra.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kril_tsutsaroth.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kril_tsutsaroth.png index 6b36fb211c..878a14c18b 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kril_tsutsaroth.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/kril_tsutsaroth.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/mimic.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/mimic.png index 0666325dfa..7f523c6b87 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/mimic.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/mimic.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/nightmare.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/nightmare.png index f8edd9b211..c0f7645c02 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/nightmare.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/nightmare.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/obor.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/obor.png index 1ec992e6cd..eb9fc64725 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/obor.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/obor.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/sarachnis.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/sarachnis.png index c8428aa984..cbea6a8024 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/sarachnis.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/sarachnis.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/scorpia.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/scorpia.png index db4c0ff988..f39f9baa6e 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/scorpia.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/scorpia.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/skotizo.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/skotizo.png index 6821a71a1e..8888d2c315 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/skotizo.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/skotizo.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_corrupted_gauntlet.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_corrupted_gauntlet.png index a1b7fd687f..f1e2b6ec3a 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_corrupted_gauntlet.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_corrupted_gauntlet.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_gauntlet.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_gauntlet.png index d24830e178..3e03f805c1 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_gauntlet.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/the_gauntlet.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/theatre_of_blood.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/theatre_of_blood.png index 6202518873..5234c59637 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/theatre_of_blood.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/theatre_of_blood.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/thermonuclear_smoke_devil.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/thermonuclear_smoke_devil.png index 84614459a4..d7315af194 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/thermonuclear_smoke_devil.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/thermonuclear_smoke_devil.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tzkal_zuk.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tzkal_zuk.png index c227671ec8..1c6a966f19 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tzkal_zuk.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tzkal_zuk.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tztok_jad.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tztok_jad.png index 000dee3fed..d181c8d93c 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tztok_jad.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/tztok_jad.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/venenatis.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/venenatis.png index 438d315334..76dbc9fac3 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/venenatis.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/venenatis.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vetion.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vetion.png index cfc9d47a03..67a7b3d645 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vetion.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vetion.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vorkath.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vorkath.png index 696669c353..c3732b1d02 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vorkath.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/vorkath.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/wintertodt.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/wintertodt.png index 4546d90a52..478e0f968d 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/wintertodt.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/wintertodt.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zalcano.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zalcano.png index f2bdac1cea..72a0a1714b 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zalcano.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zalcano.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zulrah.png b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zulrah.png index 7e2dc8807f..44a28c4cbd 100644 Binary files a/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zulrah.png and b/runelite-client/src/main/resources/net/runelite/client/plugins/hiscore/bosses/zulrah.png differ diff --git a/runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externalmanager_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externalmanager_icon.png similarity index 100% rename from runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externalmanager_icon.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externalmanager_icon.png diff --git a/runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/add_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/add_icon.png similarity index 100% rename from runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/add_icon.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/add_icon.png diff --git a/runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/add_raw_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/add_raw_icon.png similarity index 100% rename from runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/add_raw_icon.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/add_raw_icon.png diff --git a/runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/delete_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/delete_icon.png similarity index 100% rename from runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/delete_icon.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/delete_icon.png diff --git a/runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/discord_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/discord_icon.png similarity index 100% rename from runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/discord_icon.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/discord_icon.png diff --git a/runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/gh_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/gh_icon.png similarity index 100% rename from runelite-client/src/main/resources/com/openosrs/client/plugins/openosrs/externals/gh_icon.png rename to runelite-client/src/main/resources/net/runelite/client/plugins/openosrs/externals/gh_icon.png diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/notify_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/notify_icon.png new file mode 100644 index 0000000000..4a4667bedb Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/notify_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/notify_selected_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/notify_selected_icon.png new file mode 100644 index 0000000000..1fee9d1f8d Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/notify_selected_icon.png differ diff --git a/runelite-client/src/main/resources/openosrs.properties b/runelite-client/src/main/resources/openosrs.properties new file mode 100644 index 0000000000..a22fbead86 --- /dev/null +++ b/runelite-client/src/main/resources/openosrs.properties @@ -0,0 +1,2 @@ +oprs.api.version=1.0.0 +oprs.version=@open.osrs.version@ \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java b/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java new file mode 100644 index 0000000000..b75c79b277 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/chat/ChatMessageManagerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2019, Adam + * 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.chat; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.awt.Color; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.MessageNode; +import net.runelite.api.Player; +import net.runelite.api.events.ChatMessage; +import net.runelite.client.config.ChatColorConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChatMessageManagerTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ChatColorConfig chatColorConfig; + + @Inject + private ChatMessageManager chatMessageManager; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + chatMessageManager.loadColors(); + } + + @Test + public void testMessageRecoloring() + { + when(chatColorConfig.opaqueServerMessage()).thenReturn(Color.decode("#b20000")); + + chatMessageManager.loadColors(); + + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setType(ChatMessageType.GAMEMESSAGE); + + MessageNode messageNode = mock(MessageNode.class); + chatMessage.setMessageNode(messageNode); + + when(messageNode.getValue()).thenReturn("Your dodgy necklace protects you. It has 1 charge left."); + chatMessageManager.onChatMessage(chatMessage); + + verify(messageNode).setValue("Your dodgy necklace protects you. It has 1 charge left."); + } + + @Test + public void testPublicFriendUsernameRecolouring() + { + final String localPlayerName = "RuneLite"; + final String friendName = "Zezima"; + + when(chatColorConfig.opaquePublicFriendUsernames()).thenReturn(Color.decode("#b20000")); + + chatMessageManager.loadColors(); + + // Setup message + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setType(ChatMessageType.PUBLICCHAT); + chatMessage.setName(friendName); + + MessageNode messageNode = mock(MessageNode.class); + chatMessage.setMessageNode(messageNode); + when(messageNode.getName()).thenReturn(friendName); + + // Setup friend checking + Player localPlayer = mock(Player.class); + + when(client.isFriended(friendName, true)).thenReturn(true); + when(client.getLocalPlayer()).thenReturn(localPlayer); + when(localPlayer.getName()).thenReturn(localPlayerName); + + chatMessageManager.onChatMessage(chatMessage); + + verify(messageNode).setName("" + friendName + ""); + } + + @Test + public void testPublicIronmanFriendUsernameRecolouring() + { + final String localPlayerName = "RuneLite"; + final String friendName = "BuddhaPuck"; + final String sanitizedFriendName = "BuddhaPuck"; + + when(chatColorConfig.opaquePublicFriendUsernames()).thenReturn(Color.decode("#b20000")); + + chatMessageManager.loadColors(); + + // Setup message + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setType(ChatMessageType.PUBLICCHAT); + chatMessage.setName(friendName); + + MessageNode messageNode = mock(MessageNode.class); + chatMessage.setMessageNode(messageNode); + when(messageNode.getName()).thenReturn(friendName); + + // Setup friend checking + Player localPlayer = mock(Player.class); + + when(client.isFriended(sanitizedFriendName, true)).thenReturn(true); + when(client.getLocalPlayer()).thenReturn(localPlayer); + when(localPlayer.getName()).thenReturn(localPlayerName); + + chatMessageManager.onChatMessage(chatMessage); + + verify(messageNode).setName("" + friendName + ""); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/ClueScrollPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/ClueScrollPluginTest.java new file mode 100644 index 0000000000..1c3cd1ecd4 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/cluescrolls/ClueScrollPluginTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019 Hydrox6 + * Copyright (c) 2019 Adam + * 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.cluescrolls; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameTick; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.banktags.TagManager; +import net.runelite.client.plugins.cluescrolls.clues.hotcold.HotColdLocation; +import net.runelite.client.ui.overlay.OverlayManager; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ClueScrollPluginTest +{ + @Mock + @Bind + Client client; + + @Inject + ClueScrollPlugin plugin; + + @Bind + @Named("developerMode") + boolean developerMode; + + @Mock + @Bind + ClueScrollConfig config; + + @Mock + @Bind + OverlayManager overlayManager; + + @Mock + @Bind + ItemManager itemManager; + + @Mock + @Bind + TagManager tagManager; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + } + + @Test + public void getGetMirrorPoint() + { + WorldPoint point, converted; + + // Zalcano's entrance portal + point = new WorldPoint(3282, 6058, 0); + converted = ClueScrollPlugin.getMirrorPoint(point, true); + assertNotEquals(point, converted); + + // Elven Crystal Chest, which is upstairs + point = new WorldPoint(3273, 6082, 2); + converted = ClueScrollPlugin.getMirrorPoint(point, true); + assertNotEquals(point, converted); + + // Around the area of the Elite coordinate clue + point = new WorldPoint(2185, 3280, 0); + // To overworld + converted = ClueScrollPlugin.getMirrorPoint(point, true); + assertEquals(point, converted); + // To real + converted = ClueScrollPlugin.getMirrorPoint(point, false); + assertNotEquals(point, converted); + + // Brugsen Bursen, Grand Exchange + point = new WorldPoint(3165, 3477, 0); + converted = ClueScrollPlugin.getMirrorPoint(point, false); + assertEquals(point, converted); + } + + @Test + public void testLocationHintArrowCleared() + { + final Widget clueWidget = mock(Widget.class); + when(clueWidget.getText()).thenReturn("Buried beneath the ground, who knows where it's found. Lucky for you, A man called Reldo may have a clue."); + final ChatMessage hotColdMessage = new ChatMessage(); + hotColdMessage.setType(ChatMessageType.GAMEMESSAGE); + final Player localPlayer = mock(Player.class); + + when(client.getWidget(WidgetInfo.CLUE_SCROLL_TEXT)).thenReturn(clueWidget); + when(client.getLocalPlayer()).thenReturn(localPlayer); + when(client.getPlane()).thenReturn(0); + when(client.getCachedNPCs()).thenReturn(new NPC[] {}); + when(config.displayHintArrows()).thenReturn(true); + + // The hint arrow should be reset each game tick from when the clue is read onward + // This is to verify the arrow is cleared the correct number of times during the clue updating process. + int clueSetupHintArrowClears = 0; + + // Initialize a beginner hot-cold clue (which will have an end point of LUMBRIDGE_COW_FIELD) + plugin.onGameTick(new GameTick()); + verify(client, times(++clueSetupHintArrowClears)).clearHintArrow(); + + // Perform the first hot-cold check in Lumbridge near sheep pen (get 2 possible points: LUMBRIDGE_COW_FIELD and DRAYNOR_WHEAT_FIELD) + when(localPlayer.getWorldLocation()).thenReturn(new WorldPoint(3208, 3254, 0)); + hotColdMessage.setMessage("The device is hot."); + plugin.onChatMessage(hotColdMessage); + + // Move to SW of DRAYNOR_WHEAT_FIELD (hint arrow should be visible here) + when(localPlayer.getWorldLocation()).thenReturn(new WorldPoint(3105, 3265, 0)); + when(client.getBaseX()).thenReturn(3056); + when(client.getBaseY()).thenReturn(3216); + plugin.onGameTick(new GameTick()); + verify(client, times(++clueSetupHintArrowClears)).clearHintArrow(); + verify(client).setHintArrow(HotColdLocation.DRAYNOR_WHEAT_FIELD.getWorldPoint()); + + // Test in that location (get 1 possible location: LUMBRIDGE_COW_FIELD) + hotColdMessage.setMessage("The device is hot, and warmer than last time."); + plugin.onChatMessage(hotColdMessage); + plugin.onGameTick(new GameTick()); + + // Hint arrow should be cleared and not re-set now as the only remaining location is outside of the current + // scene + verify(client, times(++clueSetupHintArrowClears)).clearHintArrow(); + verify(client, times(1)).setHintArrow(any(WorldPoint.class)); + } +} diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/emojis/EmojiPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/emojis/EmojiPluginTest.java new file mode 100644 index 0000000000..99fc2dc174 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/emojis/EmojiPluginTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2019, Adam + * 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.emojis; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.IndexedSprite; +import net.runelite.api.MessageNode; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameStateChanged; +import net.runelite.client.chat.ChatMessageManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(MockitoJUnitRunner.class) +public class EmojiPluginTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ChatMessageManager chatMessageManager; + + @Inject + private EmojiPlugin emojiPlugin; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + } + + @Test + public void testOnChatMessage() + { + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + when(client.getModIcons()).thenReturn(new IndexedSprite[0]); + when(client.createIndexedSprite()).thenReturn(mock(IndexedSprite.class)); + + // Trip emoji loading + GameStateChanged gameStateChanged = new GameStateChanged(); + gameStateChanged.setGameState(GameState.LOGGED_IN); + emojiPlugin.onGameStateChanged(gameStateChanged); + + MessageNode messageNode = mock(MessageNode.class); + // With chat recolor, message may be wrapped in col tags + when(messageNode.getValue()).thenReturn(":) :) :)"); + + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setType(ChatMessageType.PUBLICCHAT); + chatMessage.setMessageNode(messageNode); + + emojiPlugin.onChatMessage(chatMessage); + + verify(messageNode).setValue(" "); + } + + @Test + public void testGtLt() + { + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + when(client.getModIcons()).thenReturn(new IndexedSprite[0]); + when(client.createIndexedSprite()).thenReturn(mock(IndexedSprite.class)); + + // Trip emoji loading + GameStateChanged gameStateChanged = new GameStateChanged(); + gameStateChanged.setGameState(GameState.LOGGED_IN); + emojiPlugin.onGameStateChanged(gameStateChanged); + + MessageNode messageNode = mock(MessageNode.class); + when(messageNode.getValue()).thenReturn(":D"); + + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setType(ChatMessageType.PUBLICCHAT); + chatMessage.setMessageNode(messageNode); + + emojiPlugin.onChatMessage(chatMessage); + + verify(messageNode).setValue(""); + } + + @Test + public void testEmojiUpdateMessage() + { + String PARTY_POPPER = "'; + String OPEN_MOUTH = "'; + assertNull(emojiPlugin.updateMessage("@@@@@")); + assertEquals(PARTY_POPPER, emojiPlugin.updateMessage("@@@")); + assertEquals(PARTY_POPPER + ' ' + PARTY_POPPER, emojiPlugin.updateMessage("@@@ @@@")); + assertEquals(PARTY_POPPER + ' ' + OPEN_MOUTH, emojiPlugin.updateMessage("@@@\u00A0:O")); + assertEquals(PARTY_POPPER + ' ' + OPEN_MOUTH + ' ' + PARTY_POPPER, emojiPlugin.updateMessage("@@@\u00A0:O @@@")); + assertEquals(PARTY_POPPER + " Hello World " + PARTY_POPPER, emojiPlugin.updateMessage("@@@\u00A0Hello World\u00A0@@@")); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java new file mode 100644 index 0000000000..48d6296ca5 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/GroundItemsPluginTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2020, Adam + * 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.grounditems; + +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.util.concurrent.ScheduledExecutorService; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemID; +import net.runelite.api.ItemLayer; +import net.runelite.api.Player; +import net.runelite.api.Tile; +import net.runelite.api.TileItem; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ItemSpawned; +import net.runelite.client.Notifier; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.input.KeyManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.plugins.grounditems.config.HighlightTier; +import net.runelite.client.ui.overlay.OverlayManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class GroundItemsPluginTest +{ + @Inject + private GroundItemsPlugin groundItemsPlugin; + + @Mock + @Bind + private MouseManager mouseManager; + + @Mock + @Bind + private KeyManager keyManager; + + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ItemManager itemManager; + + @Mock + @Bind + private OverlayManager overlayManager; + + @Mock + @Bind + private GroundItemsConfig config; + + @Mock + @Bind + private GroundItemsOverlay overlay; + + @Mock + @Bind + private Notifier notifier; + + @Mock + @Bind + private ScheduledExecutorService executor; + + @Before + public void setUp() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + doAnswer(a -> + { + a.getArgument(0).run(); + return null; + }).when(executor).execute(any(Runnable.class)); + + when(client.getLocalPlayer()).thenReturn(mock(Player.class)); + when(config.getHiddenItems()).thenReturn(""); + } + + @Test + public void testNotifyHighlightedItem() + { + when(config.getHighlightItems()).thenReturn("abyssal whip"); + when(config.notifyTier()).thenReturn(HighlightTier.OFF); + when(config.notifyHighlightedDrops()).thenReturn(true); + + when(itemManager.getItemComposition(ItemID.ABYSSAL_WHIP)).thenAnswer(a -> + { + ItemComposition itemComposition = mock(ItemComposition.class); + when(itemComposition.getName()).thenReturn("Abyssal whip"); + return itemComposition; + }); + + // trigger reload of highlighted items list + ConfigChanged configChanged = new ConfigChanged(); + configChanged.setGroup("grounditems"); + groundItemsPlugin.onConfigChanged(configChanged); + + // spawn whip + Tile tile = mock(Tile.class); + when(tile.getItemLayer()).thenReturn(mock(ItemLayer.class)); + when(tile.getWorldLocation()).thenReturn(new WorldPoint(0, 0, 0)); + + TileItem tileItem = mock(TileItem.class); + when(tileItem.getId()).thenReturn(ItemID.ABYSSAL_WHIP); + when(tileItem.getQuantity()).thenReturn(1); + + groundItemsPlugin.onItemSpawned(new ItemSpawned(tile, tileItem)); + + verify(notifier).notify("You received a highlighted drop: Abyssal whip"); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java new file mode 100644 index 0000000000..bb91fffcb8 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPluginTest.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.idlenotifier; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import net.runelite.api.Actor; +import net.runelite.api.AnimationID; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Hitsplat; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import net.runelite.api.Player; +import net.runelite.api.VarPlayer; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.InteractingChanged; +import net.runelite.client.Notifier; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import org.mockito.Mock; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class IdleNotifierPluginTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private IdleNotifierConfig config; + + @Mock + @Bind + private Notifier notifier; + + @Inject + private IdleNotifierPlugin plugin; + + @Mock + private NPC monster; + + @Mock + private NPC randomEvent; + + @Mock + private NPC fishingSpot; + + @Mock + private Player player; + + @Before + public void setUp() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + // Mock monster + final String[] monsterActions = new String[] { "Attack", "Examine" }; + final NPCComposition monsterComp = mock(NPCComposition.class); + when(monsterComp.getActions()).thenReturn(monsterActions); + when(monster.getComposition()).thenReturn(monsterComp); + + // Mock random event + final String[] randomEventActions = new String[] { "Talk-to", "Dismiss", "Examine" }; + final NPCComposition randomEventComp = mock(NPCComposition.class); + when(randomEventComp.getActions()).thenReturn(randomEventActions); + when(randomEvent.getComposition()).thenReturn(randomEventComp); + + // Mock Fishing Spot + final String[] fishingSpotActions = new String[] { "Use-rod", "Examine" }; + final NPCComposition fishingSpotComp = mock(NPCComposition.class); + when(fishingSpotComp.getActions()).thenReturn(fishingSpotActions); + when(fishingSpot.getComposition()).thenReturn(fishingSpotComp); + when(fishingSpot.getName()).thenReturn("Fishing spot"); + + // Mock player + when(player.getAnimation()).thenReturn(AnimationID.IDLE); + when(client.getLocalPlayer()).thenReturn(player); + + // Mock config + when(config.logoutIdle()).thenReturn(true); + when(config.animationIdle()).thenReturn(true); + when(config.interactionIdle()).thenReturn(true); + when(config.getIdleNotificationDelay()).thenReturn(0); + when(config.getHitpointsThreshold()).thenReturn(42); + when(config.getPrayerThreshold()).thenReturn(42); + + // Mock client + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + when(client.getKeyboardIdleTicks()).thenReturn(42); + when(client.getMouseLastPressedMillis()).thenReturn(System.currentTimeMillis() - 100_000L); + } + + @Test + public void checkAnimationIdle() + { + when(player.getAnimation()).thenReturn(AnimationID.WOODCUTTING_BRONZE); + AnimationChanged animationChanged = new AnimationChanged(); + animationChanged.setActor(player); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + when(player.getAnimation()).thenReturn(AnimationID.IDLE); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + verify(notifier).notify("You are now idle!"); + } + + @Test + public void checkAnimationReset() + { + when(player.getAnimation()).thenReturn(AnimationID.WOODCUTTING_BRONZE); + AnimationChanged animationChanged = new AnimationChanged(); + animationChanged.setActor(player); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + when(player.getAnimation()).thenReturn(AnimationID.LOOKING_INTO); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + when(player.getAnimation()).thenReturn(AnimationID.IDLE); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + verify(notifier, times(0)).notify(any()); + } + + @Test + public void checkAnimationLogout() + { + when(player.getAnimation()).thenReturn(AnimationID.WOODCUTTING_BRONZE); + AnimationChanged animationChanged = new AnimationChanged(); + animationChanged.setActor(player); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + + // Logout + when(client.getGameState()).thenReturn(GameState.LOGIN_SCREEN); + GameStateChanged gameStateChanged = new GameStateChanged(); + gameStateChanged.setGameState(GameState.LOGIN_SCREEN); + plugin.onGameStateChanged(gameStateChanged); + + // Log back in + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + gameStateChanged.setGameState(GameState.LOGGED_IN); + plugin.onGameStateChanged(gameStateChanged); + + // Tick + when(player.getAnimation()).thenReturn(AnimationID.IDLE); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + verify(notifier, times(0)).notify(any()); + } + + @Test + public void checkCombatIdle() + { + when(player.getInteracting()).thenReturn(monster); + plugin.onInteractingChanged(new InteractingChanged(player, monster)); + plugin.onGameTick(new GameTick()); + when(player.getInteracting()).thenReturn(null); + plugin.onInteractingChanged(new InteractingChanged(player, null)); + plugin.onGameTick(new GameTick()); + verify(notifier).notify("You are now out of combat!"); + } + + @Test + public void checkCombatReset() + { + when(player.getInteracting()).thenReturn(mock(Actor.class)); + plugin.onInteractingChanged(new InteractingChanged(player, monster)); + plugin.onGameTick(new GameTick()); + plugin.onInteractingChanged(new InteractingChanged(player, randomEvent)); + plugin.onGameTick(new GameTick()); + plugin.onInteractingChanged(new InteractingChanged(player, null)); + plugin.onGameTick(new GameTick()); + verify(notifier, times(0)).notify(any()); + } + + @Test + public void checkCombatLogout() + { + plugin.onInteractingChanged(new InteractingChanged(player, monster)); + when(player.getInteracting()).thenReturn(mock(Actor.class)); + plugin.onGameTick(new GameTick()); + + // Logout + when(client.getGameState()).thenReturn(GameState.LOGIN_SCREEN); + GameStateChanged gameStateChanged = new GameStateChanged(); + gameStateChanged.setGameState(GameState.LOGIN_SCREEN); + plugin.onGameStateChanged(gameStateChanged); + + // Log back in + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + gameStateChanged.setGameState(GameState.LOGGED_IN); + plugin.onGameStateChanged(gameStateChanged); + + // Tick + plugin.onInteractingChanged(new InteractingChanged(player, null)); + plugin.onGameTick(new GameTick()); + verify(notifier, times(0)).notify(any()); + } + + @Test + public void checkCombatLogoutIdle() + { + // Player is idle + when(client.getMouseIdleTicks()).thenReturn(80_000); + + // But player is being damaged (is in combat) + final HitsplatApplied hitsplatApplied = new HitsplatApplied(); + hitsplatApplied.setActor(player); + hitsplatApplied.setHitsplat(new Hitsplat(Hitsplat.HitsplatType.DAMAGE_ME, 0, 0)); + plugin.onHitsplatApplied(hitsplatApplied); + plugin.onGameTick(new GameTick()); + verify(notifier, times(0)).notify(any()); + } + + @Test + public void doubleNotifyOnMouseReset() + { + // Player is idle, but in combat so the idle packet is getting set repeatedly + // make sure we are not notifying + + when(client.getKeyboardIdleTicks()).thenReturn(80_000); + when(client.getMouseIdleTicks()).thenReturn(14_500); + + plugin.onGameTick(new GameTick()); + plugin.onGameTick(new GameTick()); + verify(notifier, times(1)).notify(any()); + } + + @Test + public void testSendOneNotificationForAnimationAndInteract() + { + when(player.getInteracting()).thenReturn(fishingSpot); + when(player.getAnimation()).thenReturn(AnimationID.FISHING_POLE_CAST); + + AnimationChanged animationChanged = new AnimationChanged(); + animationChanged.setActor(player); + + plugin.onInteractingChanged(new InteractingChanged(player, fishingSpot)); + plugin.onAnimationChanged(animationChanged); + plugin.onGameTick(new GameTick()); + + verify(notifier, never()).notify(anyString()); + + when(player.getAnimation()).thenReturn(AnimationID.IDLE); + lenient().when(player.getInteracting()).thenReturn(null); + + plugin.onAnimationChanged(animationChanged); + plugin.onInteractingChanged(new InteractingChanged(player, null)); + plugin.onGameTick(new GameTick()); + + verify(notifier).notify("You are now idle!"); + } + + @Test + public void testSpecRegen() + { + when(config.getSpecEnergyThreshold()).thenReturn(50); + + when(client.getVar(eq(VarPlayer.SPECIAL_ATTACK_PERCENT))).thenReturn(400); // 40% + plugin.onGameTick(new GameTick()); // once to set lastSpecEnergy to 400 + verify(notifier, never()).notify(any()); + + when(client.getVar(eq(VarPlayer.SPECIAL_ATTACK_PERCENT))).thenReturn(500); // 50% + plugin.onGameTick(new GameTick()); + verify(notifier).notify(eq("You have restored spec energy!")); + } + + @Test + public void testMovementIdle() + { + when(config.movementIdle()).thenReturn(true); + + when(player.getWorldLocation()).thenReturn(new WorldPoint(0, 0, 0)); + plugin.onGameTick(new GameTick()); + when(player.getWorldLocation()).thenReturn(new WorldPoint(1, 0, 0)); + plugin.onGameTick(new GameTick()); + // No movement here + plugin.onGameTick(new GameTick()); + + verify(notifier).notify(eq("You have stopped moving!")); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java index 5386db8202..edd5101817 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java @@ -57,6 +57,7 @@ import org.mockito.Mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; @@ -67,7 +68,8 @@ public class ScreenshotPluginTest private static final String BARROWS_CHEST = "Your Barrows chest count is 310"; private static final String CHAMBERS_OF_XERIC_CHEST = "Your completed Chambers of Xeric count is: 489."; private static final String THEATRE_OF_BLOOD_CHEST = "Your completed Theatre of Blood count is: 73."; - private static final String VALUABLE_DROP = "Valuable drop: 6 x Bronze arrow (42 coins)"; + private static final String NOT_SO_VALUABLE_DROP = "Valuable drop: 6 x Bronze arrow (42 coins)"; + private static final String VALUABLE_DROP = "Valuable drop: Rune scimitar (25,600 coins)"; private static final String UNTRADEABLE_DROP = "Untradeable drop: Rusty sword"; private static final String BA_HIGH_GAMBLE_REWARD = "Raw shark (x 300)!
High level gamble count: 100"; private static final String HUNTER_LEVEL_2_TEXT = "Congratulations, you've just advanced a Hunter level.

Your Hunter level is now 2."; @@ -121,6 +123,7 @@ public class ScreenshotPluginTest Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); when(screenshotConfig.screenshotLevels()).thenReturn(true); when(screenshotConfig.screenshotValuableDrop()).thenReturn(true); + when(screenshotConfig.valuableDropThreshold()).thenReturn(1000); when(screenshotConfig.screenshotUntradeableDrop()).thenReturn(true); } @@ -161,10 +164,30 @@ public class ScreenshotPluginTest assertEquals(73, screenshotPlugin.gettheatreOfBloodNumber()); } + @Test + public void testNotSoValuableDrop() + { + ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", NOT_SO_VALUABLE_DROP, null, 0); + screenshotPlugin.onChatMessage(chatMessageEvent); + + verifyNoInteractions(drawManager); + + when(screenshotConfig.valuableDropThreshold()).thenReturn(0); + screenshotPlugin.onChatMessage(chatMessageEvent); + + verify(drawManager).requestNextFrameListener(any(Consumer.class)); + } + @Test public void testValuableDrop() { ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", VALUABLE_DROP, null, 0); + when(screenshotConfig.valuableDropThreshold()).thenReturn(100_000); + screenshotPlugin.onChatMessage(chatMessageEvent); + + verifyNoInteractions(drawManager); + + when(screenshotConfig.valuableDropThreshold()).thenReturn(1000); screenshotPlugin.onChatMessage(chatMessageEvent); verify(drawManager).requestNextFrameListener(any(Consumer.class)); diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/FarmingTrackerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/FarmingTrackerTest.java new file mode 100644 index 0000000000..f407fd0a34 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/FarmingTrackerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021, Adam + * 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.timetracking.farming; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.util.EnumSet; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.api.Varbits; +import net.runelite.api.WorldType; +import net.runelite.client.Notifier; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.RuneScapeProfile; +import net.runelite.client.config.RuneScapeProfileType; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.timetracking.TimeTrackingConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class FarmingTrackerTest +{ + @Inject + private FarmingTracker farmingTracker; + + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ItemManager itemManager; + + @Mock + @Bind + private ConfigManager configManager; + + @Mock + @Bind + private TimeTrackingConfig config; + + @Mock + @Bind + private FarmingWorld farmingWorld; + + @Mock + @Bind + private Notifier notifier; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + when(client.getWorldType()).thenReturn(EnumSet.noneOf(WorldType.class)); + + Player player = mock(Player.class); + when(player.getName()).thenReturn("Adam"); + when(client.getLocalPlayer()).thenReturn(player); + } + + @Test(expected = IllegalStateException.class) + public void testEmptyNotification() + { + RuneScapeProfile runeScapeProfile = new RuneScapeProfile("Adam", RuneScapeProfileType.STANDARD, null, null); + + PatchPrediction patchPrediction = new PatchPrediction(Produce.EMPTY_COMPOST_BIN, CropState.EMPTY, 0L, 0, 0); + FarmingRegion region = new FarmingRegion("Ardougne", 10548, false, + new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), + new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB), + new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST) + ); + FarmingPatch patch = region.getPatches()[4]; + patch.setRegion(region); + farmingTracker.sendNotification(runeScapeProfile, patchPrediction, patch); + } + + @Test + public void testHarvestableNotification() + { + RuneScapeProfile runeScapeProfile = new RuneScapeProfile("Adam", RuneScapeProfileType.STANDARD, null, null); + + PatchPrediction patchPrediction = new PatchPrediction(Produce.RANARR, CropState.HARVESTABLE, 0L, 0, 0); + FarmingRegion region = new FarmingRegion("Ardougne", 10548, false, + new FarmingPatch("North", Varbits.FARMING_4771, PatchImplementation.ALLOTMENT), + new FarmingPatch("South", Varbits.FARMING_4772, PatchImplementation.ALLOTMENT), + new FarmingPatch("", Varbits.FARMING_4773, PatchImplementation.FLOWER), + new FarmingPatch("", Varbits.FARMING_4774, PatchImplementation.HERB), + new FarmingPatch("", Varbits.FARMING_4775, PatchImplementation.COMPOST) + ); + FarmingPatch patch = region.getPatches()[3]; + patch.setRegion(region); + farmingTracker.sendNotification(runeScapeProfile, patchPrediction, patch); + + verify(notifier).notify("Your Ranarr is ready to harvest in Ardougne."); + } +} \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderBridgeMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderBridgeMixin.java index 4d15ae5cf8..54745afbf2 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderBridgeMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderBridgeMixin.java @@ -25,15 +25,13 @@ */ package net.runelite.mixins; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.rs.api.RSClient; +import java.util.ArrayList; +import java.util.List; + @Mixin(RSClient.class) public abstract class EntityHiderBridgeMixin implements RSClient { @@ -41,10 +39,10 @@ public abstract class EntityHiderBridgeMixin implements RSClient public static boolean isHidingEntities; @Inject - public static boolean hidePlayers; + public static boolean hideOthers; @Inject - public static boolean hidePlayers2D; + public static boolean hideOthers2D; @Inject public static boolean hideFriends; @@ -52,6 +50,9 @@ public abstract class EntityHiderBridgeMixin implements RSClient @Inject public static boolean hideClanMates; + @Inject + public static boolean hideIgnores; + @Inject public static boolean hideLocalPlayer; @@ -76,21 +77,9 @@ public abstract class EntityHiderBridgeMixin implements RSClient @Inject public static boolean hideDeadNPCs; - @Inject - public static HashMap hiddenNpcsName = new HashMap<>(); - - @Inject - public static HashMap hiddenNpcsDeath = new HashMap<>(); - @Inject public static List hideSpecificPlayers = new ArrayList<>(); - @Inject - public static Set blacklistDeadNpcs = new HashSet<>(); - - @Inject - public static List hiddenNpcIndices = new ArrayList<>(); - @Inject @Override public void setIsHidingEntities(boolean state) @@ -100,16 +89,16 @@ public abstract class EntityHiderBridgeMixin implements RSClient @Inject @Override - public void setPlayersHidden(boolean state) + public void setOthersHidden(boolean state) { - hidePlayers = state; + hideOthers = state; } @Inject @Override - public void setPlayersHidden2D(boolean state) + public void setOthersHidden2D(boolean state) { - hidePlayers2D = state; + hideOthers2D = state; } @Inject @@ -126,6 +115,13 @@ public abstract class EntityHiderBridgeMixin implements RSClient hideClanMates = state; } + @Inject + @Override + public void setIgnoresHidden(boolean state) + { + hideIgnores = state; + } + @Inject @Override public void setLocalPlayerHidden(boolean state) @@ -154,78 +150,6 @@ public abstract class EntityHiderBridgeMixin implements RSClient hideNPCs2D = state; } - @Inject - @Override - public void addHiddenNpcName(String npc) - { - npc = npc.toLowerCase(); - int i = hiddenNpcsName.getOrDefault(npc, 0); - if (i == Integer.MAX_VALUE) - { - throw new RuntimeException("NPC name " + npc + " has been hidden Integer.MAX_VALUE times, is something wrong?"); - } - - hiddenNpcsName.put(npc, ++i); - } - - @Inject - @Override - public void removeHiddenNpcName(String npc) - { - npc = npc.toLowerCase(); - int i = hiddenNpcsName.getOrDefault(npc, 0); - if (i == 0) - { - return; - } - - hiddenNpcsName.put(npc, --i); - } - - @Inject - @Override - public void forciblyUnhideNpcName(String npc) - { - npc = npc.toLowerCase(); - hiddenNpcsName.put(npc, 0); - } - - @Inject - @Override - public void addHiddenNpcDeath(String npc) - { - npc = npc.toLowerCase(); - int i = hiddenNpcsDeath.getOrDefault(npc, 0); - if (i == Integer.MAX_VALUE) - { - throw new RuntimeException("NPC death " + npc + " has been hidden Integer.MAX_VALUE times, is something wrong?"); - } - - hiddenNpcsDeath.put(npc, ++i); - } - - @Inject - @Override - public void removeHiddenNpcDeath(String npc) - { - npc = npc.toLowerCase(); - int i = hiddenNpcsDeath.getOrDefault(npc, 0); - if (i == 0) - { - return; - } - - hiddenNpcsDeath.put(npc, --i); - } - - @Inject - @Override - public void forciblyUnhideNpcDeath(String npc) - { - npc = npc.toLowerCase(); - hiddenNpcsDeath.put(npc, 0); - } - @Inject @Override public void setHideSpecificPlayers(List players) @@ -233,13 +157,6 @@ public abstract class EntityHiderBridgeMixin implements RSClient hideSpecificPlayers = players; } - @Inject - @Override - public void setBlacklistDeadNpcs(Set blacklist) - { - blacklistDeadNpcs = blacklist; - } - @Inject @Override public void setPetsHidden(boolean state) @@ -267,18 +184,4 @@ public abstract class EntityHiderBridgeMixin implements RSClient { hideDeadNPCs = state; } - - @Inject - @Override - public void setHiddenNpcIndices(List npcIndices) - { - hiddenNpcIndices = npcIndices; - } - - @Inject - @Override - public List getHiddenNpcIndices() - { - return hiddenNpcIndices; - } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java index fc30ebb953..6a07951e73 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/EntityHiderMixin.java @@ -25,22 +25,10 @@ */ package net.runelite.mixins; -import java.util.HashMap; +import net.runelite.api.mixins.*; +import net.runelite.rs.api.*; + import java.util.List; -import java.util.Set; -import net.runelite.api.mixins.Copy; -import net.runelite.api.mixins.Inject; -import net.runelite.api.mixins.Mixin; -import net.runelite.api.mixins.Replace; -import net.runelite.api.mixins.Shadow; -import net.runelite.api.util.Text; -import net.runelite.rs.api.RSActor; -import net.runelite.rs.api.RSClient; -import net.runelite.rs.api.RSRenderable; -import net.runelite.rs.api.RSNPC; -import net.runelite.rs.api.RSPlayer; -import net.runelite.rs.api.RSProjectile; -import net.runelite.rs.api.RSScene; @Mixin(RSScene.class) public abstract class EntityHiderMixin implements RSScene @@ -51,11 +39,11 @@ public abstract class EntityHiderMixin implements RSScene @Shadow("isHidingEntities") private static boolean isHidingEntities; - @Shadow("hidePlayers") - private static boolean hidePlayers; + @Shadow("hideOthers") + private static boolean hideOthers; - @Shadow("hidePlayers2D") - private static boolean hidePlayers2D; + @Shadow("hideOthers2D") + private static boolean hideOthers2D; @Shadow("hideFriends") private static boolean hideFriends; @@ -69,21 +57,15 @@ public abstract class EntityHiderMixin implements RSScene @Shadow("hideLocalPlayer2D") private static boolean hideLocalPlayer2D; + @Shadow("hideIgnores") + private static boolean hideIgnores; + @Shadow("hideNPCs") private static boolean hideNPCs; - @Shadow("hiddenNpcsName") - private static HashMap hiddenNpcsName; - - @Shadow("hiddenNpcsDeath") - private static HashMap hiddenNpcsDeath; - @Shadow("hideSpecificPlayers") private static List hideSpecificPlayers; - @Shadow("blacklistDeadNpcs") - private static Set blacklistDeadNpcs; - @Shadow("hideNPCs2D") private static boolean hideNPCs2D; @@ -99,9 +81,6 @@ public abstract class EntityHiderMixin implements RSScene @Shadow("hideDeadNPCs") private static boolean hideDeadNPCs; - @Shadow("hiddenNpcIndices") - private static List hiddenNpcIndices; - @Copy("newGameObject") @Replace("newGameObject") boolean copy$addEntityMarker(int var1, int var2, int var3, int var4, int var5, int x, int y, int var8, RSRenderable entity, int var10, boolean var11, long var12, int var13) @@ -143,10 +122,17 @@ public abstract class EntityHiderMixin implements RSScene if (entity instanceof RSPlayer) { - boolean local = drawingUI ? hideLocalPlayer2D : hideLocalPlayer; - boolean other = drawingUI ? hidePlayers2D : hidePlayers; - boolean isLocalPlayer = entity == client.getLocalPlayer(); RSPlayer player = (RSPlayer) entity; + RSPlayer local = client.getLocalPlayer(); + if (player.getName() == null) + { + return true; + } + + if (player == local) + { + return drawingUI ? !hideLocalPlayer2D : !hideLocalPlayer; + } for (String name : hideSpecificPlayers) { @@ -159,71 +145,47 @@ public abstract class EntityHiderMixin implements RSScene } } - if (isLocalPlayer ? local : other) + if (hideAttackers && player.getInteracting() == local) { - if (!hideAttackers) - { - if (player.getInteracting() == client.getLocalPlayer()) - { - return true; - } - } - - if (player.getName() == null) - { - // player.isFriend() and player.isClanMember() npe when the player has a null name - return false; - } - - return (!hideFriends && player.isFriend()) || - (!isLocalPlayer && !hideClanMates && player.isFriendsChatMember()); + return false; } + + if (player.isFriend()) + { + return !hideFriends; + } + + if (player.isFriendsChatMember()) + { + return !hideClanMates; + } + + if (client.getFriendManager().isIgnored(player.getRsName())) + { + return !hideIgnores; + } + + return drawingUI ? !hideOthers2D : !hideOthers; } else if (entity instanceof RSNPC) { RSNPC npc = (RSNPC) entity; - if (!hideAttackers) - { - if (npc.getInteracting() == client.getLocalPlayer()) - { - return true; - } - } - - if (hidePets) - { - if (npc.getComposition().isFollower()) - { - return false; - } - } - - if (hideDeadNPCs && npc.getHealthRatio() == 0 && !blacklistDeadNpcs.contains(npc.getId())) + if (npc.isDead() && hideDeadNPCs) { return false; } - if (npc.getName() != null && - hiddenNpcsName.getOrDefault(Text.standardize(npc.getName().toLowerCase()), 0) > 0) + if (npc.getComposition().isFollower() && npc.getIndex() != client.getFollowerIndex() && hidePets) { return false; } - if (npc.getName() != null && npc.getHealthRatio() == 0 && - hiddenNpcsDeath.getOrDefault(Text.standardize(npc.getName().toLowerCase()), 0) > 0) + if (npc.getInteracting() == client.getLocalPlayer() && hideAttackers) { return false; } - for (Integer index : hiddenNpcIndices) - { - if (index != null && npc.getIndex() == index) - { - return false; - } - } - return drawingUI ? !hideNPCs2D : !hideNPCs; } else if (entity instanceof RSProjectile) diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java index 15000aee40..1096f359fe 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java @@ -292,4 +292,11 @@ public abstract class RSActorMixin implements RSActor event.setHitsplat(hitsplat); client.getCallbacks().post(event); } + + @Inject + @Override + public boolean isMoving() + { + return getPathLength() > 0; + } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 644a1346d3..7c2bf5c580 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -52,6 +52,8 @@ import net.runelite.api.IndexedSprite; import net.runelite.api.IntegerNode; import net.runelite.api.InventoryID; import net.runelite.api.ItemComposition; +import net.runelite.api.MenuAction; +import static net.runelite.api.MenuAction.*; import net.runelite.api.MenuEntry; import net.runelite.api.MenuAction; import static net.runelite.api.MenuAction.PLAYER_EIGTH_OPTION; @@ -536,17 +538,36 @@ public abstract class RSClientMixin implements RSClient } @Inject - public void addChatMessage(int type, String name, String message, String sender) + public MessageNode addChatMessage(ChatMessageType type, String name, String message, String sender, boolean postEvent) { assert this.isClientThread() : "addChatMessage must be called on client thread"; - addRSChatMessage(type, name, message, sender); + copy$addChatMessage(type.getType(), name, message, sender); + + Logger logger = client.getLogger(); + if (logger.isDebugEnabled()) + { + logger.debug("Chat message type {}: {}", type.name(), message); + } + + // Get the message node which was added + @SuppressWarnings("unchecked") Map chatLineMap = client.getChatLineMap(); + RSChatChannel chatLineBuffer = chatLineMap.get(type.getType()); + MessageNode messageNode = chatLineBuffer.getLines()[0]; + + if (postEvent) + { + final ChatMessage chatMessage = new ChatMessage(messageNode, type, name, message, sender, messageNode.getTimestamp()); + client.getCallbacks().post(chatMessage); + } + + return messageNode; } @Inject @Override - public void addChatMessage(ChatMessageType type, String name, String message, String sender) + public MessageNode addChatMessage(ChatMessageType type, String name, String message, String sender) { - addChatMessage(type.getType(), name, message, sender); + return addChatMessage(type, name, message, sender, true); } @Inject @@ -1388,13 +1409,6 @@ public abstract class RSClientMixin implements RSClient @Replace("menuAction") static void copy$menuAction(int param0, int param1, int opcode, int id, String option, String target, int canvasX, int canvasY) { - boolean authentic = true; - if (target != null && target.startsWith("!AUTHENTIC")) - { - authentic = false; - target = target.substring(10); - } - /* Along the way, the RuneScape client may change a menuAction by incrementing it with 2000. * I have no idea why, but it does. Their code contains the same conditional statement. */ @@ -1403,17 +1417,13 @@ public abstract class RSClientMixin implements RSClient opcode -= 2000; } - final MenuOptionClicked menuOptionClicked = new MenuOptionClicked( - option, - target, - id, - opcode, - param0, - param1, - false, - authentic, - client.getMouseCurrentButton() - ); + final MenuOptionClicked menuOptionClicked = new MenuOptionClicked(); + menuOptionClicked.setActionParam(param0); + menuOptionClicked.setMenuOption(option); + menuOptionClicked.setMenuTarget(target); + menuOptionClicked.setMenuAction(MenuAction.of(opcode)); + menuOptionClicked.setId(id); + menuOptionClicked.setWidgetId(param1); client.getCallbacks().post(menuOptionClicked); @@ -1425,15 +1435,15 @@ public abstract class RSClientMixin implements RSClient if (printMenuActions) { client.getLogger().info( - "|MenuAction|: MenuOption={} MenuTarget={} Id={} Opcode={} Param0={} Param1={} CanvasX={} CanvasY={} Authentic={}", - menuOptionClicked.getOption(), menuOptionClicked.getTarget(), menuOptionClicked.getIdentifier(), - menuOptionClicked.getOpcode(), menuOptionClicked.getActionParam(), menuOptionClicked.getActionParam1(), - canvasX, canvasY, authentic + "|MenuAction|: MenuOption={} MenuTarget={} Id={} Opcode={} Param0={} Param1={} CanvasX={} CanvasY={}", + menuOptionClicked.getMenuOption(), menuOptionClicked.getMenuTarget(), menuOptionClicked.getId(), + menuOptionClicked.getMenuAction(), menuOptionClicked.getActionParam(), menuOptionClicked.getWidgetId(), + canvasX, canvasY ); } - copy$menuAction(menuOptionClicked.getActionParam(), menuOptionClicked.getActionParam1(), menuOptionClicked.getOpcode(), - menuOptionClicked.getIdentifier(), menuOptionClicked.getOption(), menuOptionClicked.getTarget(), canvasX, canvasY); + copy$menuAction(menuOptionClicked.getActionParam(), menuOptionClicked.getWidgetId(), menuOptionClicked.getMenuAction().getId(), + menuOptionClicked.getId(), menuOptionClicked.getMenuOption(), menuOptionClicked.getMenuTarget(), canvasX, canvasY); } @Override @@ -1442,7 +1452,7 @@ public abstract class RSClientMixin implements RSClient { assert isClientThread(); - client.sendMenuAction(param0, param1, opcode, identifier, option, "!AUTHENTIC" + target, 658, 384); + client.sendMenuAction(param0, param1, opcode, identifier, option, target, 658, 384); } @FieldHook("Login_username") @@ -1495,14 +1505,18 @@ public abstract class RSClientMixin implements RSClient client.getCallbacks().updateNpcs(); } - @Inject - @MethodHook(value = "addChatMessage", end = true) - public static void onAddChatMessage(int type, String name, String message, String sender) + @SuppressWarnings("InfiniteRecursion") + @Copy("addChatMessage") + @Replace("addChatMessage") + public static void copy$addChatMessage(int type, String name, String message, String sender) { + copy$addChatMessage(type, name, message, sender); + Logger logger = client.getLogger(); if (logger.isDebugEnabled()) { - logger.debug("Chat message type {}: {}", ChatMessageType.of(type), message); + ChatMessageType msgType = ChatMessageType.of(type); + logger.debug("Chat message type {}: {}", msgType == ChatMessageType.UNKNOWN ? String.valueOf(type) : msgType.name(), message); } // Get the message node which was added @@ -1526,12 +1540,11 @@ public abstract class RSClientMixin implements RSClient @Inject public static void preRenderWidgetLayer(Widget[] widgets, int parentId, int minX, int minY, int maxX, int maxY, int x, int y, int var8) { - Callbacks callbacks = client.getCallbacks(); @SuppressWarnings("unchecked") HashTable componentTable = client.getComponentTable(); - for (Widget rlWidget : widgets) + for (int i = 0; i < widgets.length; i++) { - RSWidget widget = (RSWidget) rlWidget; + RSWidget widget = (RSWidget) widgets[i]; if (widget == null || widget.getRSParentId() != parentId || widget.isSelfHidden()) { continue; @@ -1547,42 +1560,36 @@ public abstract class RSClientMixin implements RSClient widget.setRenderX(renderX); widget.setRenderY(renderY); - if (widget.getContentType() == WidgetType.VIEWPORT) + if (widget.getType() == WidgetType.RECTANGLE && renderX == client.getViewportXOffset() && renderY == client.getViewportYOffset() + && widget.getWidth() == client.getViewportWidth() && widget.getHeight() == client.getViewportHeight() + && widget.getOpacity() > 0 && widget.isFilled() && widget.getFillMode().getOrdinal() == 0 && client.isGpu()) { - viewportColor = 0; + int tc = widget.getTextColor(); + int alpha = widget.getOpacity() & 0xFF; + int inverseAlpha = 256 - alpha; + int vpc = viewportColor; + int c1 = (inverseAlpha * (tc & 0xFF00FF) >> 8 & 0xFF00FF) + (inverseAlpha * (tc & 0x00FF00) >> 8 & 0x00FF00); + int c2 = (alpha * (vpc & 0xFF00FF) >> 8 & 0xFF00FF) + (alpha * (vpc & 0x00FF00) >> 8 & 0x00FF00); + int outAlpha = inverseAlpha + ((vpc >>> 24) * (255 - inverseAlpha) * 0x8081 >>> 23); + viewportColor = outAlpha << 24 | c1 + c2; + widget.setHidden(true); + hiddenWidgets.add(widget); } - else if (widget.getType() == WidgetType.RECTANGLE) + else { - if (renderX == client.getViewportXOffset() && renderY == client.getViewportYOffset() - && widget.getWidth() == client.getViewportWidth() && widget.getHeight() == client.getViewportHeight() - && widget.getOpacity() > 0 && widget.isFilled() && client.isGpu()) + WidgetNode childNode = componentTable.get(widget.getId()); + if (childNode != null) { - int tc = widget.getTextColor(); - int alpha = widget.getOpacity() & 0xFF; - int inverseAlpha = 256 - alpha; - int vpc = viewportColor; - int c1 = (alpha * (tc & 0xff00ff) >> 8 & 0xFF00FF) + (alpha * (tc & 0x00FF00) >> 8 & 0x00FF00); - int c2 = (inverseAlpha * (vpc & 0xff00ff) >> 8 & 0xFF00FF) + (inverseAlpha * (vpc & 0x00FF00) >> 8 & 0x00FF00); - int outAlpha = alpha + ((vpc >>> 24) * (255 - alpha) * 0x8081 >>> 23); - viewportColor = outAlpha << 24 | c1 + c2; - widget.setHidden(true); - hiddenWidgets.add(widget); - continue; - } - } + int widgetId = widget.getId(); + int groupId = childNode.getId(); + RSWidget[] children = client.getWidgets()[groupId]; - WidgetNode childNode = componentTable.get(widget.getId()); - if (childNode != null) - { - int widgetId = widget.getId(); - int groupId = childNode.getId(); - RSWidget[] children = client.getWidgets()[groupId]; - - for (RSWidget child : children) - { - if (child.getRSParentId() == -1) + for (RSWidget child : children) { - child.setRenderParentId(widgetId); + if (child.getRSParentId() == -1) + { + child.setRenderParentId(widgetId); + } } } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameEngineMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameEngineMixin.java index 2573152106..948cfd97c2 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameEngineMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameEngineMixin.java @@ -88,6 +88,7 @@ public abstract class RSGameEngineMixin implements RSGameEngine if (drawCallbacks != null) { drawCallbacks.draw(viewportColor); + viewportColor = 0; } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java index 5d29390a45..c90105f7e4 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSNPCMixin.java @@ -111,7 +111,18 @@ public abstract class RSNPCMixin implements RSNPC } else if (this.getId() != -1) { - client.getCallbacks().post(new NpcChanged(this, composition)); + RSNPCComposition oldComposition = getComposition(); + if (oldComposition == null) + { + return; + } + + if (composition.getId() == oldComposition.getId()) + { + return; + } + + client.getCallbacks().postDeferred(new NpcChanged(this, oldComposition)); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java index 2200710ada..80c4f3fcfd 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java @@ -28,26 +28,14 @@ import java.awt.Polygon; import java.awt.Shape; import java.util.ArrayList; import net.runelite.api.HeadIcon; -import static net.runelite.api.HeadIcon.MAGIC; -import static net.runelite.api.HeadIcon.MELEE; -import static net.runelite.api.HeadIcon.RANGED; -import static net.runelite.api.HeadIcon.REDEMPTION; -import static net.runelite.api.HeadIcon.RETRIBUTION; -import static net.runelite.api.HeadIcon.SMITE; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.SkullIcon; -import static net.runelite.api.SkullIcon.DEAD_MAN_FIVE; -import static net.runelite.api.SkullIcon.DEAD_MAN_FOUR; -import static net.runelite.api.SkullIcon.DEAD_MAN_ONE; -import static net.runelite.api.SkullIcon.DEAD_MAN_THREE; -import static net.runelite.api.SkullIcon.DEAD_MAN_TWO; -import static net.runelite.api.SkullIcon.SKULL; -import static net.runelite.api.SkullIcon.SKULL_FIGHT_PIT; +import static net.runelite.api.SkullIcon.*; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.events.OverheadPrayerChanged; import net.runelite.api.events.PlayerChanged; -import net.runelite.api.events.player.headicon.OverheadPrayerChanged; -import net.runelite.api.events.player.headicon.PlayerSkullChanged; +import net.runelite.api.events.PlayerSkullChanged; import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.FieldHook; import net.runelite.api.mixins.Inject; @@ -71,10 +59,10 @@ public abstract class RSPlayerMixin implements RSPlayer private boolean friended; @Inject - private int oldHeadIcon = -1; + private int oldHeadIcon = -2; @Inject - private int oldSkullIcon = -1; + private int oldSkullIcon = -2; @Inject @Override @@ -101,14 +89,11 @@ public abstract class RSPlayerMixin implements RSPlayer @FieldHook("headIconPrayer") public void prayerChanged(int idx) { - if (!(getRsOverheadIcon() == -1 && oldHeadIcon == -1)) + if (getRsOverheadIcon() != oldHeadIcon) { final HeadIcon headIcon = getHeadIcon(getRsOverheadIcon()); - if (getRsOverheadIcon() != oldHeadIcon) - { - client.getCallbacks().post( - new OverheadPrayerChanged(this, getHeadIcon(oldHeadIcon), headIcon)); - } + client.getCallbacks().post( + new OverheadPrayerChanged(this, getHeadIcon(oldHeadIcon), headIcon)); } oldHeadIcon = getRsOverheadIcon(); } @@ -143,23 +128,12 @@ public abstract class RSPlayerMixin implements RSPlayer @Inject private HeadIcon getHeadIcon(int overheadIcon) { - switch (overheadIcon) + if (overheadIcon == -1) { - case 0: - return MELEE; - case 1: - return RANGED; - case 2: - return MAGIC; - case 3: - return RETRIBUTION; - case 4: - return SMITE; - case 5: - return REDEMPTION; - default: - return null; + return null; } + + return HeadIcon.values()[overheadIcon]; } @Inject diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java index c940376ba4..3bc5fb52ef 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java @@ -28,6 +28,7 @@ import net.runelite.api.HashTable; import net.runelite.api.Node; import net.runelite.api.Point; import net.runelite.api.WidgetNode; +import net.runelite.api.events.WidgetHiddenChanged; import net.runelite.api.events.WidgetPositioned; import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.FieldHook; @@ -74,6 +75,48 @@ public abstract class RSWidgetMixin implements RSWidget rl$y = -1; } + @Inject + @Override + public void broadcastHidden(boolean hidden) + { + WidgetHiddenChanged event = new WidgetHiddenChanged(); + event.setWidget(this); + event.setHidden(hidden); + + client.getCallbacks().post(event); + + RSWidget[] children = getChildren(); + + if (children != null) + { + // recursive through children + for (RSWidget child : children) + { + // if the widget is hidden it will not magically unhide from its parent changing + if (child == null || child.isSelfHidden()) + { + continue; + } + + child.broadcastHidden(hidden); + } + } + + // make sure we iterate nested children as well + // cannot be null + Widget[] nestedChildren = getNestedChildren(); + + for (Widget nestedChild : nestedChildren) + { + if (nestedChild == null || nestedChild.isSelfHidden()) + { + continue; + } + + ((RSWidget) nestedChild).broadcastHidden(hidden); + } + } + @Inject @Override public void setRenderParentId(int parentId) diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java b/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java index cbf1a3810a..6d7bc1b0ec 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSActor.java @@ -184,4 +184,7 @@ public interface RSActor extends RSRenderable, Actor int getRunAnimation(); void setDead(boolean dead); + + @Import("pathLength") + int getPathLength(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClientPreferences.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClientPreferences.java index 9eaecdee24..b177444898 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClientPreferences.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClientPreferences.java @@ -34,4 +34,7 @@ public interface RSClientPreferences extends Preferences @Import("musicVolume") void setMusicVolume(int i); + + @Import("hideUsername") + boolean getHideUsername(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSEnumerated.java b/runescape-api/src/main/java/net/runelite/rs/api/RSEnumerated.java index 1b97cfebc9..3189893968 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSEnumerated.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSEnumerated.java @@ -1,3 +1,9 @@ package net.runelite.rs.api; -public interface RSEnumerated {} +import net.runelite.mapping.Import; + +public interface RSEnumerated +{ + @Import("rsOrdinal") + int getOrdinal(); +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSFriendSystem.java b/runescape-api/src/main/java/net/runelite/rs/api/RSFriendSystem.java index 283ce53c09..87bd7ed14e 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSFriendSystem.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSFriendSystem.java @@ -13,6 +13,9 @@ public interface RSFriendSystem @Import("isFriended") boolean isFriended(RSUsername var1, boolean var2); + @Import("isIgnored") + boolean isIgnored(RSUsername var1); + @Import("addFriend") void addFriend(String username); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java b/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java index 7dc5bee2f3..19182c5fb2 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java @@ -28,6 +28,10 @@ public interface RSTile extends Tile @Override GroundObject getGroundObject(); + @Import("floorDecoration") + @Override + void setGroundObject(GroundObject object); + @Import("boundaryObject") @Override WallObject getWallObject(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java index ace10f8461..9f22cac637 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java @@ -588,4 +588,9 @@ public interface RSWidget extends Widget @Import("onVarTransmit") @Override void setOnVarTransmitListener(Object[] o); + + @Import("fillMode") + RSWidgetFillMode getFillMode(); + + void broadcastHidden(boolean hidden); } \ No newline at end of file diff --git a/runelite-client/src/main/java/com/openosrs/client/events/ExternalPluginsLoaded.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWidgetFillMode.java similarity index 85% rename from runelite-client/src/main/java/com/openosrs/client/events/ExternalPluginsLoaded.java rename to runescape-api/src/main/java/net/runelite/rs/api/RSWidgetFillMode.java index 75f3910d81..14495522a4 100644 --- a/runelite-client/src/main/java/com/openosrs/client/events/ExternalPluginsLoaded.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWidgetFillMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Owain van Brakel + * Copyright (c) 2021, ThatGamerBlue * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,11 +22,8 @@ * (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 com.openosrs.client.events; +package net.runelite.rs.api; -import lombok.Data; -import net.runelite.api.events.Event; - -@Data -public class ExternalPluginsLoaded implements Event -{} +public interface RSWidgetFillMode extends RSEnumerated +{ +} diff --git a/runescape-client/src/main/java/GameEngine.java b/runescape-client/src/main/java/GameEngine.java index 23d72bba20..5cab90a2d5 100644 --- a/runescape-client/src/main/java/GameEngine.java +++ b/runescape-client/src/main/java/GameEngine.java @@ -25,7 +25,7 @@ import net.runelite.mapping.ObfuscatedName; import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("bd") -@Implements("GameShell") +@Implements("GameEngine") public abstract class GameEngine extends Applet implements Runnable, FocusListener, WindowListener { @ObfuscatedName("h") @ObfuscatedSignature( @@ -37,14 +37,14 @@ public abstract class GameEngine extends Applet implements Runnable, FocusListen @ObfuscatedSignature( descriptor = "Lbd;" ) - @Export("gameShell") + @Export("gameEngine") static GameEngine gameEngine; @ObfuscatedName("t") @ObfuscatedGetter( intValue = 548418733 ) - @Export("GameShell_redundantStartThreadCount") - static int GameShell_redundantStartThreadCount; + @Export("GameEngine_redundantStartThreadCount") + static int GameEngine_redundantStartThreadCount; @ObfuscatedName("j") @ObfuscatedGetter( longValue = -299301004563523829L @@ -193,7 +193,7 @@ public abstract class GameEngine extends Applet implements Runnable, FocusListen static { gameEngine = null; // L: 41 - GameShell_redundantStartThreadCount = 0; // L: 43 + GameEngine_redundantStartThreadCount = 0; // L: 43 stopTimeMs = 0L; // L: 44 isKilled = false; // L: 45 cycleDurationMillis = 20; // L: 48 @@ -452,8 +452,8 @@ public abstract class GameEngine extends Applet implements Runnable, FocusListen protected final void startThread(int var1, int var2, int var3) { try { if (gameEngine != null) { // L: 220 - ++GameShell_redundantStartThreadCount; // L: 221 - if (GameShell_redundantStartThreadCount >= 3) { // L: 222 + ++GameEngine_redundantStartThreadCount; // L: 221 + if (GameEngine_redundantStartThreadCount >= 3) { // L: 222 this.error("alreadyloaded"); // L: 223 return; // L: 224 } diff --git a/runescape-client/src/main/java/NetSocket.java b/runescape-client/src/main/java/NetSocket.java index dfc77db500..c872bafa17 100644 --- a/runescape-client/src/main/java/NetSocket.java +++ b/runescape-client/src/main/java/NetSocket.java @@ -333,8 +333,8 @@ public final class NetSocket extends AbstractSocket implements Runnable { garbageValue = "8" ) @Export("FillMode_values") - public static class333[] FillMode_values() { - return new class333[]{class333.field3897, class333.field3898, class333.SOLID}; // L: 15 + public static WidgetFillMode[] FillMode_values() { + return new WidgetFillMode[]{WidgetFillMode.field3897, WidgetFillMode.field3898, WidgetFillMode.SOLID}; // L: 15 } @ObfuscatedName("gh") diff --git a/runescape-client/src/main/java/Widget.java b/runescape-client/src/main/java/Widget.java index 0f8b51840e..c25c1e7d78 100644 --- a/runescape-client/src/main/java/Widget.java +++ b/runescape-client/src/main/java/Widget.java @@ -213,7 +213,7 @@ public class Widget extends Node { descriptor = "Llj;" ) @Export("fillMode") - public class333 fillMode; + public WidgetFillMode fillMode; @ObfuscatedName("ar") @ObfuscatedGetter( intValue = -318915915 @@ -711,7 +711,7 @@ public class Widget extends Node { this.mouseOverColor = 0; this.mouseOverColor2 = 0; this.fill = false; - this.fillMode = class333.SOLID; + this.fillMode = WidgetFillMode.SOLID; this.transparencyTop = 0; this.transparencyBot = 0; this.lineWid = 1; diff --git a/runescape-client/src/main/java/class333.java b/runescape-client/src/main/java/WidgetFillMode.java similarity index 85% rename from runescape-client/src/main/java/class333.java rename to runescape-client/src/main/java/WidgetFillMode.java index 0ab3733dad..d7651a784f 100644 --- a/runescape-client/src/main/java/class333.java +++ b/runescape-client/src/main/java/WidgetFillMode.java @@ -1,10 +1,12 @@ import net.runelite.mapping.Export; +import net.runelite.mapping.Implements; import net.runelite.mapping.ObfuscatedGetter; import net.runelite.mapping.ObfuscatedName; import net.runelite.mapping.ObfuscatedSignature; @ObfuscatedName("lj") -public enum class333 implements Enumerated { +@Implements("WidgetFillMode") +public enum WidgetFillMode implements Enumerated { @ObfuscatedName("h") @ObfuscatedSignature( descriptor = "Llj;" @@ -33,7 +35,7 @@ public enum class333 implements Enumerated { ) final int field3900; - class333(int var3, int var4) { + WidgetFillMode(int var3, int var4) { this.field3902 = var3; // L: 19 this.field3900 = var4; // L: 20 } // L: 21 diff --git a/runescape-client/src/main/java/WorldMapData_1.java b/runescape-client/src/main/java/WorldMapData_1.java index 40ccbef1e6..346b7c4c0c 100644 --- a/runescape-client/src/main/java/WorldMapData_1.java +++ b/runescape-client/src/main/java/WorldMapData_1.java @@ -340,7 +340,7 @@ public class WorldMapData_1 extends AbstractWorldMapData { return 1; // L: 708 } else if (var0 == ScriptOpcodes.CC_SETFILLMODE) { // L: 710 var8 = Interpreter.Interpreter_intStack[--VarcInt.Interpreter_intStackSize]; // L: 711 - class333 var6 = (class333)UrlRequester.findEnumerated(NetSocket.FillMode_values(), var8); // L: 712 + WidgetFillMode var6 = (WidgetFillMode)UrlRequester.findEnumerated(NetSocket.FillMode_values(), var8); // L: 712 if (var6 != null) { // L: 713 var3.fillMode = var6; // L: 714 CollisionMap.invalidateWidget(var3); // L: 715