mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Add ranged combat support
* Clean up the CombatAction and Attack code to make it easier to use for range. * Add collision detection to the distance checks before attacking in the CombatAction. * Create a Ruby DSL for defining projectile types and fix the ProjectileUpdateOperation so it uses the correct position offset. * Fix the packet structure of the HintIconMessageEncoder.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
AMMO = {}
|
||||
|
||||
def create_ammo(item_matcher, properties, &block)
|
||||
items = find_entities :item, item_matcher, -1
|
||||
fail "Unable to find ammo matching #{item_matcher}" unless items.size > 0
|
||||
|
||||
projectile_type = properties[:projectile_type]
|
||||
fail "Unable to find projectile type #{projectile_type}" unless PROJECTILE_TYPES.key? projectile_type
|
||||
|
||||
properties[:projectile_type] = PROJECTILE_TYPES[projectile_type]
|
||||
|
||||
items.each do |item_id|
|
||||
AMMO[item_id] = Ammo.new(item_id, properties)
|
||||
AMMO[item_id].instance_eval &block if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Ammo
|
||||
attr_reader :item, :weapon_classes, :projectile, :projectile_type, :level_requirement, :drop_rate, :graphic
|
||||
|
||||
include Combat::BonusContainer
|
||||
|
||||
def initialize(item, weapon_classes:, projectile:, projectile_type:, level_requirement:, drop_rate:, graphic: nil)
|
||||
@item = item
|
||||
@weapon_classes = weapon_classes
|
||||
@projectile = projectile
|
||||
@projectile_type = projectile_type
|
||||
@level_requirement = level_requirement
|
||||
@drop_rate = drop_rate
|
||||
@graphic = graphic
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
def create_arrow(item, hash)
|
||||
hash[:projectile_type] = :arrow
|
||||
hash[:weapon_classes] = [:longbow, :shortbow]
|
||||
|
||||
create_ammo item, hash
|
||||
end
|
||||
|
||||
create_projectile_type :arrow, start_height: 41, end_height: 37, delay: 41, speed: 6, slope: 15, radius: 10
|
||||
|
||||
create_arrow :bronze_arrow, level_requirement: 1, projectile: 10, graphic: 19, drop_rate: 0.3
|
||||
create_arrow :iron_arrow, level_requirement: 1, projectile: 9, graphic: 18, drop_rate: 0.35
|
||||
create_arrow :steel_arrow, level_requirement: 5, projectile: 11, graphic: 20, drop_rate: 0.4
|
||||
create_arrow :mithril_arrow, level_requirement: 20, projectile: 12, graphic: 21, drop_rate: 0.45
|
||||
create_arrow :adamant_arrow, level_requirement: 30, projectile: 13, graphic: 22, drop_rate: 0.5
|
||||
create_arrow :rune_arrow, level_requirement: 40, projectile: 15, graphic: 24, drop_rate: 0.6
|
||||
@@ -0,0 +1,10 @@
|
||||
def create_bolt(item, hash)
|
||||
hash[:projectile_type] = :bolt
|
||||
hash[:weapon_classes] = [:crossbow]
|
||||
hash[:projectile] = 27 unless hash.key? :projectile
|
||||
create_ammo item, hash
|
||||
end
|
||||
|
||||
create_projectile_type :bolt, start_height: 44, end_height: 44, delay: 41, speed: 5, slope: 5, radius: 10
|
||||
|
||||
create_bolt :iron_bolts, level_requirement: 1, drop_rate: 0.35
|
||||
+150
-42
@@ -3,9 +3,13 @@ java_import 'org.apollo.game.model.Animation'
|
||||
java_import 'org.apollo.game.model.Graphic'
|
||||
|
||||
class BaseAttack
|
||||
attr_reader :requirements, :range
|
||||
attr_reader :requirements, :range, :speed
|
||||
|
||||
def initialize(animation, graphic = nil, range = 1, requirements = [])
|
||||
def initialize(speed:, animation:, graphic: nil, range: 1, requirements: [])
|
||||
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
|
||||
@@ -17,7 +21,7 @@ class BaseAttack
|
||||
|
||||
unless @graphic.nil?
|
||||
if @graphic.is_a?(Hash)
|
||||
source.play_graphic(Graphic.new(@graphic[:id], @graphic[:delay] || 0, @graphic[:height] | 0))
|
||||
source.play_graphic(Graphic.new(@graphic[:id], @graphic[:delay] || 0, @graphic[:height] || 0))
|
||||
else
|
||||
source.play_graphic(Graphic.new(@graphic))
|
||||
end
|
||||
@@ -26,63 +30,167 @@ class BaseAttack
|
||||
apply(source, target)
|
||||
end
|
||||
|
||||
def apply(source, target)
|
||||
raise "BaseAttack#apply unimplemented"
|
||||
end
|
||||
|
||||
def damage!(source, target, amount, delay = 0)
|
||||
schedule delay do |task|
|
||||
task.stop && return if source.dead or target.dead
|
||||
|
||||
target_combat_state = get_combat_state target
|
||||
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.state == :attacking
|
||||
target.damage(amount, type, false)
|
||||
|
||||
task.stop
|
||||
end
|
||||
def apply(_source, _target)
|
||||
fail 'BaseAttack#apply unimplemented'
|
||||
end
|
||||
end
|
||||
|
||||
class ProcAttack < BaseAttack
|
||||
def initialize(block, animation:, graphic:, range: 1, requirements: [])
|
||||
super(animation, graphic, range, requirements)
|
||||
def initialize(block, hash)
|
||||
super(hash)
|
||||
|
||||
@block = block
|
||||
end
|
||||
|
||||
def apply(source, target)
|
||||
self.instance_exec(source, target, &@block)
|
||||
instance_exec(source, target, &@block)
|
||||
end
|
||||
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.
|
||||
|
||||
class RangedAttack < BaseAttack
|
||||
def initialize(animation:, graphic:, requirements: [])
|
||||
|
||||
end
|
||||
|
||||
def calculate_delay(source, target)
|
||||
|
||||
end
|
||||
|
||||
def apply(source, target)
|
||||
|
||||
end
|
||||
|
||||
def projectile!(source, target, projectile_id)
|
||||
|
||||
do_ranged_damage! source, target
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# A basic melee attack, which deals damage a tick after the attack was made.
|
||||
|
||||
class Attack < BaseAttack
|
||||
def initialize(animation:, graphic: nil, range: 1, requirements: [])
|
||||
super(animation, graphic, range, requirements)
|
||||
end
|
||||
|
||||
def apply(source, target)
|
||||
damage! source, target, CombatUtil::calculate_hit(source, target)
|
||||
do_damage! source, target, CombatUtil.calculate_hit(source, target)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# A DSL-like builder object for creating a new Attack, used mainly (exclusively?) for special attacks.
|
||||
|
||||
class AttackDSL
|
||||
attr_accessor :animation, :speed, :range, :graphic
|
||||
|
||||
def initialize()
|
||||
@requirements = []
|
||||
@subattacks = []
|
||||
end
|
||||
|
||||
def add_requirement(requirement)
|
||||
@requirements.push requirement
|
||||
end
|
||||
|
||||
##
|
||||
# Deal ranged damage.
|
||||
|
||||
def range_damage!(hash)
|
||||
@subattacks.push lambda { |source, target|
|
||||
do_ranged_damage! source, target, hash
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Deal melee damage.
|
||||
|
||||
def damage!(damage_modifier: 1, delay: 0)
|
||||
@subattacks.push lambda { |source, target|
|
||||
do_damage! source, target, 1, delay
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
# Process the attack instructions in this DSL then build and return an {@link Attack} object.
|
||||
|
||||
def to_attack
|
||||
subattacks = @subattacks
|
||||
|
||||
attack = lambda do |source, target|
|
||||
subattacks.each do |subattack|
|
||||
subattack[source, target]
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: clean up? Decide where keyword arguments are appropriate and where not
|
||||
ProcAttack.new(attack, speed: speed, animation: animation, graphic: graphic, range: range, requirements: @requirements)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# TODO: refactor
|
||||
def do_damage!(source, target, amount, delay = 0, secondary = false, &_block)
|
||||
schedule delay do |task|
|
||||
task.stop && return if source.dead || target.dead
|
||||
|
||||
target_combat_state = get_combat_state target
|
||||
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
|
||||
|
||||
task.stop
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# TODO: refactor
|
||||
|
||||
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)
|
||||
|
||||
ammo = EquipmentUtil.equipped_ammo source
|
||||
|
||||
if projectile.nil? && projectile_type.nil?
|
||||
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
|
||||
|
||||
unless projectile_graphic.nil?
|
||||
source.play_graphic Graphic.new(projectile_graphic, 0, 100)
|
||||
end
|
||||
end
|
||||
|
||||
if delay == 0
|
||||
apply.call
|
||||
else
|
||||
# account for the one tick delay in the scheduler
|
||||
schedule(delay - 1) do |task|
|
||||
apply.call
|
||||
task.stop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# TODO: refactor
|
||||
|
||||
def projectile!(source, target, projectile_id, projectile_type, speed_modifier = 1)
|
||||
distance = source.position.get_distance(target.position)
|
||||
|
||||
projectile_parameters = {
|
||||
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
|
||||
}
|
||||
|
||||
ProjectileModule.fire_projectile(source.position, target.position, projectile_id, projectile_parameters, target)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||
java_import 'org.apollo.game.model.entity.EquipmentConstants'
|
||||
java_import 'org.apollo.cache.def.EquipmentDefinition'
|
||||
|
||||
class AttackRequirementException < Exception
|
||||
attr_reader :message
|
||||
@@ -9,11 +11,11 @@ class AttackRequirementException < Exception
|
||||
end
|
||||
|
||||
class AttackRequirement
|
||||
def validate!(player)
|
||||
def validate(_player)
|
||||
throw RuntimeError.new('validate! not implemented')
|
||||
end
|
||||
|
||||
def apply(player)
|
||||
def apply!(_player)
|
||||
throw RuntimeError.new('apply not implemented')
|
||||
end
|
||||
end
|
||||
@@ -23,19 +25,19 @@ class SpecialEnergyRequirement < AttackRequirement
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def validate!(player)
|
||||
if player.special_energy < @amount
|
||||
def validate(player)
|
||||
if false
|
||||
player.using_special = false
|
||||
|
||||
|
||||
update_special_bar(player)
|
||||
raise AttackRequirementException.new('Not enough special attack energy.')
|
||||
fail AttackRequirementException.new('Not enough special attack energy.')
|
||||
end
|
||||
end
|
||||
|
||||
def apply(player)
|
||||
def apply!(player)
|
||||
player.special_energy = player.special_energy - @amount
|
||||
player.using_special = false
|
||||
|
||||
player.using_special = false
|
||||
|
||||
update_special_bar player
|
||||
end
|
||||
end
|
||||
@@ -46,11 +48,11 @@ class ItemRequirement < AttackRequirement
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def validate!(player)
|
||||
def validate(player)
|
||||
throw AttackRequirementException.new(item_missing_message) unless player.inventory.get_amount(@item) >= @amount
|
||||
end
|
||||
|
||||
def apply(player)
|
||||
def apply!(player)
|
||||
player.inventory.remove(@item, @amount)
|
||||
end
|
||||
|
||||
@@ -62,3 +64,39 @@ class ItemRequirement < AttackRequirement
|
||||
"You don't have enough #{lookup_item(@item).name}s"
|
||||
end
|
||||
end
|
||||
|
||||
class AmmoRequirement < AttackRequirement
|
||||
def initialize(amount = 1)
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def validate(player)
|
||||
equipped_weapon_item = player.equipment.get(EquipmentConstants::WEAPON)
|
||||
equipped_weapon_def = EquipmentDefinition.lookup(equipped_weapon_item.id)
|
||||
equipped_weapon = EquipmentUtil.equipped_weapon player
|
||||
equipped_ammo = EquipmentUtil.equipped_ammo player
|
||||
equipped_ammo_amt = player.equipment.get(EquipmentConstants::AMMO).amount
|
||||
|
||||
if equipped_ammo.nil?
|
||||
fail AttackRequirementException.new('You have no ammo left in your quiver!')
|
||||
end
|
||||
|
||||
if @amount > 1 && equipped_ammo_amt < @amount
|
||||
fail AttackRequirementException.new('You don\'t have enough ammo left in your quiver!')
|
||||
end
|
||||
|
||||
unless equipped_ammo.weapon_classes.include? equipped_weapon.weapon_class.name
|
||||
fail AttackRequirementException.new('You can\'t use this ammo with this weapon.')
|
||||
end
|
||||
|
||||
if equipped_ammo.level_requirement > equipped_weapon_def.ranged_level
|
||||
fail AttackRequirementException.new('You can\'t use this ammo with this weapon.')
|
||||
end
|
||||
end
|
||||
|
||||
def apply!(player)
|
||||
equipped_ammo = EquipmentUtil.equipped_ammo player
|
||||
|
||||
player.equipment.remove equipped_ammo.item
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
##
|
||||
# The delay a <i>Mob</i> must wait before attacking again.
|
||||
declare_attribute(:attack_delay, 0)
|
||||
# The number of ticks since a <i>Mob<i>s last attack.
|
||||
declare_attribute(:attack_timer, 100)
|
||||
|
||||
##
|
||||
# A flag indicating whether this <i>Mob</i> is currently in combat.
|
||||
@@ -22,6 +22,10 @@ declare_attribute(:combat_style, 0, :persistent)
|
||||
# A flag indicating whether the special bar is flagged for the next attack.
|
||||
declare_attribute(:using_special, false, :persistent)
|
||||
|
||||
##
|
||||
# A flag indicating whether auto retaliation is enabled.
|
||||
declare_attribute(:auto_retaliate, true, :persistent)
|
||||
|
||||
##
|
||||
# An integer between 0 and 100 indicating the amount of special energy a <i>Player</i> has.
|
||||
declare_attribute(:special_energy, 100, :persistent)
|
||||
|
||||
@@ -15,30 +15,30 @@ module Combat
|
||||
|
||||
def other_bonuses(melee_strength: 0, ranged_strength: 0, prayer: 0)
|
||||
@other_bonuses = {
|
||||
:melee_strength => melee_strength,
|
||||
:ranged_strength => ranged_strength,
|
||||
:prayer => prayer
|
||||
melee_strength: melee_strength,
|
||||
ranged_strength: ranged_strength,
|
||||
prayer: prayer
|
||||
}
|
||||
end
|
||||
|
||||
def defence_bonuses(stab: 0, slash: 0, crush: 0, magic: 0, range: 0)
|
||||
@defence_bonuses = {
|
||||
:stab => stab,
|
||||
:slash => slash,
|
||||
:crush => crush,
|
||||
:magic => magic,
|
||||
:range => range
|
||||
stab: stab,
|
||||
slash: slash,
|
||||
crush: crush,
|
||||
magic: magic,
|
||||
range: range
|
||||
}
|
||||
end
|
||||
|
||||
def attack_bonuses(stab: 0, slash: 0, crush: 0, magic: 0, range: 0)
|
||||
@attack_bonuses = {
|
||||
:stab => stab,
|
||||
:slash => slash,
|
||||
:crush => crush,
|
||||
:magic => magic,
|
||||
:range => range
|
||||
stab: stab,
|
||||
slash: slash,
|
||||
crush: crush,
|
||||
magic: magic,
|
||||
range: range
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
on :message, :npc_action do |player, message|
|
||||
player_combat_state = get_combat_state player
|
||||
player_combat_state.target = $world.npc_repository.get message.index
|
||||
java_import 'org.apollo.game.message.impl.HintIconMessage'
|
||||
|
||||
unless player.attacking
|
||||
player.start_action CombatAction.new(player)
|
||||
end
|
||||
on :message, :npc_action do |player, message|
|
||||
target = $world.npc_repository.get message.index
|
||||
|
||||
# unless target.attacking
|
||||
# target_combat_state = get_combat_state target
|
||||
# target_combat_state.target = player
|
||||
#
|
||||
# target.start_action CombatAction.new(target)
|
||||
# end
|
||||
|
||||
player_combat_state = get_combat_state player
|
||||
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?
|
||||
end
|
||||
|
||||
on :message, :player_action do |player, message|
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
schedule 0 do |_task|
|
||||
$world.player_repository.each { |player| player.attack_timer = player.attack_timer + 1 }
|
||||
$world.npc_repository.each { |npc| npc.attack_timer = npc.attack_timer + 1 }
|
||||
end
|
||||
|
||||
@@ -1,122 +1,66 @@
|
||||
java_import 'org.apollo.game.action.Action'
|
||||
|
||||
# Represents a one off {@code Attack}, which will not continue a combat session
|
||||
# upon completion.
|
||||
class AttackAction < Action
|
||||
|
||||
def initialize(source, target, delay, attack)
|
||||
@source = source
|
||||
@target = target
|
||||
@delay = delay
|
||||
@attack = attack
|
||||
end
|
||||
|
||||
def execute
|
||||
begin
|
||||
@attack.do(mob, @target)
|
||||
|
||||
stop
|
||||
rescue AttackRequirementException => e
|
||||
player.send_message e.message
|
||||
stop
|
||||
end
|
||||
end
|
||||
end
|
||||
java_import 'org.apollo.game.model.entity.EntityType'
|
||||
|
||||
class CombatAction < Action
|
||||
def initialize(source, attack = nil)
|
||||
def initialize(source, once = false)
|
||||
super(0, true, source)
|
||||
|
||||
mob.attacking = true
|
||||
|
||||
@combat_state = get_combat_state(source)
|
||||
@attack = attack
|
||||
@attack_timer = 100
|
||||
@once = once
|
||||
end
|
||||
|
||||
def can_attack?
|
||||
next_attack = @combat_state.next_attack true
|
||||
target = @combat_state.target
|
||||
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)
|
||||
|
||||
unless in_range
|
||||
mob.follow @combat_state.target, next_attack.range
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
mob.attack_timer >= next_attack.speed
|
||||
end
|
||||
|
||||
def try_attack
|
||||
next_attack = @combat_state.next_attack false
|
||||
|
||||
if mob.is_a? Player
|
||||
next_attack.requirements.each { |requirement| requirement.validate mob }
|
||||
next_attack.requirements.each { |requirement| requirement.apply! mob }
|
||||
end
|
||||
|
||||
next_attack.do(mob, @combat_state.target)
|
||||
rescue AttackRequirementException => e
|
||||
mob.send_message e.message
|
||||
ensure
|
||||
mob.attack_timer = 0
|
||||
end
|
||||
|
||||
def execute
|
||||
if @combat_state.target.nil? and @combat_state.queued_attacks.empty?
|
||||
@combat_state.reset
|
||||
if @combat_state.target.nil? || !@combat_state.target.is_active || @combat_state.next_attack(true).nil?
|
||||
stop
|
||||
end
|
||||
|
||||
case @combat_state.state
|
||||
when :idle
|
||||
update_idle
|
||||
when :attacking
|
||||
update_attacking
|
||||
when :chasing
|
||||
update_chasing
|
||||
else
|
||||
throw ArgumentError.new('invalid combat state')
|
||||
end
|
||||
end
|
||||
|
||||
def update_idle
|
||||
next_attack = @combat_state.next_attack true
|
||||
current_distance = mob.position.get_distance @combat_state.target.position
|
||||
|
||||
if current_distance > next_attack.range
|
||||
@combat_state.state = :chasing
|
||||
|
||||
mob.follow @combat_state.target, next_attack.range
|
||||
set_delay 0
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if @combat_state.supports_weapon
|
||||
weapon = EquipmentUtil.equipped_weapon mob
|
||||
weapon_class = weapon.weapon_class
|
||||
combat_style = weapon_class.style_at mob.combat_style
|
||||
return unless can_attack?
|
||||
|
||||
if mob.attacking
|
||||
set_delay weapon_class.speed(combat_style) - 1
|
||||
else
|
||||
set_delay 0
|
||||
mob.attacking = true
|
||||
end
|
||||
|
||||
@combat_state.state = :attacking
|
||||
else
|
||||
stop
|
||||
@combat_state.reset
|
||||
end
|
||||
end
|
||||
|
||||
def update_attacking
|
||||
begin
|
||||
next_attack = @combat_state.next_attack
|
||||
next_attack.requirements.each do |requirement|
|
||||
requirement.validate! mob
|
||||
end
|
||||
|
||||
next_attack.requirements.each do |requirement|
|
||||
requirement.apply mob
|
||||
end
|
||||
|
||||
next_attack.do(mob, @combat_state.target)
|
||||
rescue AttackRequirementException => e
|
||||
mob.send_message e.message
|
||||
ensure
|
||||
@combat_state.state = :idle
|
||||
set_delay 0
|
||||
end
|
||||
end
|
||||
|
||||
def update_chasing
|
||||
next_attack = @combat_state.next_attack true
|
||||
current_distance = mob.position.get_distance @combat_state.target.position
|
||||
|
||||
if current_distance <= next_attack.range
|
||||
@combat_state.state = :attacking
|
||||
|
||||
mob.following = -1
|
||||
set_delay 0
|
||||
end
|
||||
try_attack
|
||||
stop if @once
|
||||
end
|
||||
|
||||
def stop
|
||||
super
|
||||
|
||||
@combat_state.reset
|
||||
mob.attacking = false
|
||||
mob.following = -1
|
||||
@combat_state.reset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
def get_combat_state(mob)
|
||||
mob.is_a?(Player) ? type = :player : type = :npc
|
||||
|
||||
unless MOB_COMBAT_STATE_CACHE[type].has_key? mob.index
|
||||
unless MOB_COMBAT_STATE_CACHE[type].key? mob.index
|
||||
MOB_COMBAT_STATE_CACHE[type][mob.index] = CombatState.new(mob)
|
||||
end
|
||||
|
||||
@@ -11,8 +11,7 @@ end
|
||||
private
|
||||
|
||||
class CombatState
|
||||
attr_accessor :state
|
||||
attr_reader :queued_attacks, :supports_weapon
|
||||
attr_reader :target
|
||||
|
||||
def initialize(mob, supports_weapon = true)
|
||||
@mob = mob
|
||||
@@ -21,20 +20,22 @@ class CombatState
|
||||
end
|
||||
|
||||
def reset
|
||||
@state = :idle
|
||||
@target = nil
|
||||
@next_attack = nil
|
||||
@queued_attacks = []
|
||||
@mob.reset_interacting_mob
|
||||
end
|
||||
|
||||
def target
|
||||
@target
|
||||
def will_attack?
|
||||
is_attacking? && next_attack(true).speed >= @mob.attack_timer
|
||||
end
|
||||
|
||||
def is_attacking?
|
||||
!target.nil? && @mob.attacking
|
||||
end
|
||||
|
||||
def target=(target)
|
||||
@mob.reset_interacting_mob
|
||||
@mob.interacting_mob = target
|
||||
@mob.interacting_mob = target unless target.nil?
|
||||
@target = target
|
||||
end
|
||||
|
||||
@@ -44,23 +45,25 @@ class CombatState
|
||||
|
||||
def next_attack(peek = false)
|
||||
if @queued_attacks.size > 0
|
||||
peek ? @queued_attacks.first : @queued_attacks.pop
|
||||
peek ? @queued_attacks.last : @queued_attacks.pop
|
||||
else
|
||||
weapon = EquipmentUtil.equipped_weapon @mob
|
||||
|
||||
if @mob.using_special and weapon.special_attack?
|
||||
if @mob.using_special && weapon.special_attack?
|
||||
weapon.special_attack
|
||||
else
|
||||
weapon_class = weapon.weapon_class
|
||||
combat_style = weapon_class.style_at @mob.combat_style
|
||||
|
||||
weapon_class.attack combat_style
|
||||
combat_style = weapon.weapon_class.selected_style @mob
|
||||
combat_style.attack
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
MOB_COMBAT_STATE_CACHE = {
|
||||
:player => {},
|
||||
:npc => {}
|
||||
player: {},
|
||||
npc: {}
|
||||
}
|
||||
|
||||
on :logout do |event|
|
||||
MOB_COMBAT_STATE_CACHE[:player].delete event.player.index
|
||||
end
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
class CombatStyle
|
||||
attr_reader :button, :config, :attack_type, :block_animation
|
||||
attr_accessor :attack
|
||||
|
||||
def initialize(button, config, attack_type, block_animation)
|
||||
@button = button
|
||||
@config = config
|
||||
@attack_type = attack_type
|
||||
@block_animation = block_animation
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,7 @@ def create_equipment(item, &block)
|
||||
equipment = Equipment.new
|
||||
equipment.instance_eval block
|
||||
|
||||
find_entities :item, item do |equipment_item|
|
||||
find_entities :item, item do |_equipment_item|
|
||||
EQUIPMENT[id] = equipment
|
||||
end
|
||||
end
|
||||
@@ -15,4 +15,4 @@ private
|
||||
|
||||
class Equipment
|
||||
include Combat::BonusContainer
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
<description>Adds fully functioning melee, ranged and magic combat.</description>
|
||||
<authors>
|
||||
<author>garyttierney</author>
|
||||
<author>Shiver</author>
|
||||
<author>Major</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<!-- Include the BonusContainer mixin first, as its used throughout -->
|
||||
<script>ammo.rb</script>
|
||||
<script>attack.rb</script>
|
||||
<script>attack_requirements.rb</script>
|
||||
<script>attributes.rb</script>
|
||||
@@ -15,9 +19,10 @@
|
||||
<script>combat.rb</script>
|
||||
<script>combat_action.rb</script>
|
||||
<script>combat_state.rb</script>
|
||||
<script>combat_style.rb</script>
|
||||
<script>equipment.rb</script>
|
||||
<script>projectile.rb</script>
|
||||
<script>util.rb</script>
|
||||
<script>projectile_type.rb</script>
|
||||
<script>weapon.rb</script>
|
||||
<script>weapon_class.rb</script>
|
||||
<script>weapons/bows.rb</script>
|
||||
@@ -25,12 +30,18 @@
|
||||
<script>weapons/scimitars.rb</script>
|
||||
<script>weapons/swords.rb</script>
|
||||
<script>weapons/two_handed_swords.rb</script>
|
||||
<script>ammo/arrows.rb</script>
|
||||
<script>ammo/bolts.rb</script>
|
||||
<script>weapons/ranged/bows.rb</script>
|
||||
<script>weapons/ranged/magic_bows.rb</script>
|
||||
<script>weapons/ranged/crossbows.rb</script>
|
||||
<script>weapons/melee/daggers.rb</script>
|
||||
<script>weapons/melee/scimitars.rb</script>
|
||||
<script>weapons/melee/swords.rb</script>
|
||||
<script>weapons/melee/two_handed_swords.rb</script>
|
||||
<script>weapons/unarmed.rb</script>
|
||||
<script>widgets/combat_tab.rb</script>
|
||||
<script>widgets/special_bar.rb</script>
|
||||
<script>widgets/ancient_magics_tab.rb</script>
|
||||
<script>widgets/regular_magics_tab.rb</script>
|
||||
<script>widgets/lunar_magics_tab.rb</script>
|
||||
</scripts>
|
||||
<dependencies>
|
||||
<dependency>attributes</dependency>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
def create_projectile(item, drop_rate:, graphic:, projectile:)
|
||||
items = i
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
PROJECTILE_TYPES = {}
|
||||
|
||||
def create_projectile_type(name, start_height:, end_height:, delay:, speed:, slope:, radius:)
|
||||
PROJECTILE_TYPES[name] = ProjectileType.new(start_height, end_height, delay, speed, slope, radius)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class ProjectileType
|
||||
attr_reader :start_height, :end_height, :delay, :speed, :slope, :radius
|
||||
|
||||
def initialize(start_height, end_height, delay, speed, slope, radius)
|
||||
@start_height = start_height
|
||||
@end_height = end_height
|
||||
@delay = delay
|
||||
@speed = speed
|
||||
@slope = slope
|
||||
@radius = radius
|
||||
end
|
||||
end
|
||||
+16
-12
@@ -1,12 +1,14 @@
|
||||
java_import 'org.apollo.game.model.entity.EquipmentConstants'
|
||||
|
||||
class CombatUtil
|
||||
def self.current_speed(_mob)
|
||||
end
|
||||
|
||||
def self.calculate_max_hit(source)
|
||||
strength = source.skill_set.get_skill(Skill::STRENGTH)
|
||||
strength_stat = 5 #source.bonus_stat(:other, :strength)
|
||||
strength_stat = 5 # source.bonus_stat(:other, :strength)
|
||||
|
||||
effective_strength_damage = (strength.current_level) #* prayer_multiplier
|
||||
effective_strength_damage = (strength.current_level) # * prayer_multiplier
|
||||
|
||||
if [:aggressive, :alt_aggressive].include? source.combat_style
|
||||
effective_strength_damage += 3
|
||||
@@ -19,8 +21,8 @@ class CombatUtil
|
||||
def self.calculate_accuracy(source, target)
|
||||
weapon = EquipmentUtil.equipped_weapon source
|
||||
weapon_class = weapon.weapon_class
|
||||
combat_style = weapon_class.style_at source.combat_style
|
||||
attack_type = weapon.weapon_class.attack_type combat_style
|
||||
combat_style = weapon_class.selected_style source
|
||||
attack_type = combat_style.attack_type
|
||||
|
||||
attack_stat = [1, 1].max
|
||||
defence_stat = [1, 1].max
|
||||
@@ -28,11 +30,11 @@ class CombatUtil
|
||||
attack = source.skill_set.get_skill(Skill::ATTACK).current_level.to_f
|
||||
defence = target.skill_set.get_skill(Skill::DEFENCE).current_level.to_f
|
||||
|
||||
attack_prayer_multiplier = 1 #TODO: Prayer
|
||||
attack_prayer_multiplier = 1 # TODO: Prayer
|
||||
attack_accuracy = attack_stat * attack * attack_prayer_multiplier
|
||||
attack_accuracy = 0.1 if attack_accuracy < 0
|
||||
|
||||
defence_prayer_multiplier = 1 #TODO: Prayer
|
||||
defence_prayer_multiplier = 1 # TODO: Prayer
|
||||
defence_accuracy = defence_stat * defence * defence_prayer_multiplier
|
||||
defence_accuracy = 1 if defence_accuracy < 0
|
||||
|
||||
@@ -42,7 +44,7 @@ class CombatUtil
|
||||
|
||||
# Calculates a hit for the given <i>Mob</i> and special attack flag.
|
||||
def self.calculate_hit(source, target)
|
||||
accuracy = calculate_accuracy source, target
|
||||
accuracy = calculate_accuracy(source, target)
|
||||
max_hit = calculate_max_hit(source) + 1
|
||||
|
||||
if rand <= accuracy
|
||||
@@ -57,14 +59,16 @@ class EquipmentUtil
|
||||
def self.equipped_weapon(source)
|
||||
item = source.equipment.get(EquipmentConstants::WEAPON)
|
||||
|
||||
if item.nil?
|
||||
return NAMED_WEAPONS[:unarmed]
|
||||
end
|
||||
return NAMED_WEAPONS[:unarmed] if item.nil?
|
||||
|
||||
WEAPONS[item.id]
|
||||
end
|
||||
|
||||
def self.equipped_projectile(source)
|
||||
def self.equipped_ammo(source)
|
||||
item = source.equipment.get(EquipmentConstants::ARROWS)
|
||||
|
||||
return nil if item.nil?
|
||||
|
||||
AMMO[item.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||
java_import 'org.apollo.cache.def.EquipmentDefinition'
|
||||
java_import 'org.apollo.game.model.inv.InventoryAdapter'
|
||||
java_import 'org.apollo.game.model.entity.EquipmentConstants'
|
||||
java_import 'org.apollo.game.model.entity.AnimationSet'
|
||||
@@ -16,41 +17,52 @@ def create_weapon(identifier, class_name = nil, named: false, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def create_normal_weapon(item_matcher, class_name = nil, &block)
|
||||
items = find_entities :item, item_matcher, -1
|
||||
fail "Unable to find weapon matching #{item_matcher}" if items.empty?
|
||||
|
||||
items.each do |item_id|
|
||||
equipment_def = EquipmentDefinition.lookup(item_id)
|
||||
|
||||
next if equipment_def.nil? || equipment_def.slot != EquipmentConstants::WEAPON
|
||||
|
||||
definition = ItemDefinition.lookup(item_id)
|
||||
definition_name = definition.name.downcase.to_s
|
||||
|
||||
|
||||
if class_name.nil?
|
||||
class_name =
|
||||
case definition_name
|
||||
when /[a-zA-Z]+ 2h sword/
|
||||
:two_handed_sword
|
||||
when /[a-zA-Z]+ scimitar/
|
||||
:scimitar
|
||||
when /[a-zA-Z]+ dagger/
|
||||
:dagger
|
||||
else
|
||||
raise "Couldn't find a suitable weapon class for the given weapon."
|
||||
when /[a-zA-Z]+ 2h sword/
|
||||
:two_handed_sword
|
||||
when /[a-zA-Z]+ scimitar/
|
||||
:scimitar
|
||||
when /[a-zA-Z]+ dagger/
|
||||
:dagger
|
||||
when /[a-z\s]*longbow/
|
||||
:longbow
|
||||
when /[a-z\s]*shortbow/
|
||||
:shortbow
|
||||
when /[a-z]+ c'bow/
|
||||
:crossbow
|
||||
else
|
||||
fail "Couldn't find a suitable weapon class for the given weapon."
|
||||
end
|
||||
end
|
||||
|
||||
WEAPONS[item_id] = Weapon.new(definition.name, WEAPON_CLASSES[class_name])
|
||||
WEAPONS[item_id].instance_eval &block
|
||||
WEAPONS[item_id].instance_eval &block if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
def create_named_weapon(name, class_name, &block)
|
||||
NAMED_WEAPONS[name] = Weapon.new name.to_s.capitalize, WEAPON_CLASSES[class_name]
|
||||
NAMED_WEAPONS[name].instance_eval &block
|
||||
NAMED_WEAPONS[name].instance_eval &block if block_given?
|
||||
end
|
||||
|
||||
# Represents an equippable weapon, and the class it belongs to.
|
||||
#
|
||||
#
|
||||
# * has an optional special_attack
|
||||
# * belongs to a certain WeaponClass, and inherits bonuses from it.
|
||||
class Weapon
|
||||
@@ -65,18 +77,22 @@ class Weapon
|
||||
end
|
||||
|
||||
def special_attack?
|
||||
not special_attack.nil?
|
||||
!special_attack.nil?
|
||||
end
|
||||
|
||||
def set_special_attack(energy_requirement:, animation:, graphic: nil, &block)
|
||||
# todo figure out if ranged or melee
|
||||
def set_special_attack(speed:, range: 1, energy_requirement:, animation:, graphic: nil, &block)
|
||||
attack_dsl = AttackDSL.new
|
||||
attack_dsl.speed = speed
|
||||
attack_dsl.range = range
|
||||
attack_dsl.animation = animation
|
||||
attack_dsl.graphic = graphic
|
||||
attack_dsl.add_requirement SpecialEnergyRequirement.new(energy_requirement)
|
||||
attack_dsl.instance_eval &block
|
||||
|
||||
requirements = [SpecialEnergyRequirement.new(energy_requirement)]
|
||||
@special_attack = ProcAttack.new(block, animation: animation, graphic: graphic, requirements: requirements)
|
||||
@special_attack = attack_dsl.to_attack
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def update_weapon_animations(player)
|
||||
default_animations = AnimationSet::DEFAULT_ANIMATION_SET
|
||||
player_animations = player.animation_set
|
||||
@@ -93,33 +109,33 @@ def update_weapon_animations(player)
|
||||
weapon_class = weapon.weapon_class
|
||||
|
||||
[:stand, :walk, :run, :idle_turn, :turn_around, :turn_left, :turn_right].each do |key|
|
||||
animation = weapon_class.other_animation(key)
|
||||
animation = weapon_class.animation(key)
|
||||
|
||||
unless animation.nil?
|
||||
case key
|
||||
when :stand
|
||||
player_animations.stand = animation
|
||||
when :walk
|
||||
player_animations.walking = animation
|
||||
when :run
|
||||
player_animations.running = animation
|
||||
when :idle_turn
|
||||
player_animations.idle_turn = animation
|
||||
when :turn_around
|
||||
player_animations.turn_around = animation
|
||||
when :turn_left
|
||||
player_animations.turn_left = animation
|
||||
when :turn_right
|
||||
player_animations.turn_right = animation
|
||||
else
|
||||
# type code here
|
||||
end
|
||||
next if animation.nil?
|
||||
case key
|
||||
when :stand
|
||||
player_animations.stand = animation
|
||||
when :walk
|
||||
player_animations.walking = animation
|
||||
when :run
|
||||
player_animations.running = animation
|
||||
when :idle_turn
|
||||
player_animations.idle_turn = animation
|
||||
when :turn_around
|
||||
player_animations.turn_around = animation
|
||||
when :turn_left
|
||||
player_animations.turn_left = animation
|
||||
when :turn_right
|
||||
player_animations.turn_right = animation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
on :message, :item_option do |player, message|
|
||||
update_weapon_animations(player) if message.option == 2 and message.interface_id == SynchronizationInventoryListener::INVENTORY_ID
|
||||
next unless message.interface_id == SynchronizationInventoryListener::INVENTORY_ID &&
|
||||
message.option == 2
|
||||
|
||||
update_weapon_animations(player)
|
||||
end
|
||||
|
||||
on :login do |event|
|
||||
@@ -127,5 +143,8 @@ on :login do |event|
|
||||
end
|
||||
|
||||
on :message, :item_action do |player, message|
|
||||
update_weapon_animations(player) if message.interface_id == SynchronizationInventoryListener::EQUIPMENT_ID and message.slot == EquipmentConstants::WEAPON
|
||||
next unless message.interface_id == SynchronizationInventoryListener::EQUIPMENT_ID &&
|
||||
message.slot == EquipmentConstants::WEAPON
|
||||
|
||||
update_weapon_animations(player)
|
||||
end
|
||||
|
||||
@@ -4,122 +4,169 @@ COMBAT_STYLES = [
|
||||
:defensive,
|
||||
:controlled,
|
||||
:alt_aggressive,
|
||||
:accurate_ranged,
|
||||
:rapid,
|
||||
:long_range
|
||||
]
|
||||
|
||||
MELEE_COMBAT_STYLES = [
|
||||
:accurate,
|
||||
:aggressive,
|
||||
:alt_aggressive,
|
||||
:controlled,
|
||||
:defensive
|
||||
]
|
||||
|
||||
RANGE_COMBAT_STYLES = [
|
||||
:accurate_ranged,
|
||||
:rapid,
|
||||
:long_range
|
||||
]
|
||||
##
|
||||
# A model for a weapon class, which contains the necessary inform
|
||||
|
||||
class WeaponClass
|
||||
attr_reader :widget, :name, :special_bar_button, :special_bar_config
|
||||
##
|
||||
# Allow this class to set combat bonuses.
|
||||
|
||||
include Combat::BonusContainer
|
||||
|
||||
def initialize(name, widget)
|
||||
@name = name
|
||||
@widget = widget
|
||||
@styles = {}
|
||||
@style_attacks = {}
|
||||
@style_offsets = []
|
||||
@animations = {}
|
||||
##
|
||||
# The widget which is displayed on the combat tab when this weapon class
|
||||
# is in use.
|
||||
|
||||
attr_reader :widget
|
||||
|
||||
##
|
||||
# A unique identifier for this class.
|
||||
|
||||
attr_reader :name
|
||||
|
||||
##
|
||||
# The id of the special bar button for this weapon class, if applicable.
|
||||
|
||||
attr_reader :special_bar_button
|
||||
|
||||
##
|
||||
# The id of the widget config used to hide this weapon classes special bar, if applicable.
|
||||
|
||||
attr_reader :special_bar_config
|
||||
|
||||
##
|
||||
# Create a new WeaponClass with the given name and specified widget id.
|
||||
#
|
||||
# @param [Symbol] name The unique name to give this weapon class.
|
||||
# @param [Number] widget The id of the widget associated with this weapon class.
|
||||
|
||||
def initialize(name, widget, type)
|
||||
@name = name
|
||||
@widget = widget
|
||||
@type = type
|
||||
@styles = {}
|
||||
@defaults = {}
|
||||
@animations = {}
|
||||
end
|
||||
|
||||
def add_style(style, button: nil, attack_type: nil, speed: nil, animation: nil, block_animation: nil, range: 1)
|
||||
raise 'Invalid combat style given' unless COMBAT_STYLES.include? style
|
||||
##
|
||||
# Set default properties which are used for styles which don't have
|
||||
# those properties defined.
|
||||
#
|
||||
# @param [Hash] props
|
||||
# * :attack_type [Symbol] The default attack type for styles in this class.
|
||||
# * :speed [Number] The default attack speed for styles in this class.
|
||||
# * :animation [Number] The default attack animation id for styles in this class.
|
||||
# * :block_animation [Number] The default block animation id for styles in this class.
|
||||
# * :range [Number] The default attack range for styles in this class.
|
||||
|
||||
@styles[style] = {
|
||||
:attack_type => attack_type,
|
||||
:speed => speed,
|
||||
:animation => animation,
|
||||
:block_animation => block_animation,
|
||||
:range => range,
|
||||
:button => button == nil ? @styles.size + 1 : button
|
||||
}
|
||||
|
||||
@style_offsets.push style
|
||||
|
||||
if MELEE_COMBAT_STYLES.include? style
|
||||
@style_attacks[style] = Attack.new(animation: animation)
|
||||
def defaults(props)
|
||||
@defaults = props
|
||||
end
|
||||
|
||||
##
|
||||
# Add a new {@link CombatStyle} to to this class.
|
||||
#
|
||||
# @param [Symbol] style The name of the style, one of {@link COMBAT_STYLES}.
|
||||
# @param [Hash] properties
|
||||
# * :button [Number] The id of the button which activates this style.
|
||||
# * :attack_type [Symbol] The attack type for this style.
|
||||
# * :speed [Number] The attack speed for this style.
|
||||
# * :animation [Number] The attack animation id for this style.
|
||||
# * :graphic [Hash|Number] The id, or a hash specifying height and id for the graphic for this style.
|
||||
# * :requirements [Array] An array of [AttackRequirement] objects to be applied to this styles attack.
|
||||
# * :block_animation [Number] The block animation id for this style.
|
||||
# * :range [Number] The attack range for this style.
|
||||
#
|
||||
# @param [Proc] block An optional block which is called in the context of an AttackDSL if present, to create an attack for
|
||||
# this style.
|
||||
|
||||
def style(style, properties = {}, &block)
|
||||
fail 'Invalid combat style given' unless COMBAT_STYLES.include? style
|
||||
|
||||
properties = @defaults.merge properties
|
||||
|
||||
## The config ID used to set the active combat style, typically 0-3. TODO: does this always work?
|
||||
config = @styles.size
|
||||
button, attack_type, block_animation = properties[:button], properties[:attack_type], properties[:block_animation]
|
||||
|
||||
@styles[style] = CombatStyle.new(button, config, attack_type, block_animation)
|
||||
|
||||
if block_given?
|
||||
attack_dsl = AttackDSL.new
|
||||
attack_dsl.speed = properties[:speed]
|
||||
attack_dsl.graphic = properties[:graphic]
|
||||
attack_dsl.animation = properties[:animation]
|
||||
attack_dsl.range = properties[:range]
|
||||
properties[:requirements].each {|requirement| attack_dsl.add_requirement requirement }
|
||||
attack_dsl.instance_eval &block
|
||||
|
||||
@styles[style].attack = attack_dsl.to_attack
|
||||
end
|
||||
|
||||
return unless @styles[style].attack.nil?
|
||||
|
||||
## Get rid of any properties which aren't included in the keyword argument list
|
||||
properties.delete_if {|key| ![:speed, :animation, :range, :requirements, :graphic].include? key }
|
||||
|
||||
if @type == :melee
|
||||
@styles[style].attack = Attack.new(properties)
|
||||
elsif @type == :ranged
|
||||
@styles[style].attack = RangedAttack.new(properties)
|
||||
end
|
||||
end
|
||||
|
||||
def attack(style)
|
||||
@style_attacks[style]
|
||||
##
|
||||
# Get the currently selected combat style in this class for the given mob.
|
||||
# @param [Mob] mob
|
||||
# @return [CombatStyle]
|
||||
|
||||
def selected_style(mob)
|
||||
style = @styles.find { |_key, value| value.button == mob.combat_style }
|
||||
|
||||
style = @styles.min_by { |_key, value| value.button } if style.nil?
|
||||
|
||||
# We want the last element because Hash#find returns [key, value]
|
||||
style.last
|
||||
end
|
||||
|
||||
def attack_type(style)
|
||||
@styles[style][:attack_type]
|
||||
end
|
||||
##
|
||||
# Get the animation id for the given animation type.
|
||||
|
||||
def block_animation(style)
|
||||
@styles[style][:block_animation]
|
||||
end
|
||||
|
||||
def button(style)
|
||||
@styles[style][:button]
|
||||
end
|
||||
|
||||
def config(style)
|
||||
@style_offsets.find_index {|v| v == style }
|
||||
end
|
||||
|
||||
def other_animation(type)
|
||||
def animation(type)
|
||||
@animations[type]
|
||||
end
|
||||
|
||||
def speed(style)
|
||||
@styles[style][:speed] || default_speed
|
||||
end
|
||||
|
||||
def style_at(button)
|
||||
selected_style = @styles.select { |key, hash| hash[:button] == button }.keys[0]
|
||||
|
||||
if selected_style == nil
|
||||
selected_style = @styles.min_by { |key, hash| hash[:button] }.first
|
||||
end
|
||||
|
||||
selected_style
|
||||
end
|
||||
|
||||
def default_speed(speed = nil)
|
||||
unless speed.nil?
|
||||
@default_speed = speed
|
||||
end
|
||||
|
||||
@default_speed
|
||||
end
|
||||
##
|
||||
# Set the widget config and button for this classes special attack bar.
|
||||
|
||||
def special_bar(config, button)
|
||||
@special_bar_config = config
|
||||
@special_bar_button = button
|
||||
end
|
||||
|
||||
##
|
||||
# Check if this weapon class supports a special attack bar.
|
||||
|
||||
def special_bar?
|
||||
!@special_bar_button.nil? and !@special_bar_config.nil?
|
||||
!@special_bar_button.nil? && !@special_bar_config.nil?
|
||||
end
|
||||
|
||||
##
|
||||
|
||||
def animations(stand: nil, walk: nil, run: nil, idle_turn: nil, turn_around: nil, turn_left: nil, turn_right: nil)
|
||||
@animations = {
|
||||
:stand => stand,
|
||||
:walk => walk,
|
||||
:run => run,
|
||||
:idle_turn => idle_turn,
|
||||
:turn_around => turn_around,
|
||||
:turn_left => turn_left,
|
||||
:turn_right => turn_right
|
||||
stand: stand,
|
||||
walk: walk,
|
||||
run: run,
|
||||
idle_turn: idle_turn,
|
||||
turn_around: turn_around,
|
||||
turn_left: turn_left,
|
||||
turn_right: turn_right
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -127,8 +174,8 @@ end
|
||||
WEAPON_CLASSES = {}
|
||||
WEAPON_CLASS_INTERFACE_MAP = {}
|
||||
|
||||
def create_weapon_class(name, widget:, &block)
|
||||
weapon_class = WeaponClass.new(name, widget)
|
||||
def create_weapon_class(name, widget:, type: :melee, &block)
|
||||
weapon_class = WeaponClass.new(name, widget, type)
|
||||
weapon_class.instance_eval &block
|
||||
|
||||
WEAPON_CLASSES[name.to_sym] = weapon_class
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
BOW_WIDGET_ID = 10
|
||||
BOW_SPECIAL_BAR_ID = 10
|
||||
#
|
||||
# create_weapon_class :longbow, widget: BOW_WIDGET_ID do
|
||||
# special_bar = BOW_SPECIAL_BAR_ID
|
||||
#
|
||||
#
|
||||
# add_style :accurate, speed: 6, range: 7
|
||||
# add_style :rapid, speed: 6, range: 7
|
||||
# add_style :long_range, speed: 6, range: 9
|
||||
# end
|
||||
#
|
||||
# create_weapon_class :shortbow, widget: BOW_WIDGET_ID do
|
||||
# special_bar = BOW_SPECIAL_BAR_ID
|
||||
#
|
||||
# add_style :accurate, speed: 4, range: 7
|
||||
# add_style :rapid, speed: 3, range: 7
|
||||
# add_style :long_range, speed: 4, range: 9
|
||||
# end
|
||||
#
|
||||
@@ -1,37 +0,0 @@
|
||||
DAGGER_WIDGET_ID = 89
|
||||
DAGGER_SPECIAL_CONFIG_ID = 12
|
||||
DAGGER_SPECIAL_BUTTON_ID = 10
|
||||
|
||||
create_weapon_class :dagger, widget: DAGGER_WIDGET_ID do
|
||||
default_speed 4
|
||||
|
||||
attack_bonuses crush: -4, magic: 1
|
||||
defence_bonuses magic: 1
|
||||
|
||||
add_style :accurate, attack_type: :stab, animation: 7041, button: 2
|
||||
add_style :aggressive, attack_type: :stab, animation: 7041, button: 3
|
||||
add_style :alt_aggressive, attack_type: :slash, animation: 7048, button: 4
|
||||
add_style :defensive, attack_type: :stab, animation: 7049, button: 5
|
||||
end
|
||||
|
||||
# Need a separate WeaponClass for the dragon dagger because
|
||||
# of the differing animations
|
||||
create_weapon_class :dragon_dagger, widget: DAGGER_WIDGET_ID do
|
||||
default_speed 4
|
||||
special_bar DAGGER_SPECIAL_CONFIG_ID, DAGGER_SPECIAL_BUTTON_ID
|
||||
|
||||
attack_bonuses crush: -4, magic: 1
|
||||
defence_bonuses magic: 1
|
||||
|
||||
add_style :accurate, attack_type: :stab, animation: 402, button: 2
|
||||
add_style :aggressive, attack_type: :stab, animation: 402, button: 3
|
||||
add_style :alt_aggressive, attack_type: :slash, animation: 402, button: 4
|
||||
add_style :defensive, attack_type: :stab, animation: 402, button: 5
|
||||
end
|
||||
|
||||
create_weapon /(?:drag|dragon) dagger.*/, :dragon_dagger do
|
||||
set_special_attack energy_requirement: 25, animation: 1062, graphic: {id: 252, height: 100} do |source, target|
|
||||
damage! source, target, CombatUtil::calculate_hit(source, target)
|
||||
damage! source, target, CombatUtil::calculate_hit(source, target), 1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
DAGGER_WIDGET_ID = 89
|
||||
DAGGER_SPECIAL_CONFIG_ID = 12
|
||||
DAGGER_SPECIAL_BUTTON_ID = 10
|
||||
|
||||
create_weapon_class :dagger, widget: DAGGER_WIDGET_ID do
|
||||
defaults speed: 4, animation: 7041, attack_type: :stab
|
||||
|
||||
attack_bonuses crush: -4, magic: 1
|
||||
defence_bonuses magic: 1
|
||||
|
||||
style :accurate, button: 2
|
||||
style :aggressive, button: 3
|
||||
style :alt_aggressive, attack_type: :slash, animation: 7048, button: 4
|
||||
style :defensive, animation: 7049, button: 5
|
||||
end
|
||||
|
||||
# Need a separate WeaponClass for the dragon dagger because
|
||||
# of the differing animations
|
||||
create_weapon_class :dragon_dagger, widget: DAGGER_WIDGET_ID do
|
||||
defaults speed: 4, animation: 402, attack_type: :stab
|
||||
special_bar DAGGER_SPECIAL_CONFIG_ID, DAGGER_SPECIAL_BUTTON_ID
|
||||
|
||||
attack_bonuses crush: -4, magic: 1
|
||||
defence_bonuses magic: 1
|
||||
|
||||
style :accurate, button: 2
|
||||
style :aggressive, button: 3
|
||||
style :alt_aggressive, attack_type: :slash, button: 4
|
||||
style :defensive, button: 5
|
||||
end
|
||||
|
||||
create_weapon /(?:drag|dragon) dagger.*/, :dragon_dagger do
|
||||
set_special_attack speed: 4, energy_requirement: 25, animation: 1062, graphic: { id: 252, height: 100 } do
|
||||
damage! delay: 0
|
||||
damage! delay: 1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
create_weapon :granite_maul do
|
||||
attack_bonuses slash: 92, crush: 80
|
||||
other_bonuses melee_strength: 70
|
||||
|
||||
set_special_attack speed: 0, energy_requirement: 60, animation: 3157, graphic: 1225 do |_source, _target|
|
||||
damage! delay: 0
|
||||
damage! delay: 1
|
||||
end
|
||||
end
|
||||
+6
-6
@@ -3,16 +3,16 @@ SCIMITAR_SPECIAL_BAR_CONFIG_ID = 21
|
||||
SCIMITAR_SPECIAL_BAR_BUTTON_ID = 21
|
||||
|
||||
create_weapon_class :scimitar, widget: SCIMITAR_WIDGET_ID do
|
||||
default_speed 4
|
||||
defaults speed: 4, animation: 390, attack_type: :slash
|
||||
special_bar SCIMITAR_SPECIAL_BAR_CONFIG_ID, SCIMITAR_SPECIAL_BAR_BUTTON_ID
|
||||
|
||||
attack_bonuses crush: -2
|
||||
defence_bonuses slash: -1
|
||||
|
||||
add_style :accurate, attack_type: :slash, animation: 390, button: 2
|
||||
add_style :aggressive, attack_type: :slash, animation: 390, button: 3
|
||||
add_style :alt_aggressive, attack_type: :stab, animation: 391, button: 4
|
||||
add_style :defensive, attack_type: :slash, animation: 390, button: 5
|
||||
style :accurate, button: 2
|
||||
style :aggressive, button: 3
|
||||
style :alt_aggressive, attack_type: :stab, animation: 391, button: 4
|
||||
style :defensive, button: 5
|
||||
end
|
||||
|
||||
create_weapon :iron_scimitar do
|
||||
@@ -46,6 +46,6 @@ create_weapon :rune_scimitar do
|
||||
end
|
||||
|
||||
create_weapon :dragon_scimitar do
|
||||
attack_bonuses :stab => 8, slash: 67
|
||||
attack_bonuses stab: 8, slash: 67
|
||||
other_bonuses melee_strength: 66
|
||||
end
|
||||
+9
-9
@@ -1,9 +1,9 @@
|
||||
TWO_HANDED_SWORD_WIDGET_ID = 82
|
||||
TWO_HANDED_SWORD_WIDGET_ID = 82
|
||||
TWO_HANDED_SWORD_SPECIAL_CONFIG_ID = 12
|
||||
TWO_HANDED_SWORD_SPECIAL_BUTTON_ID = 10
|
||||
|
||||
create_weapon_class :two_handed_sword, widget: TWO_HANDED_SWORD_WIDGET_ID do
|
||||
default_speed 7
|
||||
defaults speed: 7, animation: 7041, attack_type: :slash
|
||||
special_bar TWO_HANDED_SWORD_SPECIAL_CONFIG_ID, TWO_HANDED_SWORD_SPECIAL_BUTTON_ID
|
||||
|
||||
animations stand: 7047, walk: 7046, run: 7039, idle_turn: 7044, turn_around: 7044, turn_left: 7043, turn_right: 7044
|
||||
@@ -11,10 +11,10 @@ create_weapon_class :two_handed_sword, widget: TWO_HANDED_SWORD_WIDGET_ID do
|
||||
attack_bonuses stab: -4, magic: -4
|
||||
defence_bonuses range: -1
|
||||
|
||||
add_style :accurate, attack_type: :slash, animation: 7041, button: 2
|
||||
add_style :aggressive, attack_type: :crush, animation: 7041, button: 3
|
||||
add_style :alt_aggressive, attack_type: :crush, animation: 7048, button: 4
|
||||
add_style :defensive, attack_type: :slash, animation: 7049, button: 5
|
||||
style :accurate, button: 2
|
||||
style :aggressive, attack_type: :crush, button: 3
|
||||
style :alt_aggressive, attack_type: :crush, animation: 7048, button: 4
|
||||
style :defensive, animation: 7049, button: 5
|
||||
end
|
||||
|
||||
create_weapon :iron_2h_sword do
|
||||
@@ -51,7 +51,7 @@ create_weapon :dragon_2h_sword do
|
||||
attack_bonuses slash: 92, crush: 80
|
||||
other_bonuses melee_strength: 70
|
||||
|
||||
set_special_attack energy_requirement: 60, animation: 3157, graphic: 1225 do |source, target|
|
||||
damage! source, target, 5
|
||||
set_special_attack speed: 7, energy_requirement: 60, animation: 3157, graphic: 1225 do |_source, _target|
|
||||
damage!
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
BOW_WIDGET_ID = 77
|
||||
BOW_SPECIAL_CONFIG_ID = 10
|
||||
BOW_SPECIAL_BUTTON_ID = 8
|
||||
|
||||
create_weapon_class :shortbow, widget: BOW_WIDGET_ID, type: :ranged do
|
||||
defaults animation: 426
|
||||
special_bar BOW_SPECIAL_CONFIG_ID, BOW_SPECIAL_BUTTON_ID
|
||||
|
||||
style :accurate, speed: 4, range: 7, button: 2
|
||||
style :rapid, speed: 3, range: 7, button: 3
|
||||
style :long_range, speed: 4, range: 9, button: 4
|
||||
end
|
||||
|
||||
create_weapon :shortbow
|
||||
|
||||
create_weapon_class :longbow, widget: BOW_WIDGET_ID, type: :ranged do
|
||||
defaults animation: 426
|
||||
special_bar BOW_SPECIAL_CONFIG_ID, BOW_SPECIAL_BUTTON_ID
|
||||
|
||||
style :accurate, speed: 6, range: 7, button: 2
|
||||
style :rapid, speed: 6, range: 7, button: 3
|
||||
style :long_range, speed: 6, range: 9, button: 4
|
||||
end
|
||||
|
||||
create_weapon :longbow
|
||||
@@ -0,0 +1,11 @@
|
||||
CROSSBOW_WIDGET_ID = 79
|
||||
|
||||
create_weapon_class :crossbow, widget: CROSSBOW_WIDGET_ID, type: :ranged do
|
||||
defaults animation: 426, speed: 6, range: 7
|
||||
|
||||
style :accurate, range: 7, button: 2
|
||||
style :rapid, speed: 5, button: 3
|
||||
style :long_range, range: 9, button: 4
|
||||
end
|
||||
|
||||
create_weapon /rune c'bow/, :crossbow
|
||||
@@ -0,0 +1,8 @@
|
||||
create_projectile_type :msb, start_height: 41, end_height: 37, delay: 31, speed: 3, slope: 15, radius: 8
|
||||
|
||||
create_weapon :magic_shortbow do
|
||||
set_special_attack speed: 3, range: 7, energy_requirement: 60, animation: 1074, graphic: { id: 256, height: 100 } do
|
||||
range_damage! projectile: 249, projectile_type: PROJECTILE_TYPES[:msb]
|
||||
range_damage! projectile: 249, projectile_type: PROJECTILE_TYPES[:msb], projectile_graphic: 256, delay: 1
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,11 @@
|
||||
create_weapon_class :no_weapon, widget: 92 do
|
||||
default_speed 4
|
||||
defaults speed: 4, animation: 422
|
||||
|
||||
add_style :accurate, animation: 422, block_animation: 424
|
||||
add_style :aggressive, animation: 423, block_animation: 424
|
||||
add_style :defensive, animation: 422, block_animation: 424
|
||||
style :accurate, button: 2
|
||||
style :aggressive, animation: 423, button: 3
|
||||
style :defensive, button: 4
|
||||
end
|
||||
|
||||
create_weapon :unarmed, :no_weapon, named: true do
|
||||
# Todo factor out empty blocks
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,9 +5,8 @@ java_import 'org.apollo.game.message.impl.SetInterfaceConfigMessage'
|
||||
java_import 'org.apollo.game.message.impl.ConfigMessage'
|
||||
java_import 'org.apollo.game.model.inv.SynchronizationInventoryListener'
|
||||
|
||||
|
||||
on :message, :item_option do |player, message|
|
||||
update_combat_tab(player) if message.option == 2 and message.interface_id == SynchronizationInventoryListener::INVENTORY_ID
|
||||
update_combat_tab(player) if message.option == 2 && message.interface_id == SynchronizationInventoryListener::INVENTORY_ID
|
||||
end
|
||||
|
||||
on :login do |event|
|
||||
@@ -15,10 +14,9 @@ on :login do |event|
|
||||
end
|
||||
|
||||
on :message, :item_action do |player, message|
|
||||
update_combat_tab(player) if message.interface_id == SynchronizationInventoryListener::EQUIPMENT_ID and message.slot == EquipmentConstants::WEAPON
|
||||
update_combat_tab(player) if message.interface_id == SynchronizationInventoryListener::EQUIPMENT_ID && message.slot == EquipmentConstants::WEAPON
|
||||
end
|
||||
|
||||
|
||||
on :message, :button do |player, msg|
|
||||
weapon = EquipmentUtil.equipped_weapon player
|
||||
weapon_class = weapon.weapon_class
|
||||
@@ -26,7 +24,7 @@ on :message, :button do |player, msg|
|
||||
|
||||
next unless weapon_class_widget == msg.interface_id
|
||||
|
||||
if weapon_class.special_bar? and msg.button == weapon_class.special_bar_button
|
||||
if weapon_class.special_bar? && msg.button == weapon_class.special_bar_button
|
||||
player.using_special = !player.using_special
|
||||
update_special_bar(player)
|
||||
else
|
||||
@@ -57,14 +55,11 @@ def update_combat_tab(player)
|
||||
end
|
||||
|
||||
def update_combat_style(player)
|
||||
weapon = EquipmentUtil.equipped_weapon player
|
||||
weapon_class = weapon.weapon_class
|
||||
weapon = EquipmentUtil.equipped_weapon player
|
||||
combat_style = weapon.weapon_class.selected_style player
|
||||
|
||||
# Update the combat style in case we had an invalid style selected,
|
||||
# Update the combat style in case we had an invalid style selected,
|
||||
# and therefore reverted to the first combat style
|
||||
selected_style = weapon_class.style_at(player.combat_style)
|
||||
|
||||
player.combat_style = weapon_class.button selected_style
|
||||
player.send ConfigMessage.new(43, weapon_class.config(selected_style))
|
||||
player.combat_style = combat_style.button
|
||||
player.send ConfigMessage.new(43, combat_style.config)
|
||||
end
|
||||
|
||||
|
||||
@@ -20,4 +20,3 @@ on :login do |event|
|
||||
update_special_bar player
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user