diff --git a/data/plugins/combat/attack.rb b/data/plugins/combat/attack.rb
index 2f7b75d0..bf4ccd9d 100644
--- a/data/plugins/combat/attack.rb
+++ b/data/plugins/combat/attack.rb
@@ -9,10 +9,10 @@ class BaseAttack
fail 'Attack speed must be a non-negative number' if speed < 0
fail 'Attack range must be a non-negative number' if range < 0
- @speed = speed
- @animation = animation
- @graphic = graphic
- @range = range
+ @speed = speed
+ @animation = animation
+ @graphic = graphic
+ @range = range
@requirements = requirements
end
@@ -49,7 +49,7 @@ end
##
# A simple ranged attack, which sends a projectile based on the currently equipped ammo and weapon
-# and deals damaged delayed by the speed and travel distance of the projectile.
+# and deals damage delayed by the speed and travel distance of the projectile.
class RangedAttack < BaseAttack
def apply(source, target)
@@ -57,6 +57,51 @@ class RangedAttack < BaseAttack
end
end
+##
+# A simple magic attack, which sends a projectile based on the current spell that the player is casting and deals
+# damage delayed by the speed and travel distance of the projectile.
+
+class MagicAttack < BaseAttack
+
+ ##
+ # The maximum distance that a magic attack can be performed from.
+ MAX_DISTANCE = 8
+
+ ##
+ # The speed that magic attacks can be casted at.
+ SPEED = 5
+
+ def initialize(spell)
+ super(animation: spell.animation, graphic: spell.graphic, speed: SPEED, range: MAX_DISTANCE)
+
+ @damage = spell.damage
+ @hit_graphic = spell.hit_graphic
+ @projectile = spell.projectile
+ @projectile_type = PROJECTILE_TYPES[spell.projectile_type]
+ end
+
+ def apply(source, target)
+
+ projectile!(source, target, @projectile, @projectile_type)
+
+ distance = source.position.get_distance(target.position)
+ damage_delay = (@projectile_type.delay + @projectile_type.speed + distance * 5) * 0.02857
+
+ schedule_damage!(source, target, rand(@damage), damage_delay) do
+
+ unless @hit_graphic.nil?
+ if @hit_graphic.is_a?(Hash)
+ target.play_graphic(Graphic.new(@hit_graphic[:id], @hit_graphic[:delay] || 0, @hit_graphic[:height] || 0))
+ else
+ target.play_graphic(Graphic.new(@hit_graphic))
+ end
+ end
+
+ end
+ end
+
+end
+
##
# A basic melee attack, which deals damage a tick after the attack was made.
@@ -74,7 +119,7 @@ class AttackDSL
def initialize()
@requirements = []
- @subattacks = []
+ @subattacks = []
end
def add_requirement(requirement)
@@ -95,7 +140,7 @@ class AttackDSL
def damage!(damage_modifier: 1, delay: 0, secondary: false)
@subattacks.push lambda { |source, target|
- do_damage! source, target, 1, delay, secondary
+ schedule_damage! source, target, 1, delay, secondary
}
end
@@ -118,33 +163,41 @@ end
private
-##
-# TODO: refactor
-def do_damage!(source, target, amount, delay = 0, secondary = false, &_block)
+def schedule_damage!(source, target, amount, delay, secondary = false, &callback)
schedule delay do |task|
- task.stop && return if source.dead || target.dead
- target_combat_state = target.get_combat_state
- target_hitpoints = target.skill_set.get_skill(Skill::HITPOINTS).get_current_level
- amount = target_hitpoints if target_hitpoints < amount
-
- type = amount > 0 ? 1 : 0
- target.play_animation(Animation.new(424)) unless target_combat_state.will_attack?
- target.damage(amount, type, secondary)
-
- if target.auto_retaliate && !target_combat_state.is_attacking?
- target_combat_state.target = source
- target.start_action CombatAction.new(target)
- end
+ do_damage!(source, target, amount, secondary)
+ callback.call if block_given?
task.stop
end
end
+def do_damage!(source, target, amount, secondary = false)
+ return if source.dead || target.dead
+
+ target_hitpoints = target.skill_set.get_skill(Skill::HITPOINTS).get_current_level
+ amount = target_hitpoints if target_hitpoints < amount
+
+ type = (amount > 0) ? 1 : 0
+ target.damage(amount, type, secondary)
+
+ auto_retaliate!(source, target)
+end
+
+def auto_retaliate!(source, target)
+ target_combat_state = target.get_combat_state
+
+ if target.auto_retaliate && !target_combat_state.is_attacking?
+ target_combat_state.target = source
+ target.start_action(CombatAction.new(target))
+ end
+end
+
##
# TODO: refactor
-def do_ranged_damage!(source, target, _damage_modifier = 1, speed_modifier = 1, projectile = nil, projectile_type = nil, projectile_graphic = nil,
+def do_ranged_damage!(source, target, _damage_modifier = 1, speed_modifier = 1, projectile = nil, projectile_type = nil, projectile_graphic = nil,
delay = 0, secondary = false)
apply = lambda do
distance = source.position.get_distance(target.position)
@@ -152,19 +205,19 @@ def do_ranged_damage!(source, target, _damage_modifier = 1, speed_modifier = 1,
ammo = EquipmentUtil.equipped_ammo source
if projectile.nil? && projectile_type.nil?
- projectile = ammo.projectile
- projectile_type = ammo.projectile_type
+ projectile = ammo.projectile
+ projectile_type = ammo.projectile_type
projectile_graphic = ammo.graphic
end
projectile! source, target, projectile, projectile_type, speed_modifier
- do_damage! source, target, rand(1...50), (projectile_type.delay + projectile_type.speed + distance * 5) * 0.02857, secondary
+ schedule_damage! source, target, rand(1...50), (projectile_type.delay + projectile_type.speed + distance * 5) * 0.02857, secondary
unless projectile_graphic.nil?
source.play_graphic Graphic.new(projectile_graphic, 0, 100)
end
end
-
+
if delay == 0
apply.call
else
@@ -183,13 +236,13 @@ def projectile!(source, target, projectile_id, projectile_type, speed_modifier =
distance = source.position.get_distance(target.position)
projectile_parameters = {
- angle: -1,
+ angle: -1,
start_height: projectile_type.start_height,
- end_height: projectile_type.end_height,
- delay: projectile_type.delay * (2 - speed_modifier),
- speed: (projectile_type.delay + projectile_type.speed + distance * 5) * speed_modifier,
- slope: projectile_type.slope,
- radius: projectile_type.radius
+ end_height: projectile_type.end_height,
+ delay: projectile_type.delay * (2 - speed_modifier),
+ speed: (projectile_type.delay + projectile_type.speed + distance * 5) * speed_modifier,
+ slope: projectile_type.slope,
+ radius: projectile_type.radius
}
ProjectileModule.fire_projectile(source.position, target.position, projectile_id, projectile_parameters, target)
diff --git a/data/plugins/combat/attributes.rb b/data/plugins/combat/attributes.rb
index ddcabf27..21dbba54 100644
--- a/data/plugins/combat/attributes.rb
+++ b/data/plugins/combat/attributes.rb
@@ -18,6 +18,10 @@ declare_attribute(:logout_timer, Time.now.to_i)
# The CombatStyle offset that a Mob is currently using.
declare_attribute(:combat_style, 0, :persistent)
+##
+# The CombatSpell offset that a Mob is currently using.
+declare_attribute(:combat_spell, :none, :persistent)
+
##
# A flag indicating whether the special bar is flagged for the next attack.
declare_attribute(:using_special, false, :persistent)
diff --git a/data/plugins/combat/combat.rb b/data/plugins/combat/combat.rb
index 92c7559e..98be870f 100644
--- a/data/plugins/combat/combat.rb
+++ b/data/plugins/combat/combat.rb
@@ -1,7 +1,7 @@
java_import 'org.apollo.game.message.impl.HintIconMessage'
on :message, :npc_action do |player, message|
- target = $world.npc_repository.get message.index
+ target = $world.npc_repository.get message.index
# unless target.attacking
# target_combat_state = get_combat_state target
@@ -10,12 +10,28 @@ on :message, :npc_action do |player, message|
# target.start_action CombatAction.new(target)
# end
- player_combat_state = player.get_combat_state
+ player_combat_state = player.get_combat_state
player_combat_state.target = target
player.send HintIconMessage.for_npc(target.index)
player.walking_queue.clear
- player.start_action CombatAction.new(player) unless player_combat_state.is_attacking?
+ player.start_action CombatAction.new(player)
+end
+
+on :message, :magic_on_mob do |player, message|
+ target = $world.npc_repository.get(message.index)
+
+ player_combat_state = player.get_combat_state
+ player_combat_state.target = target
+
+ spellbook = spellbook_for(message.interface_id)
+ spell = spell_for(spellbook, message.spell_id)
+
+ player.walking_queue.clear
+ player.start_action CombatAction.new(player)
+
+ magic_attack = MagicAttack.new(spell)
+ player_combat_state.queue_attack(magic_attack)
end
on :message, :player_action do |player, message|
diff --git a/data/plugins/combat/combat_action.rb b/data/plugins/combat/combat_action.rb
index 588bd7c7..aad85afc 100644
--- a/data/plugins/combat/combat_action.rb
+++ b/data/plugins/combat/combat_action.rb
@@ -18,7 +18,7 @@ class CombatAction < Action
current_distance = mob.position.get_distance target.position
attack_collision_type = next_attack.range > 1 ? EntityType::PROJECTILE : EntityType::NPC
- in_range = current_distance <= next_attack.range && !$world.intersects(mob.position, target.position, attack_collision_type)
+ in_range = current_distance <= next_attack.range# && !$world.intersects(mob.position, target.position, attack_collision_type)
unless in_range
mob.follow @combat_state.target, next_attack.range
diff --git a/data/plugins/combat/combat_spell.rb b/data/plugins/combat/combat_spell.rb
new file mode 100644
index 00000000..68aca543
--- /dev/null
+++ b/data/plugins/combat/combat_spell.rb
@@ -0,0 +1,95 @@
+class CombatSpell
+
+ attr_reader :button
+
+ attr_reader :spellbook
+
+ attr_reader :level
+
+ attr_reader :damage
+
+ attr_reader :runes
+
+ attr_reader :animation
+
+ attr_reader :graphic
+
+ attr_reader :hit_graphic
+
+ attr_reader :projectile
+
+ attr_reader :projectile_type
+
+ def initialize(button, spellbook, level, damage, runes, animation, graphic, hit_graphic, projectile, projectile_type)
+ @spellbook = spellbook
+ @button = button
+ @level = level
+ @damage = damage
+ @runes = runes
+ @animation = animation
+ @graphic = graphic
+ @hit_graphic = hit_graphic
+ @projectile = projectile
+ @projectile_type = projectile_type
+ end
+
+end
+
+class CombatSpellDSL
+
+ def initialize(&block)
+ instance_eval(&block)
+ end
+
+ def interface(spellbook:, button:)
+ @spellbook = spellbook
+ @button = button
+ end
+
+ def effects(animation: nil, graphic: nil, hit_graphic: nil)
+ @animation = animation
+ @graphic = graphic
+ @hit_graphic = hit_graphic
+ end
+
+ def projectile(projectile:, projectile_type:)
+ @projectile = projectile
+ @projectile_type = projectile_type
+ end
+
+ def level_requirement(level)
+ @level = level
+ end
+
+ def max_damage(damage)
+ @damage = damage
+ end
+
+ def runes(runes = {})
+ @runes = runes
+ end
+
+ def to_combat_spell
+ return CombatSpell.new(@button, @spellbook, @level, @damage, @runes, @animation, @graphic, @hit_graphic, @projectile, @projectile_type)
+ end
+
+end
+
+COMBAT_SPELLS = {}
+
+def create_combat_spell(name, &block)
+ fail 'Block not given' unless block_given?
+
+ combat_spell_dsl = CombatSpellDSL.new(&block)
+
+ COMBAT_SPELLS[name] = combat_spell_dsl.to_combat_spell
+end
+
+def spell_for(spellbook, button)
+
+ COMBAT_SPELLS.each do |name, spell|
+ return spell if spell.spellbook == spellbook && spell.button == button
+ end
+
+ fail "Unable to find a spell in spellbook '#{spellbook}' with button id '#{button}'"
+end
\ No newline at end of file
diff --git a/data/plugins/combat/plugin.xml b/data/plugins/combat/plugin.xml
index 7219e4dd..14aaa044 100644
--- a/data/plugins/combat/plugin.xml
+++ b/data/plugins/combat/plugin.xml
@@ -23,6 +23,13 @@
+
+
+
+
+
+
+
diff --git a/data/plugins/combat/spellbook.rb b/data/plugins/combat/spellbook.rb
new file mode 100644
index 00000000..9c31636a
--- /dev/null
+++ b/data/plugins/combat/spellbook.rb
@@ -0,0 +1,11 @@
+SPELLBOOKS = {}
+
+def create_spellbook(identifier, interface_id:)
+ SPELLBOOKS[interface_id] = identifier
+end
+
+def spellbook_for(interface_id)
+ fail "Could not find spellbook for #{interface_id}" unless SPELLBOOKS.has_key?(interface_id)
+
+ return SPELLBOOKS[interface_id]
+end
\ No newline at end of file
diff --git a/data/plugins/combat/spellbooks.rb b/data/plugins/combat/spellbooks.rb
new file mode 100644
index 00000000..8b41f425
--- /dev/null
+++ b/data/plugins/combat/spellbooks.rb
@@ -0,0 +1,2 @@
+
+create_spellbook :modern, interface_id: 192
\ No newline at end of file
diff --git a/data/plugins/combat/spells/bolts.rb b/data/plugins/combat/spells/bolts.rb
new file mode 100644
index 00000000..10821883
--- /dev/null
+++ b/data/plugins/combat/spells/bolts.rb
@@ -0,0 +1,12 @@
+create_combat_spell :wind_bolt do
+ interface spellbook: :modern, button: 10
+
+ level_requirement 17
+ max_damage 8
+ runes {}
+
+ effects animation: 1162, graphic: {id: 117, height: 100}, hit_graphic: {id: 119, delay: 100}
+ projectile projectile: 118, projectile_type: :bolt_spells
+end
+
+create_projectile_type :bolt_spells, start_height: 46, end_height: 36, delay: 51, speed: 12, slope: 15, radius: 86
\ No newline at end of file