mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Build a framework for a maintainable combat plugin
* Uses a hierarchy of WeaponClasses -> Weapons for performing attacks, with WeaponClass having a set of styles and associated Attacks for those styles. Weapons and their classes are built with an easy to use and clean DSL. * Adds a BonusContainer mixin, so that Equipment, Weapons, and WeaponClasses can all have their own set of bonuses which apply to the player. * Allows attacks to be queued to the Mobs CombatState instance from external code, allowing e.g., NPCs or auto-cast to queue attacks to be executed.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||
java_import 'org.apollo.game.model.Animation'
|
||||
java_import 'org.apollo.game.model.Graphic'
|
||||
|
||||
class BaseAttack
|
||||
attr_reader :requirements, :range
|
||||
|
||||
def initialize(animation, graphic = nil, range = 1, requirements = [])
|
||||
@animation = animation
|
||||
@graphic = graphic
|
||||
@range = range
|
||||
@requirements = requirements
|
||||
end
|
||||
|
||||
def do(source, target)
|
||||
source.play_animation(Animation.new(@animation))
|
||||
|
||||
unless @graphic.nil?
|
||||
if @graphic.is_a?(Hash)
|
||||
source.play_graphic(Graphic.new(@graphic[:id], @graphic[:delay] || 0, @graphic[:height] | 0))
|
||||
else
|
||||
source.play_graphic(Graphic.new(@graphic))
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
class ProcAttack < BaseAttack
|
||||
def initialize(block, animation:, graphic:, range: 1, requirements: [])
|
||||
super(animation, graphic, range, requirements)
|
||||
|
||||
@block = block
|
||||
end
|
||||
|
||||
def apply(source, target)
|
||||
self.instance_exec(source, target, &@block)
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||
|
||||
class AttackRequirementException < Exception
|
||||
attr_reader :message
|
||||
|
||||
def initialize(message)
|
||||
@message = message
|
||||
end
|
||||
end
|
||||
|
||||
class AttackRequirement
|
||||
def validate!(player)
|
||||
throw RuntimeError.new('validate! not implemented')
|
||||
end
|
||||
|
||||
def apply(player)
|
||||
throw RuntimeError.new('apply not implemented')
|
||||
end
|
||||
end
|
||||
|
||||
class SpecialEnergyRequirement < AttackRequirement
|
||||
def initialize(amount)
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def validate!(player)
|
||||
throw AttackRequirementException.new('Not enough special attack energy.') unless player.special_energy >= @amount
|
||||
end
|
||||
|
||||
def apply(player)
|
||||
player.special_energy = player.special_energy - @amount
|
||||
end
|
||||
end
|
||||
|
||||
class ItemRequirement < AttackRequirement
|
||||
def initialize(item, amount)
|
||||
@item = item
|
||||
@amount = amount
|
||||
end
|
||||
|
||||
def validate!(player)
|
||||
throw AttackRequirementException.new(item_missing_message) unless player.inventory.get_amount(@item) >= @amount
|
||||
end
|
||||
|
||||
def apply(player)
|
||||
player.inventory.remove(@item, @amount)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def item_missing_message
|
||||
definition = ItemDefinition.lookup(@item)
|
||||
|
||||
"You don't have enough #{lookup_item(@item).name}s"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
module CombatModule
|
||||
##
|
||||
# The delay a <i>Mob</i> must wait before attacking again.
|
||||
declare_attribute(:attack_delay, 0)
|
||||
|
||||
##
|
||||
# A flag indicating whether this <i>Mob</i> is currently in combat.
|
||||
declare_attribute(:attacking, false)
|
||||
|
||||
##
|
||||
# A flag indicating whether our <i>Mob</i> is dead.
|
||||
declare_attribute(:dead, false)
|
||||
|
||||
##
|
||||
# The amount of ticks a <i>Player</i> must wait before logging out after combat.
|
||||
declare_attribute(:logout_timer, Time.now.to_i)
|
||||
|
||||
##
|
||||
# The <i>CombatStyle</i> offset that a <i>Mob</i> is currently using.
|
||||
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)
|
||||
|
||||
##
|
||||
# An integer between 0 and 100 indicating the amount of special energy a <i>Player</i> has.
|
||||
declare_attribute(:special_energy, 100, :persistent)
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
module Combat
|
||||
# A module for units which can have their own bonuses. E.G., weapons, equipment, ammo.
|
||||
module BonusContainer
|
||||
def attack_bonus(type)
|
||||
@attack_bonuses[type]
|
||||
end
|
||||
|
||||
def defence_bonus(type)
|
||||
@defence_bonuses[type]
|
||||
end
|
||||
|
||||
def other_bonus(type)
|
||||
@other_bonuses[type]
|
||||
end
|
||||
|
||||
def other_bonuses(melee_strength: 0, ranged_strength: 0, prayer: 0)
|
||||
@other_bonuses = {
|
||||
: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
|
||||
}
|
||||
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
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
on :message, :npc_action do |player, message|
|
||||
player_combat_state = get_combat_state player
|
||||
player_combat_state.target = $world.npc_repository.get message.index
|
||||
|
||||
unless player.attacking
|
||||
player.start_action CombatAction.new(player)
|
||||
end
|
||||
end
|
||||
|
||||
on :message, :player_action do |player, message|
|
||||
|
||||
end
|
||||
@@ -0,0 +1,106 @@
|
||||
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
|
||||
|
||||
class CombatAction < Action
|
||||
def initialize(source, attack = nil)
|
||||
super(0, true, source)
|
||||
|
||||
@combat_state = get_combat_state(source)
|
||||
@attack = attack
|
||||
end
|
||||
|
||||
def execute
|
||||
if @combat_state.target.nil? and @combat_state.queued_attacks.empty?
|
||||
@combat_state.reset
|
||||
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
|
||||
if @combat_state.queued_attacks.empty? and @combat_state.supports_weapon
|
||||
weapon = EquipmentUtil.equipped_weapon mob
|
||||
weapon_class = weapon.weapon_class
|
||||
combat_style = weapon_class.style_at mob.combat_style
|
||||
|
||||
if mob.attacking
|
||||
set_delay weapon_class.speed(combat_style) - 1
|
||||
else
|
||||
set_delay 0
|
||||
mob.attacking = true
|
||||
end
|
||||
|
||||
if mob.using_special and weapon.special_attack?
|
||||
@combat_state.next_attack = weapon.special_attack
|
||||
else
|
||||
@combat_state.next_attack = weapon_class.attack(combat_style)
|
||||
end
|
||||
|
||||
@combat_state.state = :attacking
|
||||
elsif @combat_state.queued_attacks.size > 0
|
||||
@combat_state.next_attack = @combat_state.queued_attacks.pop
|
||||
@combat_state.state = :attacking
|
||||
else
|
||||
stop
|
||||
@combat_state.reset
|
||||
end
|
||||
end
|
||||
|
||||
def update_attacking
|
||||
raise RuntimeError.new('no attack when in :attacking state') if @combat_state.next_attack.nil?
|
||||
|
||||
begin
|
||||
@combat_state.next_attack.do(mob, @combat_state.target)
|
||||
|
||||
@combat_state.state = :idle
|
||||
set_delay 0
|
||||
rescue AttackRequirementException => e
|
||||
mob.send_message e.message
|
||||
end
|
||||
end
|
||||
|
||||
def update_chasing
|
||||
|
||||
end
|
||||
|
||||
def stop
|
||||
super
|
||||
|
||||
@combat_state.reset
|
||||
mob.attacking = false
|
||||
|
||||
puts 'stopped combat action'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
def get_combat_state(mob)
|
||||
mob.is_a?(Player) ? type = :player : type = :npc
|
||||
|
||||
unless MOB_COMBAT_STATE_CACHE[type].has_key? mob.index
|
||||
MOB_COMBAT_STATE_CACHE[type][mob.index] = CombatState.new(mob)
|
||||
end
|
||||
|
||||
MOB_COMBAT_STATE_CACHE[type][mob.index]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class CombatState
|
||||
attr_accessor :state, :next_attack
|
||||
attr_reader :queued_attacks, :supports_weapon
|
||||
|
||||
def initialize(mob, supports_weapon = true)
|
||||
@mob = mob
|
||||
@supports_weapon = supports_weapon
|
||||
reset
|
||||
end
|
||||
|
||||
def reset
|
||||
@state = :idle
|
||||
@target = nil
|
||||
@next_attack = nil
|
||||
@queued_attacks = []
|
||||
@mob.reset_interacting_mob
|
||||
end
|
||||
|
||||
def target
|
||||
@target
|
||||
end
|
||||
|
||||
def target=(target)
|
||||
@mob.reset_interacting_mob
|
||||
@mob.interacting_mob = target
|
||||
@target = target
|
||||
end
|
||||
|
||||
def queue_attack(attack)
|
||||
@queued_attacks.push(attack)
|
||||
end
|
||||
end
|
||||
|
||||
MOB_COMBAT_STATE_CACHE = {
|
||||
:player => {},
|
||||
:npc => {}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
java_import 'org.apollo.cache.def.EquipmentDefinition'
|
||||
|
||||
EQUIPMENT = {}
|
||||
|
||||
def create_equipment(item, &block)
|
||||
equipment = Equipment.new
|
||||
equipment.instance_eval block
|
||||
|
||||
find_entities :item, item do |equipment_item|
|
||||
EQUIPMENT[id] = equipment
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Equipment
|
||||
include Combat::BonusContainer
|
||||
end
|
||||
@@ -1,17 +1,36 @@
|
||||
<?xml version="1.0"?>
|
||||
<plugin>
|
||||
<id>combat</id>
|
||||
<version>1</version>
|
||||
<name>Combat</name>
|
||||
<description>Manages combat between game characters.</description>
|
||||
<authors>
|
||||
<author>Ryley</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>wilderness.rb</script>
|
||||
</scripts>
|
||||
<dependencies>
|
||||
<dependency>attributes</dependency>
|
||||
<dependency>areas</dependency>
|
||||
</dependencies>
|
||||
<id>combat</id>
|
||||
<version>0.1</version>
|
||||
<name>combat</name>
|
||||
<description>Adds fully functioning melee, ranged and magic combat.</description>
|
||||
<authors>
|
||||
<author>garyttierney</author>
|
||||
</authors>
|
||||
<scripts>
|
||||
<script>attack.rb</script>
|
||||
<script>attribute.rb</script>
|
||||
<script>bonus_container.rb</script>
|
||||
<script>combat.rb</script>
|
||||
<script>combat_action.rb</script>
|
||||
<script>combat_state.rb</script>
|
||||
<script>equipment.rb</script>
|
||||
<script>projectile.rb</script>
|
||||
<script>util.rb</script>
|
||||
<script>weapon.rb</script>
|
||||
<script>weapon_class.rb</script>
|
||||
<script>weapons/bows.rb</script>
|
||||
<script>weapons/scimitars.rb</script>
|
||||
<script>weapons/swords.rb</script>
|
||||
<script>weapons/two_handed_swords.rb</script>
|
||||
<script>weapons/unarmed.rb</script>
|
||||
<script>widgets/combat_tab.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>
|
||||
<dependency>util</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
def create_projectile(item, drop_rate:, graphic:, projectile:)
|
||||
items = i
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
java_import 'org.apollo.game.model.entity.EquipmentConstants'
|
||||
|
||||
class CombatUtil
|
||||
|
||||
def self.calculate_max_hit(source)
|
||||
strength = source.skill_set.get_skill(Skill::STRENGTH)
|
||||
strength_stat = 5 #source.bonus_stat(:other, :strength)
|
||||
|
||||
effective_strength_damage = (strength.current_level) #* prayer_multiplier
|
||||
|
||||
if [:aggressive, :alt_aggressive].include? source.combat_style
|
||||
effective_strength_damage += 3
|
||||
end
|
||||
|
||||
(1.3 + (effective_strength_damage / 10) + (strength_stat / 80) +
|
||||
((effective_strength_damage * strength_stat) / 640))
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
attack_stat = [1, 1].max
|
||||
defence_stat = [1, 1].max
|
||||
|
||||
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_accuracy = attack_stat * attack * attack_prayer_multiplier
|
||||
attack_accuracy = 0.1 if attack_accuracy < 0
|
||||
|
||||
defence_prayer_multiplier = 1 #TODO: Prayer
|
||||
defence_accuracy = defence_stat * defence * defence_prayer_multiplier
|
||||
defence_accuracy = 1 if defence_accuracy < 0
|
||||
|
||||
base = attack_accuracy / defence_accuracy
|
||||
base > 1 ? 0.9 : base * 0.9
|
||||
end
|
||||
|
||||
# Calculates a hit for the given <i>Mob</i> and special attack flag.
|
||||
def self.calculate_hit(source, target)
|
||||
accuracy = calculate_accuracy source, target
|
||||
max_hit = calculate_max_hit(source) + 1
|
||||
|
||||
if rand <= accuracy
|
||||
return rand(max_hit)
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class EquipmentUtil
|
||||
def self.equipped_weapon(source)
|
||||
item = source.equipment.get(EquipmentConstants::WEAPON)
|
||||
|
||||
if item.nil?
|
||||
return NAMED_WEAPONS[:no_weapon]
|
||||
end
|
||||
|
||||
WEAPONS[item.id]
|
||||
end
|
||||
|
||||
def self.equipped_projectile(source)
|
||||
item = source.equipment.get(EquipmentConstants::ARROWS)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
java_import 'org.apollo.cache.def.ItemDefinition'
|
||||
java_import 'org.apollo.game.model.inv.InventoryAdapter'
|
||||
java_import 'org.apollo.game.model.entity.EquipmentConstants'
|
||||
java_import 'org.apollo.game.model.entity.AnimationSet'
|
||||
java_import 'org.apollo.game.model.Animation'
|
||||
java_import 'org.apollo.game.model.inv.SynchronizationInventoryListener'
|
||||
|
||||
WEAPONS = {}
|
||||
NAMED_WEAPONS = {}
|
||||
|
||||
COMBAT_STYLES = [
|
||||
:accurate,
|
||||
:aggressive,
|
||||
:defensive,
|
||||
:controlled,
|
||||
:alt_aggressive
|
||||
]
|
||||
|
||||
def create_weapon(identifier, class_name = nil, named: false, &block)
|
||||
if named
|
||||
create_named_weapon(identifier, class_name, &block)
|
||||
else
|
||||
create_normal_weapon(identifier, class_name, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def create_normal_weapon(item_matcher, class_name = nil, &block)
|
||||
items = find_entities :item, item_matcher.to_s.gsub(/_/, ' '), -1
|
||||
items.each do |item_id|
|
||||
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
|
||||
else
|
||||
raise "Couldn't find a suitable weapon class for the given weapon."
|
||||
end
|
||||
end
|
||||
|
||||
WEAPONS[item_id] = Weapon.new(WEAPON_CLASSES[class_name])
|
||||
WEAPONS[item_id].instance_eval &block
|
||||
end
|
||||
end
|
||||
|
||||
def create_named_weapon(name, class_name, &block)
|
||||
NAMED_WEAPONS[name] = Weapon.new WEAPON_CLASSES[class_name]
|
||||
NAMED_WEAPONS[name].instance_eval &block
|
||||
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
|
||||
attr_reader :weapon_class, :special_attack
|
||||
|
||||
include Combat::BonusContainer
|
||||
|
||||
def initialize(weapon_class)
|
||||
@weapon_class = weapon_class
|
||||
@special_attack = nil
|
||||
end
|
||||
|
||||
def special_attack?
|
||||
not special_attack.nil?
|
||||
end
|
||||
|
||||
def set_special_attack(energy_requirement:, animation:, graphic: nil, &block)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def update_weapon_animations(player)
|
||||
default_animations = AnimationSet::DEFAULT_ANIMATION_SET
|
||||
player_animations = player.animation_set
|
||||
|
||||
player_animations.stand = default_animations.stand
|
||||
player_animations.walking = default_animations.walking
|
||||
player_animations.running = default_animations.running
|
||||
player_animations.idle_turn = default_animations.idle_turn
|
||||
player_animations.turn_around = default_animations.turn_around
|
||||
player_animations.turn_left = default_animations.turn_left
|
||||
player_animations.turn_right = default_animations.turn_right
|
||||
|
||||
weapon = EquipmentUtil.equipped_weapon(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)
|
||||
puts animation
|
||||
|
||||
puts 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
|
||||
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
|
||||
end
|
||||
|
||||
on :login do |event|
|
||||
update_weapon_animations(event.player)
|
||||
end
|
||||
|
||||
on :message, :item_action do |player, message|
|
||||
update_weapon_animations(player) if message.interface_id == SynchronizationInventoryListener::EQUIPMENT_ID and message.slot == EquipmentConstants::WEAPON
|
||||
end
|
||||
@@ -0,0 +1,119 @@
|
||||
COMBAT_STYLES = [
|
||||
:accurate,
|
||||
:aggressive,
|
||||
: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
|
||||
]
|
||||
|
||||
class WeaponClass
|
||||
attr_reader :widget, :name
|
||||
|
||||
include Combat::BonusContainer
|
||||
|
||||
def initialize(name, widget)
|
||||
@name = name
|
||||
@widget = widget
|
||||
@styles = {}
|
||||
@style_attacks = {}
|
||||
@style_offsets = []
|
||||
@animations = {}
|
||||
end
|
||||
|
||||
def add_style(style, attack_type: nil, speed: nil, animation: nil, block_animation: nil, range: 1)
|
||||
raise 'Invalid combat style given' unless COMBAT_STYLES.include? style
|
||||
|
||||
@styles[style] = {
|
||||
:attack_type => attack_type,
|
||||
:speed => speed,
|
||||
:animation => animation,
|
||||
:block_animation => block_animation,
|
||||
:range => range
|
||||
}
|
||||
|
||||
@style_offsets.push style
|
||||
|
||||
if MELEE_COMBAT_STYLES.include? style
|
||||
@style_attacks[style] = Attack.new(animation: animation)
|
||||
end
|
||||
end
|
||||
|
||||
def attack(style)
|
||||
@style_attacks[style]
|
||||
end
|
||||
|
||||
def attack_type(style)
|
||||
@styles[style][:attack_type]
|
||||
end
|
||||
|
||||
def block_animation(style)
|
||||
@styles[style][:block_animation]
|
||||
end
|
||||
|
||||
def other_animation(type)
|
||||
@animations[type]
|
||||
end
|
||||
|
||||
def speed(style)
|
||||
@styles[style][:speed] || default_speed
|
||||
end
|
||||
|
||||
def style_at(offset)
|
||||
@style_offsets[offset]
|
||||
end
|
||||
|
||||
def default_speed(speed = nil)
|
||||
unless speed.nil?
|
||||
@default_speed = speed
|
||||
end
|
||||
|
||||
@default_speed
|
||||
end
|
||||
|
||||
def special_bar(id = nil)
|
||||
unless id.nil?
|
||||
@special_bar = id
|
||||
end
|
||||
|
||||
@special_bar
|
||||
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
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
WEAPON_CLASSES = {}
|
||||
WEAPON_CLASS_INTERFACE_MAP = {}
|
||||
|
||||
def create_weapon_class(name, widget:, &block)
|
||||
weapon_class = WeaponClass.new(name, widget)
|
||||
weapon_class.instance_eval &block
|
||||
|
||||
WEAPON_CLASSES[name.to_sym] = weapon_class
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
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
|
||||
#
|
||||
@@ -0,0 +1,50 @@
|
||||
SCIMITAR_WIDGET_ID = 81
|
||||
SCIMITAR_SPECIAL_BAR_ID = 21
|
||||
|
||||
create_weapon_class :scimitar, widget: SCIMITAR_WIDGET_ID do
|
||||
default_speed 4
|
||||
special_bar SCIMITAR_SPECIAL_BAR_ID
|
||||
|
||||
attack_bonuses crush: -2
|
||||
defence_bonuses slash: -1
|
||||
|
||||
add_style :accurate, attack_type: :slash, animation: 390
|
||||
add_style :aggressive, attack_type: :slash, animation: 390
|
||||
add_style :alt_aggressive, attack_type: :stab, animation: 391
|
||||
add_style :defensive, attack_type: :slash, animation: 390
|
||||
end
|
||||
|
||||
create_weapon :iron_scimitar do
|
||||
attack_bonuses stab: 2, slash: 10
|
||||
other_bonuses melee_strength: 9
|
||||
end
|
||||
|
||||
create_weapon :steel_scimitar do
|
||||
attack_bonuses stab: 3, slash: 15
|
||||
other_bonuses melee_strength: 14
|
||||
end
|
||||
|
||||
create_weapon /(black|white) scimitar/ do
|
||||
attack_bonuses stab: 4, slash: 19
|
||||
other_bonuses melee_strength: 14
|
||||
end
|
||||
|
||||
create_weapon :mithril_scimitar do
|
||||
attack_bonuses stab: 5, slash: 21
|
||||
other_bonuses melee_strength: 20
|
||||
end
|
||||
|
||||
create_weapon :adamant_scimitar do
|
||||
attack_bonuses stab: 6, slash: 29
|
||||
other_bonuses melee_strength: 28
|
||||
end
|
||||
|
||||
create_weapon :rune_scimitar do
|
||||
attack_bonuses stab: 7, slash: 45
|
||||
other_bonuses melee_strength: 44
|
||||
end
|
||||
|
||||
create_weapon :dragon_scimitar do
|
||||
attack_bonuses :stab => 8, slash: 67
|
||||
other_bonuses melee_strength: 66
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
TWO_HANDED_SWORD_WIDGET_ID = 82
|
||||
TWO_HANDED_SWORD_SPECIAL_ID = 12
|
||||
|
||||
create_weapon_class :two_handed_sword, widget: TWO_HANDED_SWORD_WIDGET_ID do
|
||||
default_speed 7
|
||||
special_bar TWO_HANDED_SWORD_SPECIAL_ID
|
||||
|
||||
animations stand: 7047, walk: 7046, run: 7039, idle_turn: 7044, turn_around: 7044, turn_left: 7043, turn_right: 7044
|
||||
|
||||
attack_bonuses stab: -4, magic: -4
|
||||
defence_bonuses range: -1
|
||||
|
||||
add_style :accurate, attack_type: :slash, animation: 7041
|
||||
add_style :aggressive, attack_type: :crush, animation: 7041
|
||||
add_style :alt_aggressive, attack_type: :crush, animation: 7048
|
||||
add_style :defensive, attack_type: :slash, animation: 7049
|
||||
end
|
||||
|
||||
create_weapon :iron_2h_sword do
|
||||
attack_bonuses slash: 13, crush: 10
|
||||
other_bonuses melee_strength: 14
|
||||
end
|
||||
|
||||
create_weapon :steel_2h_sword do
|
||||
attack_bonuses slash: 21, crush: 16
|
||||
other_bonuses melee_strength: 22
|
||||
end
|
||||
|
||||
create_weapon /(?:black|white) 2h sword/ do
|
||||
attack_bonuses slash: 27, crush: 21
|
||||
other_bonuses melee_strength: 26
|
||||
end
|
||||
|
||||
create_weapon :mithril_2h_sword do
|
||||
attack_bonuses slash: 30, crush: 24
|
||||
other_bonuses melee_strength: 26
|
||||
end
|
||||
|
||||
create_weapon :adamant_2h_sword do
|
||||
attack_bonuses slash: 43, crush: 30
|
||||
other_bonuses melee_strength: 31
|
||||
end
|
||||
|
||||
create_weapon :rune_2h_sword do
|
||||
attack_bonuses slash: 69, crush: 50
|
||||
other_bonuses melee_strength: 70
|
||||
end
|
||||
|
||||
create_weapon :dragon_2h_sword do
|
||||
attack_bonuses slash: 92, crush: 80
|
||||
other_bonuses melee_strength: 70
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
create_weapon_class :unarmed, widget: -1 do
|
||||
default_speed 4
|
||||
|
||||
add_style :accurate, animation: 422, block_animation: 424
|
||||
add_style :aggressive, animation: 423, block_animation: 424
|
||||
add_style :defensive, animation: 422, block_animation: 424
|
||||
end
|
||||
|
||||
create_weapon :no_weapon, :unarmed, named: true do
|
||||
# Todo factor out empty blocks
|
||||
end
|
||||
@@ -1,66 +0,0 @@
|
||||
require 'java'
|
||||
|
||||
java_import 'org.apollo.game.model.entity.Player'
|
||||
java_import 'org.apollo.game.message.impl.SetWidgetTextMessage'
|
||||
java_import 'org.apollo.game.message.impl.OpenOverlayMessage'
|
||||
|
||||
declare_attribute(:wilderness_level, 0, :transient)
|
||||
|
||||
# Constants constants related to the wilderness
|
||||
module WildernessConstants
|
||||
|
||||
# The wilderness level overlay interface id
|
||||
OVERLAY_INTERFACE_ID = 197
|
||||
|
||||
# The wilderness level string id
|
||||
LEVEL_STRING_ID = 199
|
||||
|
||||
end
|
||||
|
||||
# Determines the wilderness level for the specified position
|
||||
def wilderness_level(position)
|
||||
((position.y - 3520) / 8).ceil + 1
|
||||
end
|
||||
|
||||
area_action :wilderness_level do
|
||||
on_entry do |player, position|
|
||||
player.wilderness_level = wilderness_level(position)
|
||||
player.interface_set.open_overlay(WildernessConstants::OVERLAY_INTERFACE_ID)
|
||||
|
||||
id = WildernessConstants::LEVEL_STRING_ID
|
||||
player.send(SetWidgetTextMessage.new(id, "Level: #{player.wilderness_level}"))
|
||||
show_action(player, ATTACK_ACTION)
|
||||
end
|
||||
|
||||
while_in do |player, position|
|
||||
current = player.wilderness_level
|
||||
updated = wilderness_level(position)
|
||||
|
||||
if current != updated
|
||||
player.wilderness_level = updated
|
||||
|
||||
id = WildernessConstants::LEVEL_STRING_ID
|
||||
player.send(SetWidgetTextMessage.new(id, "Level: #{player.wilderness_level}"))
|
||||
end
|
||||
end
|
||||
|
||||
on_exit do |player, position|
|
||||
player.wilderness_level = 0
|
||||
player.interface_set.close
|
||||
|
||||
player.send(OpenOverlayMessage.new(-1))
|
||||
hide_action(player, ATTACK_ACTION)
|
||||
end
|
||||
end
|
||||
|
||||
# Monkey patch the existing player class to add method of checking whether or not a player is
|
||||
# within the wilderness
|
||||
class Player
|
||||
|
||||
def in_wilderness
|
||||
wilderness_level > 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
area name: :wilderness, coordinates: [2945, 3522, 3390, 3972, 0], actions: :wilderness_level
|
||||
Reference in New Issue
Block a user