Combo runes are now consumed before standard elemental runes if both are present in inventory
1 combo rune can count for 2 elemental runes if the spell requires both
This commit is contained in:
@@ -47,12 +47,11 @@ abstract class SpellListener(val bookName: String) : Listener {
|
|||||||
player.sendMessage("You need a magic level of $magicLevel to cast this spell.")
|
player.sendMessage("You need a magic level of $magicLevel to cast this spell.")
|
||||||
throw IllegalStateException()
|
throw IllegalStateException()
|
||||||
}
|
}
|
||||||
for(rune in runes){
|
val missing = SpellUtils.hasRunes(player, runes)
|
||||||
if(!SpellUtils.hasRune(player,rune)){
|
if (missing != null) {
|
||||||
player.sendMessage("You don't have enough ${rune.definition.name.lowercase()}s to cast this spell.")
|
player.sendMessage("You don't have enough ${missing.definition.name.lowercase()}s to cast this spell.")
|
||||||
throw IllegalStateException()
|
throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for(item in specialEquipment){
|
for(item in specialEquipment){
|
||||||
if(!player.equipment.contains(item,1)){
|
if(!player.equipment.contains(item,1)){
|
||||||
player.sendMessage("You need a ${ItemDefinition.forId(item).name} to cast this.")
|
player.sendMessage("You need a ${ItemDefinition.forId(item).name} to cast this.")
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package content.global.skill.magic
|
|||||||
import core.game.node.entity.combat.spell.CombinationRune
|
import core.game.node.entity.combat.spell.CombinationRune
|
||||||
import core.game.node.entity.combat.spell.MagicStaff
|
import core.game.node.entity.combat.spell.MagicStaff
|
||||||
import core.game.node.entity.combat.spell.Runes
|
import core.game.node.entity.combat.spell.Runes
|
||||||
import core.game.node.entity.npc.NPC
|
|
||||||
import core.game.node.entity.player.Player
|
import core.game.node.entity.player.Player
|
||||||
import core.game.node.item.Item
|
import core.game.node.item.Item
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
object SpellUtils {
|
object SpellUtils {
|
||||||
/**
|
/**
|
||||||
@@ -32,70 +32,57 @@ object SpellUtils {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasRune(p:Player,rune:Item):Boolean{
|
/**
|
||||||
val removeItems = p.getAttribute("spell:runes",ArrayList<Item>())
|
* Validates if the player has the necessary runes to cast a spell.
|
||||||
if(usingStaff(p,rune.id)) return true
|
*
|
||||||
if(p.inventory.containsItem(rune)){
|
* If the player is able to cast the spell, the "spell:runes" attribute will be set to the list of items that should
|
||||||
removeItems.add(rune)
|
* be removed from the player's inventory after successfully casting the spell. This accounts for staves and
|
||||||
p.setAttribute("spell:runes",removeItems)
|
* combination runes.
|
||||||
|
*
|
||||||
|
* @param p The player casting the spell
|
||||||
|
* @param runes The runes and other items required to cast the spell
|
||||||
|
* @return null if the player can cast the spell or an Item representing at least one of the runes the player is missing
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun hasRunes(p: Player, runes: Array<Item>): Item? {
|
||||||
|
val cost = HashMap<Int, Int>()
|
||||||
|
// `runes` are mostly actual runes but occasionally other items like staves or unpowered orbs
|
||||||
|
for (rune in runes) {
|
||||||
|
if (usingStaff(p, rune.id)) continue
|
||||||
|
cost[rune.id] = cost.getOrDefault(rune.id, 0) + rune.amount
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseAmt = p.inventory.getAmount(rune.id)
|
val toRemove = ArrayList<Item>()
|
||||||
var amtRemaining = rune.amount - baseAmt
|
|
||||||
val possibleComboRunes = CombinationRune.eligibleFor(Runes.forId(rune.id))
|
// Combination runes are used before elemental runes.
|
||||||
for (r in possibleComboRunes) {
|
// https://runescape.wiki/w/Runecrafting?oldid=2618332#Function_and_Usage_of_Combination_Runes:
|
||||||
if (p.inventory.containsItem(Item(r.id)) && amtRemaining > 0) {
|
for (combo in CombinationRune.values()) {
|
||||||
val amt = p.inventory.getAmount(r.id)
|
val available = p.inventory.getAmount(combo.id)
|
||||||
if (amtRemaining <= amt) {
|
val maxUsage = combo.types.mapNotNull { cost[it.id] }.maxOrNull() ?: 0
|
||||||
removeItems.add(Item(r.id,amtRemaining))
|
if (maxUsage > 0 && available >= 0) {
|
||||||
amtRemaining = 0
|
val usage = min(maxUsage, available)
|
||||||
break
|
|
||||||
|
toRemove.add(Item(combo.id, usage))
|
||||||
|
// Even if a spell uses both parts of a combo rune, it should only consume a single rune. For example,
|
||||||
|
// a spell that requires an air rune and an earth rune should only consume a single dust rune.
|
||||||
|
// https://youtu.be/9gAiqEmF-Hc?t=67
|
||||||
|
combo.types.forEach { cost[it.id] = cost.getOrDefault(it.id, 0) - usage }
|
||||||
}
|
}
|
||||||
removeItems.add(Item(r.id,p.inventory.getAmount(r.id)))
|
|
||||||
amtRemaining -= p.inventory.getAmount(r.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.setAttribute("spell:runes",removeItems)
|
|
||||||
return amtRemaining <= 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasRune(p: Player, item: Item, toRemove: MutableList<Item?>, message: Boolean): Boolean {
|
for ((runeId, amount) in cost) {
|
||||||
if (!usingStaff(p, item.id)) {
|
if (amount <= 0) continue
|
||||||
val hasBaseRune = p.inventory.contains(item.id, item.amount)
|
|
||||||
if (!hasBaseRune) {
|
val available = p.inventory.getAmount(runeId)
|
||||||
val baseAmt = p.inventory.getAmount(item.id)
|
if (available < amount) {
|
||||||
if (baseAmt > 0) {
|
return Item(runeId, amount)
|
||||||
toRemove.add(Item(item.id, p.inventory.getAmount(item.id)))
|
|
||||||
}
|
|
||||||
var amtRemaining = item.amount - baseAmt
|
|
||||||
val possibleComboRunes = CombinationRune.eligibleFor(Runes.forId(item.id))
|
|
||||||
for (r in possibleComboRunes) {
|
|
||||||
if (p.inventory.containsItem(Item(r.id)) && amtRemaining > 0) {
|
|
||||||
val amt = p.inventory.getAmount(r.id)
|
|
||||||
if (amtRemaining < amt) {
|
|
||||||
toRemove.add(Item(r.id, amtRemaining))
|
|
||||||
amtRemaining = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
amtRemaining -= p.inventory.getAmount(r.id)
|
|
||||||
toRemove.add(Item(r.id, p.inventory.getAmount(r.id)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (amtRemaining <= 0) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
p.packetDispatch.sendMessage("You don't have enough " + item.name + "s to cast this spell.")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toRemove.add(item)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun attackableNPC(npc: NPC): Boolean{
|
toRemove.add(Item(runeId, amount))
|
||||||
return npc.definition.hasAction("attack")
|
}
|
||||||
|
|
||||||
|
p.setAttribute("spell:runes", toRemove)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package content.global.skill.magic.modern
|
|||||||
|
|
||||||
import content.data.Quests
|
import content.data.Quests
|
||||||
import content.global.skill.magic.SpellListener
|
import content.global.skill.magic.SpellListener
|
||||||
import content.global.skill.magic.SpellUtils.hasRune
|
import content.global.skill.magic.SpellUtils.hasRunes
|
||||||
import content.global.skill.magic.TeleportMethod
|
import content.global.skill.magic.TeleportMethod
|
||||||
import content.global.skill.magic.homeTeleport
|
import content.global.skill.magic.homeTeleport
|
||||||
import content.global.skill.magic.spellconsts.Modern
|
import content.global.skill.magic.spellconsts.Modern
|
||||||
@@ -323,12 +323,11 @@ class ModernListeners : SpellListener("modern"){
|
|||||||
sendMessage(player, "You need a magic level of ${spell.level} to cast this spell.")
|
sendMessage(player, "You need a magic level of ${spell.level} to cast this spell.")
|
||||||
return@queueScript stopExecuting(player)
|
return@queueScript stopExecuting(player)
|
||||||
}
|
}
|
||||||
for (rune in spell.requiredRunes) {
|
val missing = hasRunes(player, spell.requiredRunes)
|
||||||
if(!hasRune(player,rune)){
|
if (missing != null) {
|
||||||
sendMessage(player, "You don't have enough ${rune.name.lowercase()}s to cast this spell.")
|
sendMessage(player, "You don't have enough ${missing.name.lowercase()}s to cast this spell.")
|
||||||
return@queueScript stopExecuting(player)
|
return@queueScript stopExecuting(player)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
visualizeSpell(player, CHARGE_ORB_ANIM, spell.graphics, spell.sound)
|
visualizeSpell(player, CHARGE_ORB_ANIM, spell.graphics, spell.sound)
|
||||||
removeRunes(player)
|
removeRunes(player)
|
||||||
val success = addItem(player, spell.chargedOrb)
|
val success = addItem(player, spell.chargedOrb)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package core.game.node.entity.combat.spell;
|
package core.game.node.entity.combat.spell;
|
||||||
|
|
||||||
|
import content.global.skill.magic.SpellUtils;
|
||||||
import core.game.event.SpellCastEvent;
|
import core.game.event.SpellCastEvent;
|
||||||
import core.game.node.Node;
|
import core.game.node.Node;
|
||||||
import core.game.node.entity.Entity;
|
import core.game.node.entity.Entity;
|
||||||
@@ -17,7 +18,6 @@ import core.plugin.Plugin;
|
|||||||
import core.tools.RandomFunction;
|
import core.tools.RandomFunction;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static core.api.ContentAPIKt.playGlobalAudio;
|
import static core.api.ContentAPIKt.playGlobalAudio;
|
||||||
|
|
||||||
@@ -223,16 +223,17 @@ public abstract class MagicSpell implements Plugin<SpellType> {
|
|||||||
if (runes == null) {
|
if (runes == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
List<Item> toRemove = new ArrayList<>(20);
|
Item missing = SpellUtils.hasRunes(p, runes);
|
||||||
for (Item item : runes) {
|
if (missing != null) {
|
||||||
if (!hasRune(p, item, toRemove, message)) {
|
if (message) {
|
||||||
|
p.getPacketDispatch().sendMessage("You don't have enough " + missing.getName() + "s to cast this spell.");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (remove) {
|
if (remove) {
|
||||||
toRemove.forEach(i -> {
|
ArrayList<Item> toRemove = p.getAttribute("spell:runes", new ArrayList<>());
|
||||||
p.getInventory().remove(i);
|
toRemove.forEach(i -> p.getInventory().remove(i));
|
||||||
});
|
p.removeAttribute("spell:runes");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -255,49 +256,6 @@ public abstract class MagicSpell implements Plugin<SpellType> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the player has a rune to remove.
|
|
||||||
* @param p the player.
|
|
||||||
* @param item the item.
|
|
||||||
* @param toRemove the list of items to remove.
|
|
||||||
* @param message the message.
|
|
||||||
* @return {@code True} if so.
|
|
||||||
*/
|
|
||||||
public boolean hasRune(Player p, Item item, List<Item> toRemove, boolean message) {
|
|
||||||
if (!usingStaff(p, item.getId())) {
|
|
||||||
boolean hasBaseRune = p.getInventory().contains(item.getId(),item.getAmount());
|
|
||||||
if(!hasBaseRune){
|
|
||||||
int baseAmt = p.getInventory().getAmount(item.getId());
|
|
||||||
if(baseAmt > 0){
|
|
||||||
toRemove.add(new Item(item.getId(),p.getInventory().getAmount(item.getId())));
|
|
||||||
}
|
|
||||||
int amtRemaining = item.getAmount() - baseAmt;
|
|
||||||
List<CombinationRune> possibleComboRunes = CombinationRune.eligibleFor(Runes.forId(item.getId()));
|
|
||||||
for(CombinationRune r : possibleComboRunes){
|
|
||||||
if(p.getInventory().containsItem(new Item(r.id)) && amtRemaining > 0){
|
|
||||||
int amt = p.getInventory().getAmount(r.id);
|
|
||||||
if(amtRemaining < amt){
|
|
||||||
toRemove.add(new Item(r.id,amtRemaining));
|
|
||||||
amtRemaining = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
amtRemaining -= p.getInventory().getAmount(r.id);
|
|
||||||
toRemove.add(new Item(r.id,p.getInventory().getAmount(r.id)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(amtRemaining <= 0){
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
p.getPacketDispatch().sendMessage("You don't have enough " + item.getName() + "s to cast this spell.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toRemove.add(item);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the experience for casting this spell.
|
* Adds the experience for casting this spell.
|
||||||
* @param entity The entity to reward with experience.
|
* @param entity The entity to reward with experience.
|
||||||
|
|||||||
Reference in New Issue
Block a user