From ec248a185bd9f1928c132bfc48fef838a103ac6b Mon Sep 17 00:00:00 2001 From: Steve Soltys Date: Wed, 27 Jan 2016 12:32:55 -0500 Subject: [PATCH] Add initial combat spell support --- data/plugins/combat/attack.rb | 121 +++++++++++++++++++-------- data/plugins/combat/attributes.rb | 4 + data/plugins/combat/combat.rb | 22 ++++- data/plugins/combat/combat_action.rb | 2 +- data/plugins/combat/combat_spell.rb | 95 +++++++++++++++++++++ data/plugins/combat/plugin.xml | 7 ++ data/plugins/combat/spellbook.rb | 11 +++ data/plugins/combat/spellbooks.rb | 2 + data/plugins/combat/spells/bolts.rb | 12 +++ 9 files changed, 238 insertions(+), 38 deletions(-) create mode 100644 data/plugins/combat/combat_spell.rb create mode 100644 data/plugins/combat/spellbook.rb create mode 100644 data/plugins/combat/spellbooks.rb create mode 100644 data/plugins/combat/spells/bolts.rb 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