chore: init

This commit is contained in:
Hikyu
2026-09-18 17:18:57 +02:00
commit 15463c2e3c
200 changed files with 51032 additions and 0 deletions
@@ -0,0 +1,445 @@
package com.example.essentialsplus.helpers.combat;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import net.runelite.api.gameval.AnimationID;
import org.apache.commons.lang3.StringUtils;
public enum AnimationData
{
// MELEE
MELEE_VIGGORAS_CHAINMACE(AnimationID.WILD_CAVE_CHAINMACE_CRUSH, AttackStyle.MELEE),
MELEE_DAGGER_SLASH(AnimationID.HUMAN_DDAGGER_LUNGE, AttackStyle.MELEE),
MELEE_SPEAR_STAB(AnimationID.HUMAN_DSPEAR_STAB, AttackStyle.MELEE),
MELEE_SWORD_STAB(AnimationID.HUMAN_SWORD_STAB, AttackStyle.MELEE),
MELEE_SCIM_SLASH(AnimationID.HUMAN_SWORD_SLASH, AttackStyle.MELEE),
MELEE_LANCE_STAB(AnimationID.HUMAN_DHUNTER_LANCE_ATTACK, AttackStyle.MELEE),
MELEE_LANCE_CRUSH(AnimationID.HUMAN_DHUNTER_LANCE_CRUSH, AttackStyle.MELEE),
MELEE_LANCE_SLASH(AnimationID.HUMAN_DHUNTER_LANCE_SLASH, AttackStyle.MELEE),
MELEE_FANG_STAB(AnimationID.HUMAN_OSMUMTENS_FANG, AttackStyle.MELEE),
MELEE_FANG_SPEC(AnimationID.OLAF2_BRINE_SABRE_SPECIAL, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_GENERIC_SLASH(AnimationID.HUMAN_AXE_CHOP, AttackStyle.MELEE),
MELEE_BATTLEAXE_SLASH(AnimationID.HUMAN_AXE_HACK, AttackStyle.MELEE),
MELEE_MACE_STAB(AnimationID.HUMAN_BLUNT_SPIKE, AttackStyle.MELEE),
MELEE_BATTLEAXE_CRUSH(AnimationID.HUMAN_BLUNT_POUND, AttackStyle.MELEE),
MELEE_2H_CRUSH(AnimationID.HUMAN_DHSWORD_CHOP, AttackStyle.MELEE),
MELEE_2H_SLASH(AnimationID.HUMAN_DHSWORD_SLASH, AttackStyle.MELEE),
MELEE_STAFF_CRUSH_2(AnimationID.HUMAN_STAFF_PUMMEL, AttackStyle.MELEE),
MELEE_STAFF_CRUSH_3(AnimationID.HUMAN_STAFFORB_PUMMEL, AttackStyle.MELEE),
MELEE_PUNCH(AnimationID.HUMAN_UNARMEDPUNCH, AttackStyle.MELEE),
MELEE_KICK(AnimationID.HUMAN_UNARMEDKICK, AttackStyle.MELEE),
MELEE_STAFF_STAB(AnimationID.HUMAN_SPEAR_SPIKE, AttackStyle.MELEE),
MELEE_SPEAR_CRUSH(AnimationID.HUMAN_SPEAR_LUNGE, AttackStyle.MELEE),
MELEE_STAFF_SLASH(AnimationID.HUMAN_SCYTHE_SWEEP, AttackStyle.MELEE),
MELEE_DLONG_SPEC(AnimationID.CLEAVE, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DRAGON_MACE_SPEC(AnimationID.SHATTER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DRAGON_DAGGER_SPEC(AnimationID.PUNCTURE, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DRAGON_WARHAMMER_SPEC(AnimationID.DRAGON_WARHAMMER_SA_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ABYSSAL_WHIP(AnimationID.SLAYER_ABYSSAL_WHIP_ATTACK, AttackStyle.MELEE),
MELEE_GRANITE_MAUL(AnimationID.SLAYER_GRANITE_MAUL_ATTACK, AttackStyle.MELEE),
MELEE_GRANITE_MAUL_SPEC(AnimationID.SLAYER_GRANITE_MAUL_SPECIAL_ATTACK, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DHAROKS_GREATAXE_CRUSH(AnimationID.BARROW_DHAROK_SLASH, AttackStyle.MELEE),
MELEE_DHAROKS_GREATAXE_SLASH(AnimationID.BARROW_DHAROK_CRUSH, AttackStyle.MELEE),
MELEE_AHRIMS_STAFF_CRUSH(AnimationID.BARROWS_QUARTERSTAFF_ATTACK, AttackStyle.MELEE),
MELEE_OBBY_MAUL_CRUSH(AnimationID.DRAGON_PICKAXE_ANIM, AttackStyle.MELEE),
MELEE_ABYSSAL_DAGGER_STAB(AnimationID.ABYSSAL_DAGGER_LUNGE, AttackStyle.MELEE),
MELEE_ABYSSAL_BLUDGEON_CRUSH(AnimationID.ABYSSAL_BLUDGEON_CRUSH, AttackStyle.MELEE),
MELEE_ABYSSAL_BLUDGEON_SPEC(AnimationID.ABYSSAL_BLUDGEON_SPECIAL_ATTACK, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_LEAF_BLADED_BATTLEAXE_CRUSH(AnimationID.BATTLEAXE_CRUSH, AttackStyle.MELEE),
MELEE_INQUISITORS_MACE(AnimationID.HUMAN_INQUISITORS_MACE_CRUSH, AttackStyle.MELEE),
MELEE_BARRELCHEST_ANCHOR_CRUSH(AnimationID.BRAIN_PLAYER_ANCHOR_ATTACK, AttackStyle.MELEE),
MELEE_BARRELCHEST_ANCHOR_CRUSH_SPEC(AnimationID.BRAIN_PLAYER_ANCHOR_SPECIAL_ATTACK, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_LEAF_BLADED_BATTLEAXE_SLASH(AnimationID.GODWARS_GODSWORD_ZAMORAK_PLAYER, AttackStyle.MELEE),
MELEE_GODSWORD_SLASH(AnimationID.DH_SWORD_UPDATE_SLASH, AttackStyle.MELEE),
MELEE_GODSWORD_CRUSH(AnimationID.DH_SWORD_UPDATE_SMASH, AttackStyle.MELEE),
MELEE_GODSWORD_DEFENSIVE(AnimationID.DH_SWORD_UPDATE_BLOCK, AttackStyle.MELEE),
MELEE_RUNE_CLAWS_SPEC(AnimationID.IMPALE, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DRAGON_CLAWS_SPEC(AnimationID.HUMAN_DRAGON_CLAWS_SPEC, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_VLS_SPEC(AnimationID.HUMAN_DRAGON_SWORD_SPEC, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ELDER_MAUL(AnimationID.HUMAN_ELDER_MAUL_ATTACK, AttackStyle.MELEE),
MELEE_ZAMORAK_GODSWORD_SPEC(AnimationID.ZGS_SPECIAL_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ELDER_MAUL_SPEC(AnimationID.HUMAN_ELDER_MAUL_SPEC, AttackStyle.MELEE),
MELEE_ZAMORAK_GODSWORD_OR_SPEC(AnimationID.ZGS_SPECIAL_ORNATE_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SARADOMIN_GODSWORD_SPEC(AnimationID.SGS_SPECIAL_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SARADOMIN_GODSWORD_OR_SPEC(AnimationID.SGS_SPECIAL_ORNATE_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_BANDOS_GODSWORD_SPEC(AnimationID.BGS_SPECIAL_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_BANDOS_GODSWORD_OR_SPEC(AnimationID.BGS_SPECIAL_ORNATE_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ARMADYL_GODSWORD_SPEC(AnimationID.AGS_SPECIAL_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ARMADYL_GODSWORD_OR_SPEC(AnimationID.AGS_SPECIAL_ORNATE_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SCYTHE(AnimationID.SCYTHE_OF_VITUR_ATTACK, AttackStyle.MELEE),
MELEE_GHAZI_RAPIER_STAB(AnimationID.GHRAZI_RAPIER_ATTACK, AttackStyle.MELEE),
MELEE_ANCIENT_GODSWORD_SPEC(AnimationID.NGS_SPECIAL_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_CRYSTAL_HALBERD_SPEC(AnimationID.DRAGON_HALBERD_SPECIAL_ATTACK, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SOULREAPER_AXE(AnimationID.ANCIENT_AXE_CRUSH, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SOULREAPER_AXE_SPEC(AnimationID.ANCIENT_AXE_SPECIAL, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_GUTHANS_LUNGE(AnimationID.BARROWS_WAR_SPEAR_STAB, AttackStyle.MELEE),
MELEE_GUTHANS_SWIPE(AnimationID.BARROWS_WAR_SPEAR_SLASH, AttackStyle.MELEE),
MELEE_GUTHANS_POUNDMA(AnimationID.BARROWS_WAR_SPEAR_CRUSH, AttackStyle.MELEE),
MELEE_TORAG_HAMMERS(AnimationID.BARROW_TORAG_CRUSH, AttackStyle.MELEE),
MELEE_VERACS_FLAIL(AnimationID.BARROW_GUTHAN_CRUSH, AttackStyle.MELEE),
MELEE_BLISTERWOOD_FLAIL_CRUSH(AnimationID.IVANDIS_FLAIL_ATTACK, AttackStyle.MELEE),
MELEE_BONE_DAGGER_SPEC(AnimationID.DTTD_PLAYER_STAB_BONE_DAGGER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DUAL_MACUAHUITL(AnimationID.PMOON_MACUAHUITL_CRUSH, AttackStyle.MELEE),
MELEE_BLUE_MOON_SPEAR_SPEC(AnimationID.HUMAN_ZAMORAKSPEAR_LUNGE, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_BLUE_MOON_SPEAR(AnimationID.HUMAN_ZAMORAKSPEAR_STAB, AttackStyle.MELEE),
MELEE_DHINS(AnimationID.HUMAN_DINHS_BULWARK_BASH, AttackStyle.MELEE),
MELEE_URSINE_CHAINMACE_SPEC(AnimationID.HUMAN_SPECIAL02_URSINE, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ANCIENT_MACE_SPEC(AnimationID.SLICE_PLAYER_MACE_SPECIAL_ATTACK, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_DSCIM_SPEC(AnimationID.SP_ATTACK_DRAGON_SCIMITAR, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_D2H_SPEC(AnimationID.DRAGON_TWO_HANDED_SWORD, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_ARCLIGHT_SPEC(AnimationID.DARK_SPEC_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SARA_SWORD_SPEC(AnimationID.SARADOMIN_SWORD_SPECIAL_PLAYER, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_RED_KERIS_SPEC(AnimationID.TOA_KERIS_PARTISAN_SPECIAL01, AttackStyle.MELEE, MetaData.SPECIAL_ATTACK.d),
MELEE_SALAMANDER(AnimationID.HUMAN_ATTACK_SALAMANDER, AttackStyle.MELEE),
MELEE_INFERNAL_TECPATL(AnimationID.TECPATL_STAB, AttackStyle.MELEE),
MELEE_HALLOWED_FLAIL(AnimationID.HUMAN_WEAPONS_HALLOWED_FLAIL01_ATTACK01, AttackStyle.MELEE),
MELEE_FELLING_AXE(AnimationID.FORESTRY_2H_AXE_ATTACK, AttackStyle.MELEE),
MELEE_HALLOWFELL(AnimationID.HUMAN_HALLOWFELL_SLASH, AttackStyle.MELEE),
// RANGED
RANGED_CHINCHOMPA(AnimationID.HUMAN_CHINCHOMPA_ATTACK_PVN, AttackStyle.RANGED),
RANGED_SHORTBOW(AnimationID.HUMAN_BOW, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d),
RANGED_RUNE_KNIFE_PVP(AnimationID.HUMAN_STAKE2, AttackStyle.RANGED),
RANGED_MAGIC_SHORTBOW_SPEC(AnimationID.SNAPSHOT, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d | MetaData.SPECIAL_ATTACK.d),
RANGED_CROSSBOW_PVP(AnimationID.XBOWS_HUMAN_FIRE_AND_RELOAD, AttackStyle.RANGED),
RANGED_BLOWPIPE(AnimationID.SNAKEBOSS_BLOWPIPE_ATTACK, AttackStyle.RANGED),
RANGED_DARTS(AnimationID.II_HUMAN_DART_THROW_PVN, AttackStyle.RANGED),
RANGED_BALLISTA(AnimationID.BALLISTA_ATTACK, AttackStyle.RANGED),
RANGED_BALLISTA_SPEC(AnimationID.BALLISTA_SPECIAL_ATTACK_PVN, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_RUNE_THROWNAXE_SPEC(AnimationID.CHAINHIT, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_DRAGON_THROWNAXE_SPEC(AnimationID.HUMAN_DRAGON_TAXE_SPEC, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_RUNE_CROSSBOW(AnimationID.XBOWS_HUMAN_FIRE_AND_RELOAD_PVN, AttackStyle.RANGED),
RANGED_RUNE_CROSSBOW_OR(AnimationID.HUMAN_XBOWS_LEAGUE03_ATTACK_PVN, AttackStyle.RANGED),
RANGED_BALLISTA_2(AnimationID.BALLISTA_ATTACK_PVN, AttackStyle.RANGED),
RANGED_RUNE_KNIFE(AnimationID.HUMAN_STAKE2_PVN, AttackStyle.RANGED),
RANGED_DRAGON_KNIFE(AnimationID.HUMAN_DRAGON_KNIFE, AttackStyle.RANGED),
RANGED_DRAGON_KNIFE_SPEC(AnimationID.HUMAN_DRAGON_TKNIVES_SPEC, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_DRAGON_KNIFE_POISONED(AnimationID.HUMAN_DRAGON_KNIFE_P, AttackStyle.RANGED),
RANGED_DRAGON_KNIFE_POISONED_SPEC(AnimationID.HUMAN_DRAGON_TKNIVES_SPEC_POISON, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_ZARYTE_CROSSBOW(AnimationID.ZCB_ATTACK_PVN, AttackStyle.RANGED),
RANGED_ZARYTE_CROSSBOW_PVP(AnimationID.ZCB_ATTACK, AttackStyle.RANGED),
RANGED_BLAZING_BLOWPIPE(AnimationID.SNAKEBOSS_BLOWPIPE_ATTACK_ORNAMENT, AttackStyle.RANGED),
RANGED_VENATOR_BOW(AnimationID.HUMAN_WEAPON_BOW_VENATOR01_SHOOT, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d),
RANGED_KARIL_CROSSBOW(AnimationID.BARROWS_REPEATING_CROSSBOW_FIRE, AttackStyle.RANGED),
RANGED_ATLATL(AnimationID.HUMAN_ATLATL_ATTACK_RANGED_01, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d),
RANGED_ATLATL_SPEC(AnimationID.HUMAN_SPECIAL_ATLATL_01, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d | MetaData.SPECIAL_ATTACK.d),
RANGED_TONALZTICS(AnimationID.HUMAN_GLAIVE_RALOS01_CHARGED_THROW, AttackStyle.RANGED),
RANGED_TONALZTICS_SPEC(AnimationID.HUMAN_GLAIVE_RALOS01_CHARGED_SPECIAL, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_WEBWEAVER_SPEC(AnimationID.HUMAN_SPECIAL01_WEBWEAVER, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d | MetaData.SPECIAL_ATTACK.d),
RANGED_BONE_CROSSBOW_SPEC(AnimationID.DTTD_PLAYER_FIRE_BONE_CROSSBOW_PVN, AttackStyle.RANGED, MetaData.SPECIAL_ATTACK.d),
RANGED_SCORCHING_BOW_SPEC(AnimationID.HUMAN_WEAPON_BOW_SCORCHED_01_SPEC, AttackStyle.RANGED, MetaData.STANDARD_BOW_ATTACK.d | MetaData.SPECIAL_ATTACK.d),
// MAGIC
MAGIC_GOD_SPELL(AnimationID.HUMAN_CASTING, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_IBAN_BLAST(AnimationID.HUMAN_CASTIBANBLAST, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_SLAYER_DART(AnimationID.SLAYER_MAGICDART_CAST, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_BIND(AnimationID.HUMAN_CASTENTANGLE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_BIND_STAFF(AnimationID.HUMAN_CASTENTANGLE_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_CONFUSE(AnimationID.HUMAN_CASTCONFUSE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_CONFUSE_STAFF(AnimationID.HUMAN_CASTCONFUSE_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_CONFUSE_STAFF_WALK(AnimationID.HUMAN_CASTCONFUSE_STAFF_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_CONFUSE_WALK(AnimationID.HUMAN_CASTCONFUSE_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_CRUMBLE_UNDEAD(AnimationID.HUMAN_CASTCRUMBLEUNDEAD, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_CRUMBLE_UNDEAD_HOLDING_STAFF(AnimationID.HUMAN_CASTCRUMBLEUNDEAD_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_ENFEEBLE(AnimationID.HUMAN_CASTENFEEBLE_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_STRIKE_BOLT_BLAST(AnimationID.HUMAN_CASTSTRIKE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF(AnimationID.HUMAN_CASTSTRIKE_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF_WALK(AnimationID.HUMAN_CASTSTRIKE_STAFF_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_STRIKE_BOLT_BLAST_WALK(AnimationID.HUMAN_CASTSTRIKE_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_STUN(AnimationID.HUMAN_CASTSTUN_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_SURGE(AnimationID.HUMAN_CAST_SURGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_SURGE_STAFF(AnimationID.HUMAN_CAST_SURGE_FAST, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_SURGE_STAFF_WALK(AnimationID.HUMAN_CAST_SURGE_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_VULNERABILITY_CURSE(AnimationID.HUMAN_CASTCURSE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_VULNERABILITY_CURSE_STAFF(AnimationID.HUMAN_CASTCURSE_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_VULNERABILITY_CURSE_STAFF_WALK(AnimationID.HUMAN_CASTCURSE_STAFF_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_VULNERABILITY_CURSE_WALK(AnimationID.HUMAN_CASTCURSE_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WAVE(AnimationID.HUMAN_CASTWAVE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WAVE_STAFF(AnimationID.HUMAN_CASTWAVE_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WAVE_STAFF_WALK(AnimationID.HUMAN_CASTWAVE_STAFF_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WAVE_WALK(AnimationID.HUMAN_CASTWAVE_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WEAKEN(AnimationID.HUMAN_CASTWEAKEN, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WEAKEN_STAFF(AnimationID.HUMAN_CASTWEAKEN_STAFF, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WEAKEN_STAFF_WALK(AnimationID.HUMAN_CASTWEAKEN_STAFF_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_STANDARD_WEAKEN_WALK(AnimationID.HUMAN_CASTWEAKEN_WALKMERGE, AttackStyle.MAGIC, Spellbook.STANDARD, MetaData.NO_DATA.d),
MAGIC_ANCIENT_MULTI_TARGET(AnimationID.ZAROS_VERTICAL_CASTING_WALKMERGE, AttackStyle.MAGIC, Spellbook.ANCIENT, MetaData.NO_DATA.d),
MAGIC_ANCIENT_MULTI_TARGET_PVP(AnimationID.ZAROS_VERTICAL_CASTING, AttackStyle.MAGIC, Spellbook.ANCIENT, MetaData.NO_DATA.d),
MAGIC_ANCIENT_SINGLE_TARGET(AnimationID.ZAROS_CASTING_WALKMERGE, AttackStyle.MAGIC, Spellbook.ANCIENT, MetaData.NO_DATA.d),
MAGIC_ANCIENT_SINGLE_TARGET_PVP(AnimationID.ZAROS_CASTING, AttackStyle.MAGIC, Spellbook.ANCIENT, MetaData.NO_DATA.d),
MAGIC_ARCEUUS_DEMONBANE(AnimationID.HUMAN_SPELLCAST_DEMONBANE, AttackStyle.MAGIC, Spellbook.ARCEUUS, MetaData.NO_DATA.d),
MAGIC_ARCEUUS_GRASP(AnimationID.HUMAN_SPELLCAST_GRASP, AttackStyle.MAGIC, Spellbook.ARCEUUS, MetaData.NO_DATA.d),
MAGIC_ACCURSED_SCEPTRE_SPEC(AnimationID.HUMAN_SPECIAL_ACCURSED, AttackStyle.MAGIC, MetaData.SPECIAL_ATTACK.d),
MAGIC_EYE_OF_AYAK(AnimationID.HUMAN_EYE_OF_AYAK_NORMAL, AttackStyle.MAGIC, MetaData.NO_DATA.d),
MAGIC_EYE_OF_AYAK_SPEC(AnimationID.HUMAN_EYE_OF_AYAK_SPECIAL, AttackStyle.MAGIC, MetaData.SPECIAL_ATTACK.d),
MAGIC_TUMEKENS_SHADOW(AnimationID.TOA_SOT_CAST_B, AttackStyle.MAGIC, MetaData.NO_DATA.d),
MAGIC_VOLATILE_NIGHTMARE_STAFF_SPEC(AnimationID.NIGHTMARE_STAFF_SPECIAL, AttackStyle.MAGIC, MetaData.SPECIAL_ATTACK.d),
MAGIC_WARPED_SCEPTRE(AnimationID.POG_WARPED_SCEPTRE_ATTACK, AttackStyle.MAGIC, MetaData.NO_DATA.d),
// Misc Non-attacks
MAGIC_IMBUE(AnimationID.HUMAN_CASTBONESTOBANANAS, AttackStyle.NON_ATTACK),
SPELLBOOK_SWAP(AnimationID.DREAM_PLAYER_SPELLBOOK_SWAP, AttackStyle.NON_ATTACK),
LUNAR_GROUP(AnimationID.QUEST_LUNAR_SPELL_CAST_SPELL_ON_GROUP, AttackStyle.NON_ATTACK),
LUNAR_OTHER(AnimationID.QUEST_LUNAR_PUSHING_MAGIC_ANIMATION, AttackStyle.NON_ATTACK),
NPC_CONTACT(AnimationID.LUNAR_HUMAN_MAGIC_SUMMON2, AttackStyle.NON_ATTACK),
VENGEANCE_1(AnimationID.VENGEANCE_SPELL_ANIM_NOSTALLING, AttackStyle.NON_ATTACK),
VENGEANCE_2(AnimationID.VENGEANCE_SPELL_ANIM_STALLING, AttackStyle.NON_ATTACK),
REANIMATION(AnimationID.ARCEUUS_NECROMANCY_PLAYERANIM, AttackStyle.NON_ATTACK),
DEMONIC_OFFERING(AnimationID.HUMAN_CAST_OFFERING, AttackStyle.NON_ATTACK),
SHADOW_VEIL(AnimationID.HUMAN_SPELLCAST_SHADOWVEIL, AttackStyle.NON_ATTACK),
MARK_OF_DARKNESS(AnimationID.HUMAN_CAST_SELFIMBUE, AttackStyle.NON_ATTACK),
PICK_POCKETING(AnimationID.HUMAN_PICKPOCKET, AttackStyle.NON_ATTACK),
SUMMON_THRALL(AnimationID.HUMAN_SPELLCAST_RESURRECT, AttackStyle.NON_ATTACK),
LUNAR_TELEPORT(AnimationID.HUMAN_TELEPORT_OTHER_IMPACT, AttackStyle.NON_ATTACK),
MONSTER_EXAMINE(AnimationID.DREAM_PLAYER_MONSTEREXAM_STATSPY, AttackStyle.NON_ATTACK),
HUMIDIFY(AnimationID.DREAM_PLAYER_HUMIDIFY_SPELL, AttackStyle.NON_ATTACK),
GEOMANCY(AnimationID.LUNAR_HUMAN_MAGIC_GEOMANCY, AttackStyle.NON_ATTACK),
DREAM(AnimationID.FOSSIL_LOC_CLAM_IDLE_SHUT, AttackStyle.NON_ATTACK),
ROCKSLUG_BAG_OF_SALT(AnimationID.SLAYER_SALT_SPRINKLE, AttackStyle.NON_ATTACK),
DESSET_LIZARD_ICE_COOLER(AnimationID.HUMAN_CHINCHOMPA_ATTACK, AttackStyle.NON_ATTACK),
FLECTHING_KNIFE(AnimationID.HUMAN_FLETCHING, AttackStyle.NON_ATTACK),
FLECTHING_KEBBIT(AnimationID.HUMAN_CRAFTING_SPIKEDVAMBRACES, AttackStyle.NON_ATTACK),
FLECTHING_CHISEL(AnimationID.HUMAN_FLETCHING_HUNTINGBOLTS, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_1(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_BRONZE, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_2(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_IRON, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_3(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_STEEL, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_4(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_MITHRIL, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_5(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_ADAMANT, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_6(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_RUNE, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_7(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_DRAGON, AttackStyle.NON_ATTACK),
FLECTHING_DART_TIP_8(AnimationID.HUMAN_FLETCHING_ADD_DART_FEATHERS_AMETHYST, AttackStyle.NON_ATTACK),
HERB_TAR(AnimationID.HUMAN_SALAMANDER_TAR_GRIND, AttackStyle.NON_ATTACK),
SETUP_HUNTER_TRAP(AnimationID.HUMAN_LAYTRAP, AttackStyle.NON_ATTACK),
RESET_SNARE_TRAP(AnimationID.HUMAN_HUNTING_DISMANTLE_NET, AttackStyle.NON_ATTACK),
RESET_BOX_TRAP(AnimationID.HUNTING_SETTING_TRAP_SMALL, AttackStyle.NON_ATTACK),
DESERT_AMMY(AnimationID.TELEPORT_NARDAH_HUMAN, AttackStyle.NON_ATTACK),
EAT_FOOD_OR_POTION(AnimationID.HUMAN_EAT, AttackStyle.NON_ATTACK),
OVERLOAD_HIT(AnimationID.HUMAN_KILLERWATT_ELECTRICSHOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_1HANDED_UNARMED(AnimationID.HUMAN_AXE_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_2H_SWORD(AnimationID.HUMAN_DHSWORD_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_ANCHOR(AnimationID.BRAIN_PLAYER_ANCHOR_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_BLISTERWOOD_FLAIL(AnimationID.IVANDIS_FLAIL_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_BLOWPIPE(AnimationID.HUMAN_SPEAR_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_BULWARK(AnimationID.HUMAN_DINHS_BULWARK_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_CHAINMACE(AnimationID.WILD_CAVE_CHAINMACE_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_CHIN_CHOMPA(AnimationID.HUMAN_CHINCHOMPA_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_DAGGER(AnimationID.HUMAN_DDAGGER_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_DEFENDER(AnimationID.WARGUILD_PARRY_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_FANG(AnimationID.HUMAN_SWORD_DEF, AttackStyle.NON_ATTACK),
TAKING_HIT_GODSWORD(AnimationID.DH_SWORD_UPDATE_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_KERIS(AnimationID.HUMAN_DSPEAR_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_LARGE_STAFF(AnimationID.HUMAN_STAFFORB_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_MACE(AnimationID.HUMAN_BLUNT_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_OBBY_MAUL(AnimationID.SLAYER_GRANITE_MAUL_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_SCYTHE(AnimationID.HUMAN_SCYTHE_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_SHIELD(AnimationID.HUMAN_SHIELD_DEFENCE, AttackStyle.NON_ATTACK),
TAKING_HIT_SPEAR(AnimationID.HUMAN_ZAMORAKSPEAR_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_STAFF(AnimationID.HUMAN_STAFF_BLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_UNARMED(AnimationID.HUMAN_UNARMEDBLOCK, AttackStyle.NON_ATTACK),
TAKING_HIT_VERACS_FLAIL(AnimationID.BARROW_GUTHAN_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_WHIP(AnimationID.SLAYER_ABYSSAL_WHIP_DEFEND, AttackStyle.NON_ATTACK),
TAKING_HIT_KISTEN(AnimationID.HUMAN_WEAPONS_CRIMSON_KISTEN_DEF, AttackStyle.NON_ATTACK),
TAKING_HIT_HALLOWFELL(AnimationID.HUMAN_HALLOWFELL_DEFEND, AttackStyle.NON_ATTACK),
LOW_ALCH(AnimationID.HUMAN_CASTLOWLVLALCHEMY, AttackStyle.NON_ATTACK),
HIGH_ALCH(AnimationID.HUMAN_CASTHIGHLVLALCHEMY, AttackStyle.NON_ATTACK);
private static final Map<Integer, AnimationData> DATA;
private static final Map<Spellbook, Set<AnimationData>> SPELL_BOOK_ANIMATIONS;
private static final Map<Integer, AnimationData> NOT_ATTACKS;
private static final Set<Integer> STANDARD_BOW_ATTACKS;
public final int animationId;
public final long metaData;
@Getter
public final AttackStyle attackStyle;
@Getter
private final Spellbook spellbook;
AnimationData(int animationId, AttackStyle attackStyle)
{
if (attackStyle == null)
{
throw new InvalidParameterException("Attack Style must be valid for AnimationData");
}
this.animationId = animationId;
this.attackStyle = attackStyle;
this.metaData = MetaData.NO_DATA.d;
this.spellbook = null;
}
AnimationData(int animationId, AttackStyle attackStyle, long data)
{
if (attackStyle == null)
{
throw new InvalidParameterException("Attack Style must be valid for AnimationData");
}
this.animationId = animationId;
this.attackStyle = attackStyle;
this.metaData = data;
this.spellbook = null;
}
AnimationData(int animationId, AttackStyle attackStyle, Spellbook book, long data)
{
if (attackStyle == null)
{
throw new InvalidParameterException("Attack Style must be valid for AnimationData");
}
this.animationId = animationId;
this.attackStyle = attackStyle;
this.metaData = data;
this.spellbook = book;
}
static
{
ImmutableMap.Builder<Integer, AnimationData> builder = new ImmutableMap.Builder<>();
ImmutableMap.Builder<Integer, AnimationData> notAttacksBuilder = new ImmutableMap.Builder<>();
Map<Spellbook, Set<AnimationData>> spellBookBuilder = new HashMap<>();
ImmutableSet.Builder<Integer> standardBowBuilder = new ImmutableSet.Builder<>();
for (Spellbook s : Spellbook.values())
{
spellBookBuilder.put(s, new HashSet<>());
}
for (AnimationData data : values())
{
builder.put(data.animationId, data);
if (data.spellbook != null)
{
if (data.attackStyle != AttackStyle.MAGIC)
{
throw new InvalidParameterException("Spell book should only be magic animations");
}
spellBookBuilder.get(data.spellbook).add(data);
}
if (data.attackStyle == AttackStyle.NON_ATTACK)
{
notAttacksBuilder.put(data.animationId, data);
}
if (MetaData.hasFlagSet(data.metaData, MetaData.STANDARD_BOW_ATTACK))
{
standardBowBuilder.add(data.animationId);
}
}
DATA = builder.build();
NOT_ATTACKS = notAttacksBuilder.build();
SPELL_BOOK_ANIMATIONS = spellBookBuilder;
STANDARD_BOW_ATTACKS = standardBowBuilder.build();
}
public static AnimationData fromId(int animationId)
{
return DATA.get(animationId);
}
public static Set<AnimationData> getAnimationsForSpellbook(Spellbook s)
{
return SPELL_BOOK_ANIMATIONS.get(s);
}
public static boolean isManualCasting(AnimationData animationData)
{
if (animationData != null && animationData.spellbook != null)
{
return animationData.attackStyle == AttackStyle.MAGIC &&
SPELL_BOOK_ANIMATIONS.get(animationData.spellbook).contains(animationData);
}
return false;
}
public static boolean isBlockListAnimation(int animationId)
{
return NOT_ATTACKS.containsKey(animationId);
}
@Override
public String toString()
{
String[] words = super.toString().toLowerCase().split("_");
Arrays.stream(words)
.map(StringUtils::capitalize).collect(Collectors.toList()).toArray(words);
return String.join(" ", words);
}
public boolean matchesSpellbook(Spellbook s)
{
if (this.spellbook != null)
{
return this.spellbook == s;
}
return false;
}
public boolean isStandardBowAttack()
{
return STANDARD_BOW_ATTACKS.contains(this.animationId);
}
public boolean isBlockListAnimation()
{
return NOT_ATTACKS.containsKey(this.animationId);
}
public enum AttackStyle
{
MELEE,
RANGED,
MAGIC,
NON_ATTACK;
@Override
public String toString()
{
return StringUtils.capitalize(super.toString().toLowerCase());
}
}
public enum MetaData
{
NO_DATA(),
SPECIAL_ATTACK(1),
STANDARD_BOW_ATTACK(2);
MetaData(int bitshift)
{
this.d = 1 << bitshift;
}
MetaData()
{
this.d = 0;
}
public static boolean hasFlagSet(long input, MetaData data)
{
return (input & data.d) == data.d;
}
private final long d;
@Override
public String toString()
{
return StringUtils.capitalize(super.toString().toLowerCase());
}
}
}
@@ -0,0 +1,8 @@
package com.example.essentialsplus.helpers.combat;
public enum AttackProcedure
{
POWERED_STAVE,
MANUAL_AUTO_CAST,
MELEE_OR_RANGE
}
@@ -0,0 +1,129 @@
package com.example.essentialsplus.helpers.combat;
import com.example.essentialsplus.helpers.combat.variablespeed.VariableSpeed;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayDeque;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.events.FakeXpDrop;
import net.runelite.api.events.SoundEffectPlayed;
import net.runelite.api.events.StatChanged;
import net.runelite.api.gameval.ItemID;
import net.runelite.client.game.ItemManager;
public class AttackSpeed
{
private final ArrayDeque<Integer> specialPercentageEvents = new ArrayDeque<>();
private final Damage damage = new Damage();
@Getter
private int dmgDealt = -1;
private final TickCount tickCount;
private final VariableSpeed variableSpeed;
@Getter
private int soundEffectTick = -1;
@Getter
private int soundEffectId = -1;
@Getter
private boolean isUsingMagic = false;
public AttackSpeed(final TickCount tc, final VariableSpeed vs)
{
this.tickCount = tc;
this.variableSpeed = vs;
}
public int compute(final Client client, final AnimationData curAnimation, final Spellbook spellbook, final ItemManager itemManager)
{
final int weaponId = CombatUtils.getWeaponId(client);
final PoweredStaves stave = PoweredStaves.getPoweredStaves(weaponId, curAnimation);
final int specDelta = CombatUtils.getLastDelta(specialPercentageEvents);
dmgDealt = damage.compute(tickCount);
if (stave != null && stave.getAnimations() != null && stave.getAnimations().contains(curAnimation))
{
isUsingMagic = true;
return variableSpeed.compute(client, curAnimation, AttackProcedure.POWERED_STAVE, spellbook, dmgDealt, specDelta, 4);
}
if (matchesSpellbook(client, curAnimation, spellbook) && ManualCasting.is(client, curAnimation, soundEffectTick, soundEffectId))
{
isUsingMagic = true;
return variableSpeed.compute(client, curAnimation, AttackProcedure.MANUAL_AUTO_CAST, spellbook, dmgDealt, specDelta, getMagicBaseSpeed(weaponId));
}
isUsingMagic = false;
final int aspeed = CombatUtils.getWeaponSpeed(client, itemManager, weaponId);
return variableSpeed.compute(client, curAnimation, AttackProcedure.MELEE_OR_RANGE, spellbook, dmgDealt, specDelta, aspeed);
}
public void onTick()
{
while (specialPercentageEvents.size() > 5)
{
specialPercentageEvents.removeFirst();
}
damage.onTick();
}
public void varbitSpecialAttackChanged(final int value)
{
specialPercentageEvents.addLast(value);
}
public boolean onXpDrop(final FakeXpDrop event)
{
return damage.onXpDrop(event, tickCount);
}
public boolean onXpDrop(final StatChanged event)
{
return damage.onXpDrop(event, tickCount);
}
public void onSoundEffectPlayed(final Client client, final SoundEffectPlayed event)
{
soundEffectTick = client.getTickCount();
soundEffectId = event.getSoundId();
}
private static final Map<Integer, Integer> NON_STANDARD_MAGIC_WEAPON_SPEEDS = new ImmutableMap.Builder<Integer, Integer>()
.put(ItemID.TWINFLAME_STAFF, 6)
.build();
private static int getMagicBaseSpeed(final int weaponId)
{
if (weaponId == ItemID.NIGHTMARE_STAFF_HARMONISED)
{
return 4;
}
return NON_STANDARD_MAGIC_WEAPON_SPEEDS.getOrDefault(weaponId, 5);
}
private boolean matchesSpellbook(final Client client, final AnimationData curAnimation, final Spellbook currentSpellBook)
{
if (curAnimation != null && curAnimation.getAttackStyle() == AnimationData.AttackStyle.MAGIC && curAnimation.getSpellbook() == null)
{
return false;
}
if (curAnimation != null && curAnimation.matchesSpellbook(currentSpellBook))
{
return true;
}
if (client.getTickCount() == soundEffectTick)
{
return CastingSoundData.getSpellBookFromId(soundEffectId) == currentSpellBook;
}
return false;
}
public void reset()
{
dmgDealt = -1;
soundEffectId = -1;
soundEffectTick = -1;
isUsingMagic = false;
specialPercentageEvents.clear();
damage.reset();
}
}
@@ -0,0 +1,34 @@
package com.example.essentialsplus.helpers.combat;
import lombok.Getter;
import net.runelite.api.Skill;
public enum AttackStyle
{
ACCURATE("Accurate", Skill.ATTACK),
AGGRESSIVE("Aggressive", Skill.STRENGTH),
DEFENSIVE("Defensive", Skill.DEFENCE),
CONTROLLED("Controlled", Skill.ATTACK, Skill.STRENGTH, Skill.DEFENCE),
RANGING("Ranging", Skill.RANGED),
LONGRANGE("Longrange", Skill.RANGED, Skill.DEFENCE),
CASTING("Casting", Skill.MAGIC),
DEFENSIVE_CASTING("Defensive Casting", Skill.MAGIC, Skill.DEFENCE),
OTHER("Other");
@Getter
private final String name;
@Getter
private final Skill[] skills;
AttackStyle(String name, Skill... skills)
{
this.name = name;
this.skills = skills;
}
@Override
public String toString()
{
return this.name;
}
}
@@ -0,0 +1,16 @@
package com.example.essentialsplus.helpers.combat;
public enum AttackType
{
CRUSH,
SLASH,
STAB,
RANGED,
MAGIC,
NONE;
public boolean IsMelee()
{
return this.equals(CRUSH) || this.equals(SLASH) || this.equals(STAB);
}
}
@@ -0,0 +1,118 @@
package com.example.essentialsplus.helpers.combat;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Set;
import lombok.Getter;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.Player;
import net.runelite.api.gameval.NpcID;
import net.runelite.api.gameval.SpotanimID;
import net.runelite.client.game.NPCManager;
public class Attacking
{
private static final Set<AnimationData> UNRELIABLE_ANIMATIONS = new ImmutableSet.Builder<AnimationData>()
.add(AnimationData.RANGED_BLOWPIPE).add(AnimationData.RANGED_BLAZING_BLOWPIPE)
.add(AnimationData.MAGIC_EYE_OF_AYAK).add(AnimationData.MAGIC_EYE_OF_AYAK_SPEC).build();
private static final Set<Integer> SPECIAL_NPCS = new ImmutableSet.Builder<Integer>()
.add(NpcID.POH_COMBAT_DUMMY_UPGRADED_UNDEADSLAYER_NPC, NpcID.POH_COMBAT_DUMMY_UPGRADED_ETHER_NPC,
NpcID.POH_COMBAT_DUMMY_UPGRADED_KQ_NPC, NpcID.POH_COMBAT_DUMMY_UPGRADED_KURASK_NPC,
NpcID.POH_COMBAT_DUMMY_UPGRADED_VAMPIRE_NPC, NpcID.POH_COMBAT_DUMMY_UPGRADED_VORKATH_NPC,
NpcID.POH_COMBAT_DUMMY_NPC, NpcID.POH_COMBAT_DUMMY_UNDEADSLAYER_NPC,
NpcID.POH_COMBAT_DUMMY_UPGRADED_NPC, NpcID.NIGHTMARE_TOTEM_1_READY, NpcID.NIGHTMARE_TOTEM_2_READY,
NpcID.NIGHTMARE_TOTEM_3_READY, NpcID.NIGHTMARE_TOTEM_4_READY)
.build();
public static boolean isPlayerAttacking(final Client client, final NPCManager npcManager)
{
final Player localPlayer = client.getLocalPlayer();
if (localPlayer == null)
{
return false;
}
final int animationId = localPlayer.getAnimation();
if (AnimationData.isBlockListAnimation(animationId))
{
return false;
}
final boolean notWalking = animationId != -1 || getSalamanderAttack(client);
final Actor target = localPlayer.getInteracting();
if (target instanceof NPC)
{
final NPC npc = (NPC) target;
final boolean containsAttackOption = npc.getComposition() != null
&& npc.getComposition().getActions() != null
&& Arrays.asList(npc.getComposition().getActions()).contains("Attack");
Integer health = null;
if (npcManager != null)
{
try
{
health = npcManager.getHealth(npc.getId());
}
catch (Exception ignored)
{
}
}
final boolean hasHealthAndLevel = health != null && health > 0 && target.getCombatLevel() > 0;
final boolean attackingNPC = hasHealthAndLevel || SPECIAL_NPCS.contains(npc.getId()) || containsAttackOption;
return attackingNPC && notWalking;
}
if (target instanceof Player)
{
return notWalking;
}
if (target == null)
{
return false;
}
final AnimationData fromId = AnimationData.fromId(animationId);
if (UNRELIABLE_ANIMATIONS.contains(fromId))
{
return false;
}
return fromId != null;
}
public static Attack playerAttack(final Client client)
{
final Player localPlayer = client.getLocalPlayer();
if (localPlayer == null)
{
return new Attack(-1, false, null);
}
final int animationId = localPlayer.getAnimation();
final Actor target = localPlayer.getInteracting();
return new Attack(animationId, getSalamanderAttack(client), target);
}
private static boolean getSalamanderAttack(final Client client)
{
Player localPlayer = client.getLocalPlayer();
return localPlayer != null && localPlayer.hasSpotAnim(SpotanimID.FIREBREATH);
}
public static final class Attack
{
@Getter
private final int animationId;
@Getter
private final boolean salamander;
@Getter
private final Actor target;
public Attack(final int animationId, final boolean salamander, final Actor target)
{
this.animationId = animationId;
this.salamander = salamander;
this.target = target;
}
}
}
@@ -0,0 +1,85 @@
package com.example.essentialsplus.helpers.combat;
import com.google.common.collect.ImmutableMap;
public enum CastingSoundData
{
STANDARD_BIND(101, Spellbook.STANDARD),
STANDARD_CONFUSE(119, Spellbook.STANDARD),
STANDARD_CRUMBLE_UNDEAD(122, Spellbook.STANDARD),
STANDARD_CURSE(127, Spellbook.STANDARD),
STANDARD_EARTH_BLAST(128, Spellbook.STANDARD),
STANDARD_EARTH_BOLT(130, Spellbook.STANDARD),
STANDARD_EARTH_STRIKE(132, Spellbook.STANDARD),
STANDARD_EARTH_SURGE(4025, Spellbook.STANDARD),
STANDARD_EARTH_WAVE(134, Spellbook.STANDARD),
STANDARD_ENFEEBLE(148, Spellbook.STANDARD),
STANDARD_ENTANGLE(151, Spellbook.STANDARD),
STANDARD_FIRE_BLAST(155, Spellbook.STANDARD),
STANDARD_FIRE_BOLT(157, Spellbook.STANDARD),
STANDARD_FIRE_STRIKE(160, Spellbook.STANDARD),
STANDARD_FIRE_SURGE(4032, Spellbook.STANDARD),
STANDARD_IBANS_BLAST_FIRE_WAVE(162, Spellbook.STANDARD),
STANDARD_SLAYER_DART(1718, Spellbook.STANDARD),
STANDARD_SNARE(3003, Spellbook.STANDARD),
STANDARD_STUN(3004, Spellbook.STANDARD),
STANDARD_VULNERABILITY(3009, Spellbook.STANDARD),
STANDARD_WATER_BLAST(207, Spellbook.STANDARD),
STANDARD_WATER_BOLT(209, Spellbook.STANDARD),
STANDARD_WATER_STRIKE(221, Spellbook.STANDARD),
STANDARD_WATER_SURGE(4030, Spellbook.STANDARD),
STANDARD_WATER_WAVE(213, Spellbook.STANDARD),
STANDARD_WEAKEN(3011, Spellbook.STANDARD),
STANDARD_WIND_BLAST(216, Spellbook.STANDARD),
STANDARD_WIND_BOLT(218, Spellbook.STANDARD),
STANDARD_WIND_STRIKE(220, Spellbook.STANDARD),
STANDARD_WIND_SURGE(4028, Spellbook.STANDARD),
STANDARD_WIND_WAVE(222, Spellbook.STANDARD),
ANCIENT_BLOOD_SPELL(106, Spellbook.ANCIENT),
ANCIENT_ICE_SPELL(171, Spellbook.ANCIENT),
ANCIENT_SHADOW_SPELL(178, Spellbook.ANCIENT),
ANCIENT_SMOKE_SPELL(183, Spellbook.ANCIENT),
ARCEUUS_DARK_DEMONBANE(5053, Spellbook.ARCEUUS),
ARCEUUS_GHOSTLY_GRASP(5042, Spellbook.ARCEUUS),
ARCEUUS_INFERIOR_DEMONBANE(5038, Spellbook.ARCEUUS),
ARCEUUS_SKELETAL_GRASP(5026, Spellbook.ARCEUUS),
ARCEUUS_SUPERIOR_DEMONBANE(5027, Spellbook.ARCEUUS),
ARCEUUS_UNDEAD_GRASP(5030, Spellbook.ARCEUUS);
private final int id;
private final Spellbook spellbook;
CastingSoundData(int id, Spellbook b)
{
this.id = id;
this.spellbook = b;
}
private static final ImmutableMap<Integer, CastingSoundData> SOUNDS;
static
{
ImmutableMap.Builder<Integer, CastingSoundData> builder = new ImmutableMap.Builder<>();
for (CastingSoundData data : values())
{
builder.put(data.id, data);
}
SOUNDS = builder.build();
}
public static boolean isCastingSound(int id)
{
return SOUNDS.containsKey(id);
}
public static Spellbook getSpellBookFromId(int id)
{
if (!SOUNDS.containsKey(id))
{
return null;
}
return SOUNDS.get(id).spellbook;
}
}
@@ -0,0 +1,196 @@
package com.example.essentialsplus.helpers.combat;
import com.google.common.collect.ImmutableMap;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.Set;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.EquipmentInventorySlot;
import net.runelite.api.Item;
import net.runelite.api.ItemContainer;
import net.runelite.api.NPC;
import net.runelite.api.WorldView;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.gameval.InventoryID;
import net.runelite.api.gameval.ItemID;
import net.runelite.api.gameval.VarPlayerID;
import net.runelite.api.gameval.VarbitID;
import net.runelite.client.game.ItemEquipmentStats;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.ItemStats;
import org.apache.commons.lang3.ArrayUtils;
public class CombatUtils
{
public static int getItemIdFromContainer(final ItemContainer container, final int slotID)
{
if (container == null)
{
return -1;
}
final Item item = container.getItem(slotID);
return (item != null) ? item.getId() : -1;
}
public static int getWeaponIdRaw(final Client client)
{
return getItemIdFromContainer(client.getItemContainer(InventoryID.WORN),
EquipmentInventorySlot.WEAPON.getSlotIdx());
}
public static WorldPoint getLocalLocation(final Client client)
{
final LocalPoint localPoint = client.getLocalPlayer().getLocalLocation();
return WorldPoint.fromLocalInstance(client, localPoint);
}
public static AttackStyle getAttackStyle(final Client client)
{
final AttackStyle[] attackStyles = getWeaponType(client).getAttackStyles(client);
int currentAttackStyleVarbit = client.getVarpValue(VarPlayerID.COM_MODE);
final int castingMode = client.getVarbitValue(VarbitID.AUTOCAST_DEFMODE);
if (currentAttackStyleVarbit < attackStyles.length)
{
if (currentAttackStyleVarbit == 4)
{
currentAttackStyleVarbit += castingMode;
}
return attackStyles[currentAttackStyleVarbit];
}
return AttackStyle.ACCURATE;
}
public static WeaponType getWeaponType(final Client client)
{
final int currentEquippedWeaponTypeVarbit = client.getVarbitValue(VarbitID.COMBAT_WEAPON_CATEGORY);
return WeaponType.getWeaponType(currentEquippedWeaponTypeVarbit);
}
public static AttackType getAttackType(final Client client)
{
final WeaponType weaponType = getWeaponType(client);
final int currentAttackStyleVarbit = client.getVarpValue(VarPlayerID.COM_MODE);
if (currentAttackStyleVarbit < weaponType.getAttackTypes().length)
{
return weaponType.getAttackTypes()[currentAttackStyleVarbit];
}
return null;
}
public static int getTargetId(final Client client)
{
final NPC target = getTargetNPC(client);
int targetId = 0;
if (target != null)
{
targetId = target.getId();
}
return targetId;
}
public static NPC getTargetNPC(final Client client)
{
final Actor target = client.getLocalPlayer().getInteracting();
if (target != null && (target instanceof NPC))
{
return (NPC) target;
}
return null;
}
public static boolean isInRegionId(final Client client, final int id)
{
final int[] regions = regions(client);
if (regions == null || regions.length == 0)
{
return false;
}
return ArrayUtils.contains(regions, id);
}
public static boolean isInRegionId(final Client client, final Set<Integer> ids)
{
final int[] regions = regions(client);
if (regions == null || regions.length == 0)
{
return false;
}
for (final int id : regions)
{
if (ids.contains(id))
{
return true;
}
}
return false;
}
private static int[] regions(final Client client)
{
final WorldView wv = client.getTopLevelWorldView();
if (wv == null)
{
return null;
}
return wv.getMapRegions();
}
public static int getLastDelta(final ArrayDeque<Integer> events)
{
int i = 0, last = -1, secondLast = -1;
final var it = events.descendingIterator();
while (it.hasNext())
{
if (i == 0)
{
last = it.next();
}
else if (i == 1)
{
secondLast = it.next();
}
else
{
break;
}
i++;
}
return last - secondLast;
}
private static final Map<Integer, Integer> WEAPON_ID_MAPPING_WORKAROUNDS = new ImmutableMap.Builder<Integer, Integer>()
.put(ItemID.VENATOR_BOW_ORNAMENT, ItemID.VENATOR_BOW)
.build();
private static final Map<Integer, Integer> NON_STANDARD_ATTACK_SPEEDS = new ImmutableMap.Builder<Integer, Integer>()
.put(ItemID.HALLOWFELL, 6)
.build();
public static int getWeaponId(final Client client)
{
final int weaponId = getWeaponIdRaw(client);
return WEAPON_ID_MAPPING_WORKAROUNDS.getOrDefault(weaponId, weaponId);
}
public static ItemStats getWeaponStats(final Client client, final ItemManager itemManager, final int weaponId)
{
if (NON_STANDARD_ATTACK_SPEEDS.containsKey(weaponId))
{
return new ItemStats(true, -1, -1,
ItemEquipmentStats.builder().aspeed(NON_STANDARD_ATTACK_SPEEDS.get(weaponId)).build());
}
return itemManager.getItemStats(weaponId);
}
public static int getWeaponSpeed(final Client client, final ItemManager itemManager, final int weaponId)
{
final ItemStats weaponStats = getWeaponStats(client, itemManager, weaponId);
if (weaponStats == null || weaponStats.getEquipment() == null)
{
return 4;
}
return weaponStats.getEquipment().getAspeed();
}
}
@@ -0,0 +1,73 @@
package com.example.essentialsplus.helpers.combat;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import java.util.ArrayDeque;
import net.runelite.api.Skill;
import net.runelite.api.events.FakeXpDrop;
import net.runelite.api.events.StatChanged;
public class Damage
{
private static final double MODIFIER = 1;
private static final double GLOBAL_MODIFIER = 1;
private final ArrayDeque<Integer> hpExpEarned = new ArrayDeque<>();
private final ArrayDeque<Integer> hpExpEarnedTickCount = new ArrayDeque<>();
public boolean onXpDrop(StatChanged event, TickCount tc)
{
if (event.getSkill() != Skill.HITPOINTS)
{
return false;
}
hpExpEarnedTickCount.addLast(tc.get());
hpExpEarned.addLast(event.getXp());
return true;
}
public boolean onXpDrop(FakeXpDrop event, TickCount tc)
{
if (event.getSkill() != Skill.HITPOINTS)
{
return false;
}
hpExpEarnedTickCount.addLast(tc.get());
hpExpEarnedTickCount.addLast(tc.get());
hpExpEarned.addLast(0);
hpExpEarned.addLast(event.getXp());
return true;
}
public int compute(TickCount tc)
{
if (hpExpEarnedTickCount.isEmpty())
{
return -1;
}
final int lastTc = hpExpEarnedTickCount.getLast();
if (!tc.isWithinNTicks(lastTc, 1))
{
return 0;
}
final double xp = (double) CombatUtils.getLastDelta(hpExpEarned);
return (int) Math.round(xp * (3.0d / 4.0d) * MODIFIER * GLOBAL_MODIFIER);
}
public void onTick()
{
while (hpExpEarnedTickCount.size() > 5)
{
hpExpEarnedTickCount.removeFirst();
}
while (hpExpEarned.size() > 5)
{
hpExpEarned.removeFirst();
}
}
public void reset()
{
hpExpEarned.clear();
hpExpEarnedTickCount.clear();
}
}
@@ -0,0 +1,15 @@
package com.example.essentialsplus.helpers.combat;
import net.runelite.api.Client;
public class ManualCasting
{
public static boolean is(final Client client, final AnimationData curId, final int soundEffectTick, final int soundEffectId)
{
final boolean castingFromSound = client.getTickCount() == soundEffectTick
? CastingSoundData.isCastingSound(soundEffectId)
: false;
final boolean castingFromAnimation = AnimationData.isManualCasting(curId);
return castingFromSound || castingFromAnimation;
}
}
@@ -0,0 +1,127 @@
package com.example.essentialsplus.helpers.combat;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import net.runelite.api.gameval.ItemID;
import org.apache.commons.lang3.StringUtils;
public enum PoweredStaves
{
WEAPON_ACCURSED(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK,
AnimationData.MAGIC_ACCURSED_SCEPTRE_SPEC), ItemID.WILD_CAVE_ACCURSED_CHARGED),
WEAPON_GAUNTLET_STAFF(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK),
ItemID.GAUNTLET_MAGIC_T1, ItemID.GAUNTLET_MAGIC_T2, ItemID.GAUNTLET_MAGIC_T3, ItemID.GAUNTLET_MAGIC_T1_HM,
ItemID.GAUNTLET_MAGIC_T2_HM, ItemID.GAUNTLET_MAGIC_T3_HM),
WEAPON_BONE_STAFF(AnimationData.MELEE_GENERIC_SLASH, ItemID.RAT_BONE_STAFF),
WEAPON_DAWNBRINGER(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK),
ItemID.VERZIK_SPECIAL_WEAPON),
WEAPON_HARM(Set.of(AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF,
AnimationData.MAGIC_STANDARD_SURGE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK,
AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF_WALK, AnimationData.MAGIC_STANDARD_SURGE_STAFF_WALK),
ItemID.NIGHTMARE_STAFF_HARMONISED),
WEAPON_SANG(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK),
ItemID.SANGUINESTI_STAFF, ItemID.SANGUINESTI_STAFF_OR),
WEAPON_STARTER_STAFF(ItemID.DEADMAN_STARTER_STAFF, ItemID.DEADMAN_APOCALYPSE_STAFF),
WEAPON_SWAMP(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK),
ItemID.TOXIC_TOTS_CHARGED, ItemID.TOXIC_TOTS_I_CHARGED, ItemID.TOXIC_TOTS_CHARGED_ORN,
ItemID.TOXIC_TOTS_I_CHARGED_ORN),
WEAPON_THAMMARON(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK),
ItemID.WILD_CAVE_SCEPTRE_CHARGED),
WEAPON_TRIDENT(Set.of(AnimationData.MAGIC_STANDARD_WAVE_STAFF, AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK),
ItemID.TOTS, ItemID.TOTS_CHARGED, ItemID.TOTS_I_CHARGED, ItemID.TOTS_CHARGED_ORN, ItemID.TOTS_ORN,
ItemID.TOTS_I_CHARGED_ORN),
WEAPON_WARPED_SCEPTRE(AnimationData.MAGIC_WARPED_SCEPTRE, ItemID.WARPED_SCEPTRE);
@Getter
private final Set<Integer> ids;
@Getter
private final Set<AnimationData> animations;
private static Set<Integer> ListToSet(int... id)
{
ImmutableSet.Builder<Integer> builder = new ImmutableSet.Builder<>();
if (id.length == 0)
{
return builder.build();
}
for (int i : id)
{
builder.add(i);
}
return builder.build();
}
PoweredStaves(int... id)
{
this.ids = ListToSet(id);
this.animations = null;
}
PoweredStaves(AnimationData spell, int... id)
{
this.ids = ListToSet(id);
this.animations = Set.of(spell);
}
PoweredStaves(Set<AnimationData> spell, int... id)
{
this.ids = ListToSet(id);
this.animations = spell;
}
protected static final int UNKNOWN_SPELL = 0xDEADBEEF;
protected static final ImmutableMap<Integer, ImmutableMap<Integer, PoweredStaves>> POWERED_STAVES;
static
{
ImmutableMap.Builder<Integer, ImmutableMap<Integer, PoweredStaves>> builder = new ImmutableMap.Builder<>();
for (PoweredStaves p : values())
{
for (int id : p.ids)
{
ImmutableMap.Builder<Integer, PoweredStaves> spellMap = new ImmutableMap.Builder<>();
if (p.animations == null)
{
spellMap.put(UNKNOWN_SPELL, p);
}
else
{
for (AnimationData spell : p.animations)
{
spellMap.put(spell.animationId, p);
}
}
builder.put(id, spellMap.build());
}
}
POWERED_STAVES = builder.build();
}
public static PoweredStaves getPoweredStaves(int weaponId, AnimationData animation)
{
ImmutableMap<Integer, PoweredStaves> weaponMap = POWERED_STAVES.get(weaponId);
if (weaponMap == null || animation == null)
{
return null;
}
if (weaponMap.containsKey(UNKNOWN_SPELL))
{
return weaponMap.get(UNKNOWN_SPELL);
}
return weaponMap.get(animation.animationId);
}
@Override
public String toString()
{
String[] words = super.toString().toLowerCase().split("_");
Arrays.stream(words).map(StringUtils::capitalize).collect(Collectors.toList()).toArray(words);
return String.join(" ", words);
}
}
@@ -0,0 +1,35 @@
package com.example.essentialsplus.helpers.combat;
import com.google.common.collect.ImmutableMap;
public enum Spellbook
{
STANDARD(0),
ANCIENT(1),
LUNAR(2),
ARCEUUS(3);
private final int id;
Spellbook(int id)
{
this.id = id;
}
private static final ImmutableMap<Integer, Spellbook> BOOKS;
static
{
ImmutableMap.Builder<Integer, Spellbook> builder = new ImmutableMap.Builder<>();
for (Spellbook data : values())
{
builder.put(data.id, data);
}
BOOKS = builder.build();
}
public static Spellbook fromVarbit(int varbit)
{
return BOOKS.get(varbit);
}
}
@@ -0,0 +1,133 @@
package com.example.essentialsplus.helpers.combat;
import static com.example.essentialsplus.helpers.combat.AttackStyle.ACCURATE;
import static com.example.essentialsplus.helpers.combat.AttackStyle.AGGRESSIVE;
import static com.example.essentialsplus.helpers.combat.AttackStyle.CASTING;
import static com.example.essentialsplus.helpers.combat.AttackStyle.DEFENSIVE;
import static com.example.essentialsplus.helpers.combat.AttackStyle.DEFENSIVE_CASTING;
import static com.example.essentialsplus.helpers.combat.AttackStyle.OTHER;
import static com.example.essentialsplus.helpers.combat.AttackType.CRUSH;
import static com.example.essentialsplus.helpers.combat.AttackType.MAGIC;
import static com.example.essentialsplus.helpers.combat.AttackType.NONE;
import static com.example.essentialsplus.helpers.combat.AttackType.RANGED;
import static com.example.essentialsplus.helpers.combat.AttackType.SLASH;
import static com.example.essentialsplus.helpers.combat.AttackType.STAB;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.EnumID;
import net.runelite.api.ParamID;
import net.runelite.api.StructComposition;
public enum WeaponType
{
UNARMED(CRUSH, CRUSH, null, CRUSH),
AXE(SLASH, SLASH, CRUSH, SLASH),
BLUNT(CRUSH, CRUSH, null, CRUSH),
BOW(RANGED, RANGED, null, RANGED),
CLAW(SLASH, SLASH, STAB, SLASH),
HEAVY_RANGED(RANGED, RANGED, null, RANGED),
SALAMANDER(SLASH, RANGED, MAGIC, null),
CHINCHOMPA(RANGED, RANGED, null, RANGED),
GUN(NONE, CRUSH, null, null),
SLASH_SWORD(SLASH, SLASH, STAB, SLASH),
TWO_HANDED_SWORD(SLASH, SLASH, CRUSH, SLASH),
PICKAXE(STAB, STAB, CRUSH, STAB),
POLEARM(STAB, SLASH, null, STAB),
POLESTAFF(CRUSH, CRUSH, null, CRUSH),
SCYTHE(SLASH, SLASH, CRUSH, SLASH),
SPEAR(STAB, SLASH, CRUSH, STAB),
SPIKED(CRUSH, CRUSH, STAB, CRUSH),
STAB_SWORD(STAB, STAB, SLASH, STAB),
STAFF(CRUSH, CRUSH, null, CRUSH, MAGIC, MAGIC),
THROWN(RANGED, RANGED, null, RANGED),
WHIP(SLASH, SLASH, null, SLASH),
BLADED_STAFF(STAB, SLASH, null, CRUSH, MAGIC, MAGIC),
TWO_HANDED_SWORD_GODSWORD(STAB, SLASH, null, CRUSH, MAGIC, MAGIC),
POWERED_STAFF(SLASH, SLASH, CRUSH, SLASH),
BANNER(MAGIC, MAGIC, null, MAGIC),
POLEARM_CRYSTAL_HALBERD(STAB, SLASH, CRUSH, STAB),
BLUDGEON(STAB, SLASH, null, STAB),
BULWARK(CRUSH, CRUSH, null, CRUSH),
POWERED_WAND(CRUSH, null, null, NONE),
PARTISAN(MAGIC, MAGIC, null, MAGIC),
PARTISAN_2(STAB, STAB, CRUSH, STAB),
PLACE_HOLDER_0(null, null, null),
PLACE_HOLDER_1(null, null, null),
PLACE_HOLDER_2(null, null, null),
PLACE_HOLDER_3(null, null, null),
FLAIL(SLASH, SLASH, null, SLASH);
private static final Map<Integer, WeaponType> WEAPON_TYPES;
@Getter
private final AttackType[] attackTypes;
static
{
ImmutableMap.Builder<Integer, WeaponType> builder = new ImmutableMap.Builder<>();
for (WeaponType weaponType : values())
{
builder.put(weaponType.ordinal(), weaponType);
}
WEAPON_TYPES = builder.build();
}
WeaponType(AttackType... attackTypes)
{
this.attackTypes = attackTypes;
}
public AttackStyle[] getAttackStyles(Client client)
{
int weaponType = this.ordinal();
int weaponStyleEnum = client.getEnum(EnumID.WEAPON_STYLES).getIntValue(weaponType);
if (weaponStyleEnum == -1)
{
if (weaponType == 22)
{
return new AttackStyle[]{
ACCURATE, AGGRESSIVE, null, DEFENSIVE, CASTING, DEFENSIVE_CASTING,
};
}
if (weaponType == 30)
{
return new AttackStyle[]{
ACCURATE, AGGRESSIVE, AGGRESSIVE, DEFENSIVE,
};
}
return new AttackStyle[0];
}
int[] weaponStyleStructs = client.getEnum(weaponStyleEnum).getIntVals();
AttackStyle[] styles = new AttackStyle[weaponStyleStructs.length];
int i = 0;
for (int style : weaponStyleStructs)
{
StructComposition attackStyleStruct = client.getStructComposition(style);
String attackStyleName = attackStyleStruct.getStringValue(ParamID.ATTACK_STYLE_NAME);
AttackStyle attackStyle = AttackStyle.valueOf(attackStyleName.toUpperCase());
if (attackStyle == OTHER)
{
++i;
continue;
}
if (i == 5 && attackStyle == DEFENSIVE)
{
attackStyle = DEFENSIVE_CASTING;
}
styles[i++] = attackStyle;
}
return styles;
}
public static WeaponType getWeaponType(int id)
{
return WEAPON_TYPES.get(id);
}
}
@@ -0,0 +1,30 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.AttackType;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.gameval.NpcID;
public class Amoxliatl implements IVariableSpeed
{
private static final int AMOXLIATL_REGION_ID = 5446;
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
final WorldPoint location = CombatUtils.getLocalLocation(client);
final int targetId = CombatUtils.getTargetId(client);
final AttackType attkType = CombatUtils.getAttackType(client);
if (location.getRegionID() == AMOXLIATL_REGION_ID && targetId == NpcID.AMOXLIATL_ICE_BLOCK && attkType != null && attkType.IsMelee())
{
return 1;
}
return curSpeed;
}
}
@@ -0,0 +1,22 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
import net.runelite.api.gameval.SpotanimID;
public class BloodMoonSet implements IVariableSpeed
{
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
if (client.getLocalPlayer() != null && client.getLocalPlayer().hasSpotAnim(SpotanimID.SPECIAL_DUAL_MACUAHUITL_SPOTANIM))
{
return curSpeed - 1;
}
return curSpeed;
}
}
@@ -0,0 +1,127 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.Attacking;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.gameval.AnimationID;
import net.runelite.api.gameval.ItemID;
import net.runelite.api.gameval.NpcID;
import net.runelite.client.game.ItemManager;
@Slf4j
public class DoomOfMokhaiotl implements IVariableSpeed
{
private static final Set<Integer> DOOM_REGION_IDS = new ImmutableSet.Builder<Integer>()
.add(5269)
.add(13668)
.add(14180)
.build();
private static final Set<Integer> COOLDOWN_OVERWRITES_IDS = new ImmutableSet.Builder<Integer>()
.add(NpcID.DOM_DEMONIC_ENERGY)
.add(NpcID.DOM_DEMONIC_ENERGY_GIANT_MAGE)
.add(NpcID.DOM_DEMONIC_ENERGY_GIANT_RANGE)
.add(NpcID.DOM_DEMONIC_ENERGY_MAGE)
.add(NpcID.DOM_DEMONIC_ENERGY_RANGE)
.add(NpcID.DOM_DEMONIC_ENERGY_MELEE)
.add(NpcID.DOM_SHOCKWAVE_PATH_NODE)
.build();
private static final Set<Integer> NO_COOLDOWN_WEAPON = new ImmutableSet.Builder<Integer>()
.add(ItemID.SILVERLIGHT)
.add(ItemID.DARKLIGHT)
.add(ItemID.ARCLIGHT)
.add(ItemID.EMBERLIGHT)
.add(ItemID.BONE_CLAWS)
.add(ItemID.SCORCHING_BOW)
.add(ItemID.HOLY_WATER)
.add(ItemID.EYE_OF_AYAK)
.build();
private final TickCount tickCount;
private int larvaeConsumed = -1;
private int shieldConsumed = -1;
public DoomOfMokhaiotl(final TickCount tc)
{
this.tickCount = tc;
}
public boolean onRender(final Client client, final ItemManager itemManager, final Spellbook spellbook)
{
if (!CombatUtils.isInRegionId(client, DOOM_REGION_IDS))
{
return false;
}
final var atk = Attacking.playerAttack(client);
final AnimationData anim = AnimationData.fromId(atk.getAnimationId());
if (anim == null || atk.getTarget() == null || !(atk.getTarget() instanceof NPC) || anim.isBlockListAnimation())
{
return false;
}
final NPC npc = (NPC) atk.getTarget();
final int npcId = npc.getId();
if (COOLDOWN_OVERWRITES_IDS.contains(npcId))
{
if (tickCount.isWithinNTicks(larvaeConsumed, 1))
{
return false;
}
final int weaponId = CombatUtils.getWeaponId(client);
final boolean isDemonbaneSpell = spellbook == Spellbook.ARCEUUS && AnimationData.isManualCasting(anim) && anim == AnimationData.MAGIC_ARCEUUS_DEMONBANE;
if (NO_COOLDOWN_WEAPON.contains(weaponId) || isDemonbaneSpell)
{
return false;
}
larvaeConsumed = tickCount.get();
return true;
}
else if (npcId == NpcID.DOM_BOSS)
{
if (tickCount.isWithinNTicks(shieldConsumed, 30))
{
return false;
}
final int animId = npc.getAnimation();
if (animId == AnimationID.DOM_BEAM_CHARGE_LOOP || animId == AnimationID.DOM_BEAM_CHARGE)
{
if (CombatUtils.getAttackType(client) != null && CombatUtils.getAttackType(client).IsMelee())
{
shieldConsumed = tickCount.get();
return true;
}
}
return false;
}
return false;
}
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
final int targetId = CombatUtils.getTargetId(client);
final boolean inDoom = CombatUtils.isInRegionId(client, DOOM_REGION_IDS);
if (inDoom && COOLDOWN_OVERWRITES_IDS.contains(targetId))
{
final int weaponId = CombatUtils.getWeaponId(client);
final boolean isDemonbaneSpell = spellbook == Spellbook.ARCEUUS && AnimationData.isManualCasting(curAnimation) && curAnimation == AnimationData.MAGIC_ARCEUUS_DEMONBANE;
if (NO_COOLDOWN_WEAPON.contains(weaponId) || isDemonbaneSpell)
{
return 1;
}
}
return curSpeed;
}
}
@@ -0,0 +1,21 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
public class EyeOfAyak implements IVariableSpeed
{
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
if (curAnimation == AnimationData.MAGIC_EYE_OF_AYAK_SPEC)
{
return 5;
}
return curSpeed;
}
}
@@ -0,0 +1,21 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.Spellbook;
import com.example.essentialsplus.helpers.combat.variablespeed.state.IStateTracker;
import net.runelite.api.Client;
public interface IVariableSpeed extends IStateTracker
{
int apply(
final Client client,
final AnimationData curAnimation,
final AttackProcedure atkType,
final Spellbook spellbook,
final int damageDealt,
final int lastSpecDelta,
final int baseSpeed,
final int curSpeed
);
}
@@ -0,0 +1,67 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.Attacking;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.gameval.AnimationID;
import net.runelite.api.gameval.NpcID;
import net.runelite.client.game.ItemManager;
@Slf4j
public class MaggotKing
{
private static final int MAGGOT_KING_REGION_ID = 11645;
private final TickCount tickCount;
private int consumed = -1;
public MaggotKing(final TickCount tc)
{
this.tickCount = tc;
}
public boolean onRender(final Client client, final ItemManager itemManager)
{
if (!CombatUtils.isInRegionId(client, MAGGOT_KING_REGION_ID) || tickCount.isWithinNTicks(consumed, 1))
{
return false;
}
final var atk = Attacking.playerAttack(client);
final AnimationData anim = AnimationData.fromId(atk.getAnimationId());
if (anim == null || atk.getTarget() == null || !(atk.getTarget() instanceof NPC))
{
return false;
}
final NPC npc = (NPC) atk.getTarget();
if (npc.getId() != NpcID.UR_MAGGOT_LARVAE || !anim.isStandardBowAttack()
|| npc.getAnimation() != AnimationID.UR_MAGGOT_LARVAE_FLY)
{
return false;
}
consumed = tickCount.get();
return true;
}
public int onChatMessage(final Client client, final String message)
{
if (message == null)
{
return 0;
}
if (message.contains("The sticky acid hampers your ability to attack!"))
{
return 1;
}
else if (message.contains("The Maggot King's screech disrupts your concentration!"))
{
return 3;
}
return 0;
}
}
@@ -0,0 +1,51 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import com.example.essentialsplus.helpers.combat.variablespeed.state.Yama;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.gameval.ItemID;
public class PurgingStaffSpec implements IVariableSpeed
{
private final Yama yama;
public NPC lastTarget;
public PurgingStaffSpec(Yama yama)
{
this.yama = yama;
}
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
if (this.yama == null || !this.yama.inYamaRegion)
{
return curSpeed;
}
final NPC target = CombatUtils.getTargetNPC(client);
final NPC flare = Yama.isEitherVoidFlare(target, lastTarget);
lastTarget = target;
if (flare == null)
{
return curSpeed;
}
yama.dealVoidFlareDamage(flare, damageDealt);
if (lastSpecDelta != -250 || CombatUtils.getWeaponId(client) != ItemID.PURGING_STAFF || spellbook != Spellbook.ARCEUUS)
{
return curSpeed;
}
if (yama.getVoidFlareDead(flare))
{
return curSpeed - 2;
}
return curSpeed;
}
}
@@ -0,0 +1,25 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.AttackStyle;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
import net.runelite.api.gameval.VarPlayerID;
public class RapidAttackStyle implements IVariableSpeed
{
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
final boolean isRapid = client.getVarpValue(VarPlayerID.COM_MODE) == 1;
if (atkType == AttackProcedure.MELEE_OR_RANGE && CombatUtils.getAttackStyle(client) == AttackStyle.RANGING && isRapid)
{
return curSpeed - 1;
}
return curSpeed;
}
}
@@ -0,0 +1,25 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
public class RedKerisSpec implements IVariableSpeed
{
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
if (lastSpecDelta != -750 || curAnimation != AnimationData.MELEE_RED_KERIS_SPEC)
{
return curSpeed;
}
if (damageDealt > 0)
{
return curSpeed + 4;
}
return curSpeed;
}
}
@@ -0,0 +1,174 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.Set;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.gameval.ItemID;
import net.runelite.api.gameval.NpcID;
public class RoyalTitans implements IVariableSpeed
{
private static final int ROYAL_TITANS_REGION_ID = 11669;
private static final int HP_FUDGE = 1;
private static final int ELEMENTAL_HP = 40 - HP_FUDGE;
private final Set<NPC> iceElementals = new HashSet<>();
private final Set<NPC> fireElementals = new HashSet<>();
private boolean removeDead = false;
private static final Set<AnimationData> ONE_SHOT_SPELLS = new ImmutableSet.Builder<AnimationData>()
.add(AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST)
.add(AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF)
.add(AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF_WALK)
.add(AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST_WALK)
.add(AnimationData.MAGIC_STANDARD_SURGE)
.add(AnimationData.MAGIC_STANDARD_SURGE_STAFF)
.add(AnimationData.MAGIC_STANDARD_SURGE_STAFF_WALK)
.add(AnimationData.MAGIC_STANDARD_WAVE)
.add(AnimationData.MAGIC_STANDARD_WAVE_STAFF)
.add(AnimationData.MAGIC_STANDARD_WAVE_STAFF_WALK)
.add(AnimationData.MAGIC_STANDARD_WAVE_WALK)
.build();
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
if (earlyExit(client, atkType, damageDealt, spellbook))
{
return curSpeed;
}
final int targetId = CombatUtils.getTargetId(client);
if (spellbook != Spellbook.STANDARD || !isElemental(targetId) || !ONE_SHOT_SPELLS.contains(curAnimation))
{
return curSpeed;
}
final NPC target = CombatUtils.getTargetNPC(client);
if (target == null)
{
return curSpeed;
}
final boolean wieldingTwinflame = CombatUtils.getWeaponId(client) == ItemID.TWINFLAME_STAFF;
final int computedDamage = wieldingTwinflame ? damageDealt + ((damageDealt * 4) / 10) : damageDealt;
if (computedDamage < ELEMENTAL_HP)
{
return curSpeed;
}
final var set = targetId == NpcID.RT_SUMMON_ELEMENTAL_FIRE ? fireElementals : iceElementals;
int count = 1;
final var reference = target.getWorldLocation();
for (final NPC elemental : set)
{
if (elemental == target)
{
continue;
}
final WorldPoint worldLocation = elemental.getWorldLocation();
final int distanceTo2D = reference.distanceTo2D(worldLocation);
if (distanceTo2D <= 1)
{
count++;
}
}
final boolean isTwinflameKillOnSecondProjectile = wieldingTwinflame &&
curAnimation == AnimationData.MAGIC_STANDARD_STRIKE_BOLT_BLAST_STAFF;
final int extraHitOffset = isTwinflameKillOnSecondProjectile ? 1 : 0;
final WorldPoint playerLoc = client.getLocalPlayer().getWorldLocation();
final int distance = playerLoc.distanceTo2D(reference);
final int hitDelay = 2 + (distance / 3) + extraHitOffset;
final int remainingAtImpact = Math.max(0, curSpeed - hitDelay);
final int remainingAfterReduction = Math.max(0, remainingAtImpact - count);
final int finalSpeed = Math.min(curSpeed, hitDelay + remainingAfterReduction);
removeDead = true;
return finalSpeed;
}
private static boolean earlyExit(final Client client, final AttackProcedure atkType, final int damageDealt,
final Spellbook spellbook)
{
return notInRegion(client)
|| atkType != AttackProcedure.MANUAL_AUTO_CAST
|| damageDealt <= 0
|| spellbook != Spellbook.STANDARD;
}
@Override
public void onGameTick(Client client, GameTick tick)
{
if (removeDead)
{
boolean removed = iceElementals.removeIf(NPC::isDead);
removed |= fireElementals.removeIf(NPC::isDead);
if (removed)
{
removeDead = false;
}
}
}
@Override
public void onNpcSpawned(final Client client, final NpcSpawned npcSpawned)
{
if (notInRegion(client))
{
return;
}
final NPC npc = npcSpawned.getNpc();
final int id = npc.getId();
if (id == NpcID.RT_SUMMON_ELEMENTAL_ICE)
{
iceElementals.add(npc);
}
else if (id == NpcID.RT_SUMMON_ELEMENTAL_FIRE)
{
fireElementals.add(npc);
}
}
@Override
public void onNpcDespawned(final Client client, final NpcDespawned npcDespawned)
{
if (notInRegion(client))
{
return;
}
final NPC npc = npcDespawned.getNpc();
final int id = npc.getId();
if (id == NpcID.RT_SUMMON_ELEMENTAL_ICE)
{
iceElementals.remove(npc);
}
else if (id == NpcID.RT_SUMMON_ELEMENTAL_FIRE)
{
fireElementals.remove(npc);
}
}
private static boolean isElemental(int id)
{
return id == NpcID.RT_SUMMON_ELEMENTAL_FIRE || id == NpcID.RT_SUMMON_ELEMENTAL_ICE;
}
private static boolean notInRegion(final Client client)
{
return CombatUtils.getLocalLocation(client).getRegionID() != ROYAL_TITANS_REGION_ID;
}
}
@@ -0,0 +1,44 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.gameval.ItemID;
import net.runelite.api.gameval.NpcID;
public class Scurrius implements IVariableSpeed
{
private static final int SCURRIUS_REGION_ID = 13210;
private static final int SCURRIUS_MIN_X = 3290;
private static final int SCURRIUS_MAX_X = 3307;
private static final int SCURRIUS_MIN_Y = 9859;
private static final int SCURRIUS_MAX_Y = 9876;
private static boolean attackingGiantRatWithBoneWeapon(final int equipped, final int regionId, final int x,
final int y, final int target)
{
final boolean correctWeapon = equipped == ItemID.RAT_BONE_STAFF || equipped == ItemID.RAT_BONE_MACE || equipped == ItemID.RAT_BONE_BOW;
final boolean correctCoords = x >= SCURRIUS_MIN_X && x <= SCURRIUS_MAX_X && y >= SCURRIUS_MIN_Y && y <= SCURRIUS_MAX_Y;
final boolean correctRegion = regionId == SCURRIUS_REGION_ID;
final boolean correctEnemy = target == NpcID.RAT_BOSS_GIANT_RAT;
return correctWeapon && correctCoords && correctRegion && correctEnemy;
}
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
final WorldPoint location = CombatUtils.getLocalLocation(client);
final int weaponId = CombatUtils.getWeaponId(client);
final int targetId = CombatUtils.getTargetId(client);
if (attackingGiantRatWithBoneWeapon(weaponId, location.getRegionID(), location.getX(), location.getY(), targetId))
{
return 1;
}
return curSpeed;
}
}
@@ -0,0 +1,257 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.variablespeed.state.MarkOfDarkness;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import com.example.essentialsplus.helpers.combat.variablespeed.state.Yama;
import com.example.essentialsplus.helpers.combat.variablespeed.state.YamaPhase;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GraphicsObject;
import net.runelite.api.Player;
import net.runelite.api.WorldView;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
@Slf4j
public class ShadowCrash
{
private static final int FIREBALL_ID = 3262;
private static final int TILE = 128;
private static final int HV_GAP_1 = 3;
private static final int HV_GAP_2 = 6;
private static final int HV_DELTA_1 = HV_GAP_1 * TILE;
private static final int HV_DELTA_2 = HV_GAP_2 * TILE;
private static final int D_GAP_1 = 2;
private static final int D_GAP_2 = 4;
private static final int D_DELTA_1 = D_GAP_1 * TILE;
private static final int D_DELTA_2 = D_GAP_2 * TILE;
private final TickCount tickCount;
private final Yama yama;
private final MarkOfDarkness mod;
private int consumed = -1;
private final Map<LocalPoint, GraphicsObject> fireballs;
private ArrayList<FireballLine> lines;
private WorldPoint player;
public ShadowCrash(Yama yama, MarkOfDarkness mod, TickCount tc)
{
this.yama = yama;
this.mod = mod;
this.tickCount = tc;
this.fireballs = new HashMap<>();
this.lines = new ArrayList<>();
}
public int onRender(final Client client, final int attackDelayHoldoffTicks, final boolean isUsingMagic)
{
if (yama == null || !yama.inYamaRegion || yama.phase() != YamaPhase.P3 || !mod.isActive())
{
return 0;
}
if (!tickCount.isWithinNTicks(consumed, 2) && shadowCrashSweetSpot(client))
{
consumed = tickCount.get();
if (attackDelayHoldoffTicks <= 1 && isUsingMagic)
{
return 0;
}
return -1;
}
return 0;
}
private boolean shadowCrashSweetSpot(final Client client)
{
getState(client);
if (lines.isEmpty())
{
return false;
}
for (var line : lines)
{
if (line.isActive())
{
return line.inSweetSpot(client, player);
}
}
return false;
}
private void getState(final Client client)
{
fireballs.clear();
lines.clear();
final WorldView tlwv = client.getTopLevelWorldView();
final Player localPlayer = client.getLocalPlayer();
if (localPlayer == null)
{
return;
}
final WorldView playerWv = localPlayer.getWorldView();
if (tlwv != null)
{
getFireballs(tlwv);
}
if (playerWv != null && playerWv != tlwv)
{
getFireballs(playerWv);
}
if (fireballs.size() < 3)
{
return;
}
player = localPlayer.getWorldLocation();
lines = getLines();
}
private void getFireballs(final WorldView wv)
{
for (GraphicsObject graphicsObject : wv.getGraphicsObjects())
{
if (graphicsObject.getId() == FIREBALL_ID)
{
fireballs.put(graphicsObject.getLocation(), graphicsObject);
}
}
}
private ArrayList<FireballLine> getLines()
{
final var result = new ArrayList<FireballLine>();
while (fireballs.size() >= 3)
{
LocalPoint first = null;
final Set<LocalPoint> fireballsLocations = fireballs.keySet();
for (var location : fireballsLocations)
{
if (first == null)
{
first = location;
}
else
{
if (first.getX() > location.getX())
{
first = location;
}
else if (first.getX() == location.getX())
{
if (first.getY() > location.getY())
{
first = location;
}
}
}
}
if (first == null)
{
return result;
}
var centre = first.plus(D_DELTA_1, D_DELTA_1);
var end = first.plus(D_DELTA_2, D_DELTA_2);
if (fireballsLocations.contains(centre) && fireballsLocations.contains(end))
{
result.add(new FireballLine(LineType.D2, centre, fireballs.get(centre)));
removeLine(first, centre, end);
continue;
}
centre = first.plus(HV_DELTA_1, 0);
end = first.plus(HV_DELTA_2, 0);
if (fireballsLocations.contains(centre) && fireballsLocations.contains(end))
{
result.add(new FireballLine(LineType.H, centre, fireballs.get(centre)));
removeLine(first, centre, end);
continue;
}
centre = first.plus(0, HV_DELTA_1);
end = first.plus(0, HV_DELTA_2);
if (fireballsLocations.contains(centre) && fireballsLocations.contains(end))
{
result.add(new FireballLine(LineType.V, centre, fireballs.get(centre)));
removeLine(first, centre, end);
continue;
}
centre = first.plus(D_DELTA_1, -D_DELTA_1);
end = first.plus(D_DELTA_2, -D_DELTA_2);
if (fireballsLocations.contains(centre) && fireballsLocations.contains(end))
{
result.add(new FireballLine(LineType.D1, centre, fireballs.get(centre)));
removeLine(first, centre, end);
continue;
}
return result;
}
return result;
}
private void removeLine(final LocalPoint a, final LocalPoint b, final LocalPoint c)
{
fireballs.remove(a);
fireballs.remove(b);
fireballs.remove(c);
}
public static boolean eq(final WorldPoint t, final int dx, final int dy, final WorldPoint other)
{
if (t == null || other == null)
{
return false;
}
return t.getX() + dx == other.getX() && t.getY() + dy == other.getY();
}
static class FireballLine
{
private final LineType type;
private final LocalPoint centre;
private final GraphicsObject cObject;
FireballLine(final LineType type, final LocalPoint centre, final GraphicsObject cObject)
{
this.centre = centre;
this.cObject = cObject;
this.type = type;
}
public boolean isActive()
{
final int animationFrame = cObject.getAnimationFrame();
return animationFrame >= 30 && animationFrame <= 40;
}
public boolean inSweetSpot(final Client client, final WorldPoint lp)
{
final WorldPoint wp = WorldPoint.fromLocal(client, centre);
switch (this.type)
{
case V:
return eq(wp, 1, 0, lp) || eq(wp, -1, 0, lp);
case H:
return eq(wp, 0, 1, lp) || eq(wp, 0, -1, lp);
case D1:
return eq(wp, 1, 1, lp) || eq(wp, -1, -1, lp);
case D2:
return eq(wp, -1, 1, lp) || eq(wp, 1, -1, lp);
}
return false;
}
}
enum LineType
{
V, H, D1, D2
}
}
@@ -0,0 +1,26 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.AttackType;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import net.runelite.api.Client;
import net.runelite.api.gameval.NpcID;
public class TombsOfAmascut implements IVariableSpeed
{
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
final int targetId = CombatUtils.getTargetId(client);
final AttackType attkType = CombatUtils.getAttackType(client);
if (targetId == NpcID.WARDENS_P3_ORB_BLUE && attkType != null && attkType.IsMelee())
{
return 1;
}
return curSpeed;
}
}
@@ -0,0 +1,184 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.AttackType;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import com.example.essentialsplus.helpers.combat.Spellbook;
import com.example.essentialsplus.helpers.combat.WeaponType;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import java.util.HashMap;
import java.util.Map;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.gameval.NpcID;
import net.runelite.api.gameval.SpotanimID;
public class TormentedDemons implements IVariableSpeed
{
private final TickCount tickCount;
private final Map<NPC, DemonData> tormentedDemons = new HashMap<>();
public TormentedDemons(TickCount tc)
{
this.tickCount = tc;
}
@Override
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
final int targetId = CombatUtils.getTargetId(client);
if (!isTormentedDemon(targetId))
{
return curSpeed;
}
final NPC target = CombatUtils.getTargetNPC(client);
if (!tormentedDemons.containsKey(target))
{
return curSpeed;
}
final DemonData targetDemon = tormentedDemons.get(target);
if (!targetDemon.isVulnerable(tickCount.get()))
{
return curSpeed;
}
if (targetDemon.vulnConsumed(tickCount.get()))
{
return curSpeed;
}
switch (atkType)
{
case POWERED_STAVE:
return curSpeed;
case MANUAL_AUTO_CAST:
break;
case MELEE_OR_RANGE:
final WeaponType wep = CombatUtils.getWeaponType(client);
final AttackType attkType = CombatUtils.getAttackType(client);
if (wep != WeaponType.HEAVY_RANGED && (attkType == null || attkType != AttackType.CRUSH))
{
return curSpeed;
}
break;
}
if (curSpeed > 4)
{
return 4;
}
return curSpeed;
}
private static boolean isTormentedDemon(int targetId)
{
return targetId == NpcID.TORMENTED_DEMON_1 || targetId == NpcID.TORMENTED_DEMON_2;
}
@Override
public void onGameTick(Client client, GameTick tick)
{
for (final Map.Entry<NPC, DemonData> td : tormentedDemons.entrySet())
{
final boolean isVulnerable = td.getKey().hasSpotAnim(SpotanimID.LUC2_UNDEAD_DEMON_EXPLOSION_FIRE_SPOT);
td.getValue().update(tickCount.get(), isVulnerable);
}
}
@Override
public void onNpcSpawned(final Client client, final NpcSpawned npcSpawned)
{
final NPC npc = npcSpawned.getNpc();
maybeInsertDemon(npc);
}
private void maybeInsertDemon(final NPC npc)
{
if (isTormentedDemon(npc.getId()))
{
final boolean isVulnerable = npc.hasSpotAnim(SpotanimID.LUC2_UNDEAD_DEMON_EXPLOSION_FIRE_SPOT);
tormentedDemons.put(npc, new DemonData(tickCount.get(), isVulnerable));
}
}
@Override
public void onNpcDespawned(final Client client, final NpcDespawned npcDespawned)
{
final NPC npc = npcDespawned.getNpc();
if (isTormentedDemon(npc.getId()))
{
tormentedDemons.remove(npc);
}
}
static class DemonData
{
private static final int VULN_TICKS_AFTER_END = 10;
private Integer vulnerableStart;
private Integer vulnerableFinish;
private int attacked;
DemonData(int tick, boolean vuln)
{
this.update(tick, vuln);
this.attacked = -1;
}
void update(int tick, boolean vuln)
{
if (vuln && this.vulnerableStart == null)
{
this.vulnerableStart = tick;
this.vulnerableFinish = tick;
}
else if (vuln && this.vulnerableStart != null)
{
this.vulnerableFinish = tick;
}
if (!vuln)
{
this.vulnerableStart = null;
}
if (this.attacked != -1 && this.attacked + 1 >= tick)
{
this.attacked = -1;
}
}
boolean isVulnerable(int tick)
{
if (this.vulnerableFinish == null)
{
return false;
}
return (this.vulnerableFinish + VULN_TICKS_AFTER_END) > tick;
}
boolean vulnConsumed(int tick)
{
if (this.attacked == tick || this.attacked == -1)
{
this.consumeVuln(tick);
return false;
}
else if (this.attacked > tick + VULN_TICKS_AFTER_END)
{
return true;
}
else
{
this.consumeVuln(tick);
return false;
}
}
void consumeVuln(int tick)
{
this.attacked = tick;
}
}
}
@@ -0,0 +1,117 @@
package com.example.essentialsplus.helpers.combat.variablespeed;
import com.example.essentialsplus.helpers.combat.AnimationData;
import com.example.essentialsplus.helpers.combat.AttackProcedure;
import com.example.essentialsplus.helpers.combat.Spellbook;
import com.example.essentialsplus.helpers.combat.variablespeed.state.IStateTracker;
import com.example.essentialsplus.helpers.combat.variablespeed.state.MarkOfDarkness;
import com.example.essentialsplus.helpers.combat.variablespeed.state.TickCount;
import com.example.essentialsplus.helpers.combat.variablespeed.state.Yama;
import net.runelite.api.Client;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
public class VariableSpeed
{
private final TickCount tickCount;
private final Yama yama;
private final MarkOfDarkness markOfDarkness;
public final ShadowCrash shadowCrash;
public final MaggotKing maggotKing;
public final DoomOfMokhaiotl doomOfMokhaiotl;
private final IStateTracker[] toTrack;
private final IVariableSpeed[] toApply;
public VariableSpeed(TickCount tc)
{
this.tickCount = tc;
this.yama = new Yama();
this.markOfDarkness = new MarkOfDarkness();
this.shadowCrash = new ShadowCrash(yama, markOfDarkness, tc);
this.maggotKing = new MaggotKing(tc);
this.doomOfMokhaiotl = new DoomOfMokhaiotl(tc);
this.toTrack = new IStateTracker[]{
tc,
yama,
markOfDarkness
};
this.toApply = new IVariableSpeed[]{
new BloodMoonSet(),
new RapidAttackStyle(),
new RedKerisSpec(),
new PurgingStaffSpec(yama),
new EyeOfAyak(),
new TormentedDemons(tc),
new RoyalTitans(),
new Amoxliatl(),
new Scurrius(),
new TombsOfAmascut(),
doomOfMokhaiotl
};
}
public int compute(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed)
{
int newSpeed = baseSpeed;
for (final IVariableSpeed i : toApply)
{
newSpeed = i.apply(client, curAnimation, atkType, spellbook, damageDealt, lastSpecDelta, baseSpeed, newSpeed);
}
return newSpeed;
}
public void onGameTick(final Client client, final GameTick tick)
{
for (final IStateTracker i : toTrack)
{
i.onGameTick(client, tick);
}
for (final IStateTracker i : toApply)
{
i.onGameTick(client, tick);
}
}
public void onChatMessage(final Client client, final ChatMessage event)
{
for (final IStateTracker i : toTrack)
{
i.onChatMessage(client, event);
}
for (final IStateTracker i : toApply)
{
i.onChatMessage(client, event);
}
}
public void onNpcSpawned(final Client client, final NpcSpawned npcSpawned)
{
for (final IStateTracker i : toTrack)
{
i.onNpcSpawned(client, npcSpawned);
}
for (final IStateTracker i : toApply)
{
i.onNpcSpawned(client, npcSpawned);
}
}
public void onNpcDespawned(final Client client, final NpcDespawned npcDespawned)
{
for (final IStateTracker i : toTrack)
{
i.onNpcDespawned(client, npcDespawned);
}
for (final IStateTracker i : toApply)
{
i.onNpcDespawned(client, npcDespawned);
}
}
}
@@ -0,0 +1,26 @@
package com.example.essentialsplus.helpers.combat.variablespeed.state;
import net.runelite.api.Client;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
public interface IStateTracker
{
default void onGameTick(final Client client, final GameTick tick)
{
}
default void onChatMessage(final Client client, final ChatMessage event)
{
}
default void onNpcSpawned(final Client client, final NpcSpawned npcSpawned)
{
}
default void onNpcDespawned(final Client client, final NpcDespawned npcDespawned)
{
}
}
@@ -0,0 +1,47 @@
package com.example.essentialsplus.helpers.combat.variablespeed.state;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import java.time.Duration;
import java.time.Instant;
import net.runelite.api.Client;
import net.runelite.api.Skill;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.gameval.ItemID;
import net.runelite.client.util.RSTimeUnit;
public class MarkOfDarkness implements IStateTracker
{
private static final String MARK_OF_DARKNESS_MESSAGE = "You have placed a Mark of Darkness upon yourself.</col>";
private Instant modStartTime = Instant.now();
private Instant modEndTime = Instant.now();
@Override
public void onChatMessage(Client client, ChatMessage event)
{
final String message = event.getMessage();
if (message != null && message.endsWith(MARK_OF_DARKNESS_MESSAGE))
{
final Duration duration = getMarkOfDarknessDuration(client);
this.modStartTime = Instant.now();
this.modEndTime = modStartTime.plus(duration);
}
}
public boolean isActive()
{
final Instant n = Instant.now();
return this.modStartTime.isBefore(n) && this.modEndTime.isAfter(n);
}
private static Duration getMarkOfDarknessDuration(Client client)
{
final int magicLevel = client.getRealSkillLevel(Skill.MAGIC);
final Duration markOfDarknessDuration = Duration.of((long) magicLevel * 3, RSTimeUnit.GAME_TICKS);
if (CombatUtils.getWeaponId(client) == ItemID.PURGING_STAFF)
{
return markOfDarknessDuration.multipliedBy(5);
}
return markOfDarknessDuration;
}
}
@@ -0,0 +1,30 @@
package com.example.essentialsplus.helpers.combat.variablespeed.state;
import net.runelite.api.Client;
import net.runelite.api.events.GameTick;
public class TickCount implements IStateTracker
{
private int tickCount = 0;
public int get()
{
return tickCount;
}
@Override
public void onGameTick(Client client, GameTick tick)
{
tickCount++;
}
public boolean isWithinNTicks(int toCheckAgainst, int n)
{
return this.tickCount <= toCheckAgainst + n && this.tickCount >= toCheckAgainst;
}
public void reset()
{
tickCount = 0;
}
}
@@ -0,0 +1,177 @@
package com.example.essentialsplus.helpers.combat.variablespeed.state;
import com.example.essentialsplus.helpers.combat.CombatUtils;
import java.util.HashMap;
import java.util.Map;
import lombok.NonNull;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.gameval.NpcID;
public class Yama implements IStateTracker
{
private static final int HP_FUDGE = 2;
private static final int VOID_FLARE_HP_P1_P2 = 140 - HP_FUDGE;
private static final int VOID_FLARE_HP_P3 = 71 - HP_FUDGE;
private static final int YAMA_REGION_ID = 6045;
private static final int YAMA_PHASE_TRANSITION_ANIMATION_ID = 12147;
public YamaData yama;
public boolean inYamaRegion;
public NPC lastTarget;
@Override
public void onGameTick(Client client, GameTick tick)
{
inYamaRegion = CombatUtils.isInRegionId(client, YAMA_REGION_ID);
if (!inYamaRegion)
{
yama = null;
return;
}
if (yama != null)
{
yama.determineYamaPhase();
}
}
@Override
public void onChatMessage(Client client, ChatMessage event)
{
if (!inYamaRegion || yama == null)
{
return;
}
if (event.getMessage().startsWith("Your Yama success count is:"))
{
yama.killed();
}
}
@Override
public void onNpcSpawned(final Client client, final NpcSpawned npcSpawned)
{
if (!inYamaRegion || yama != null)
{
return;
}
final NPC npc = npcSpawned.getNpc();
if (npc.getId() == NpcID.YAMA)
{
yama = new YamaData(npc);
}
}
public static NPC isEitherVoidFlare(NPC a, NPC b)
{
if (a != null && a.getId() == NpcID.YAMA_VOIDFLARE)
{
return a;
}
if (b != null && b.getId() == NpcID.YAMA_VOIDFLARE)
{
return b;
}
return null;
}
public void dealVoidFlareDamage(NPC target, int damageDealt)
{
if (yama == null)
{
return;
}
this.yama.dealVoidFlareDamage(target, damageDealt);
}
public boolean getVoidFlareDead(NPC target)
{
if (yama == null)
{
return false;
}
return this.yama.getVoidFlareDead(target);
}
public YamaPhase phase()
{
if (yama == null)
{
return YamaPhase.P1;
}
return this.yama.phase;
}
public static class YamaData
{
@NonNull
private final NPC yama;
private YamaPhase phase;
private int phaseChangeCooldown;
private final Map<NPC, Integer> voidFlares;
YamaData(@NonNull NPC yama)
{
this.yama = yama;
this.phase = YamaPhase.P1;
this.phaseChangeCooldown = 0;
this.voidFlares = new HashMap<>();
}
public boolean getVoidFlareDead(NPC target)
{
if (!this.voidFlares.containsKey(target))
{
return false;
}
return this.voidFlares.get(target) <= 0;
}
public void dealVoidFlareDamage(NPC target, int damageDealt)
{
if (this.voidFlares.containsKey(target))
{
this.voidFlares.put(target, this.voidFlares.get(target) - damageDealt);
}
else
{
final int hp = this.getVoidFlareHp() - damageDealt;
this.voidFlares.put(target, hp);
}
}
private void determineYamaPhase()
{
if (phaseChangeCooldown == 0 && this.yama.getAnimation() == YAMA_PHASE_TRANSITION_ANIMATION_ID)
{
this.phaseChangeCooldown = 20;
this.phase = this.phase.nextPhase();
}
if (this.phaseChangeCooldown > 0)
{
this.phaseChangeCooldown--;
}
}
private int getVoidFlareHp()
{
switch (this.phase)
{
case P1:
case P2:
return VOID_FLARE_HP_P1_P2;
default:
return VOID_FLARE_HP_P3;
}
}
private void killed()
{
this.phase = YamaPhase.P1;
this.voidFlares.clear();
}
}
}
@@ -0,0 +1,20 @@
package com.example.essentialsplus.helpers.combat.variablespeed.state;
public enum YamaPhase
{
P1, P2, P3;
public YamaPhase nextPhase()
{
switch (this)
{
case P1:
return P2;
case P2:
return P3;
case P3:
return P3;
}
return P1;
}
}