apollo-rsps / apollo

An open-source Java game server suite designed to be lightweight, fast, and secure.
ISC License
183 stars 141 forks source link

Firemaking #303

Open lare96 opened 8 years ago

lare96 commented 8 years ago

Don't want to submit a PR for untested/unfinished work so I guess an issue is okay.

I was working on Firemaking awhile back, iirc it's near complete it just needs the ability to light logs from the ground. I don't have the time to finish it off so I'll just leave it here for anyone that's interested

log.rb

# Hash of log ids -> log data.
LOGS = {}

# A PORO representing a log that can be lit by a Tinderbox.
class Log
  attr_accessor :id, :name, :level, :experience

  def initialize(id, level, experience)
    @id = id
    @level = level
    @experience = experience
    @name = name_of(:item, id)
  end
end

# Add a new Log to the hash.
def log(id, hash)
  unless hash.has_keys?(:level, :experience)
    fail 'Hash must contain a level and experience.'
  end

  LOGS[id] = Log.new(id, hash[:level], hash[:experience])
end

log 1511, level: 1, experience: 40.0 # Normal
log 2862, level: 1, experience: 40.0 # Achey
log 1521, level: 15, experience: 60.0 # Oak
log 1519, level: 30, experience: 90.0 # Willow
log 6333, level: 35, experience: 105.0 # Teak
log 1517, level: 45, experience: 135.0 # Maple
log 6332, level: 50, experience: 157.5 # Mahogany
log 1515, level: 60, experience: 202.5 # Yew
log 1513, level: 75, experience: 303.8 # Magic

firemake.rb

require 'java'

java_import 'org.apollo.game.action.Action'
java_import 'org.apollo.game.model.Animation'
java_import 'org.apollo.game.model.entity.obj.DynamicGameObject'
java_import 'org.apollo.game.model.entity.EntityType'
java_import 'org.apollo.game.model.entity.GroundItem'
java_import 'org.apollo.game.model.Direction'
java_import 'org.apollo.game.model.Item'
java_import 'org.apollo.game.scheduling.ScheduledTask'

# The identifier for the Tinderbox item.
TINDERBOX_ID = 590

# The identifier for the Fire object.
FIRE_OBJECT_ID = 2732

# The Item spawned when a fire has burnt out.
ASHES_ITEM = Item.new(592)

# The Animation played when lighting a fire.
LIGHT_ANIMATION = Animation.new(733)

# Starts the FiremakingAction if a valid log is found.
def fm_action(player, id, message)
  log = LOGS[id]

  unless log.nil?
    player.start_action(FiremakingAction.new(player, log))
    message.terminate()
  end
end

# TODO: Tinderbox on tile item, tile item "Light" iteraction.

# Intercepts the message for using a Tinderbox on a log in the inventory and vice-versa.
on :message, :item_on_item do |player, message|
  if message.id == TINDERBOX_ID
    fm_action(player, message.target_id, message)
  elsif message.target_id == TINDERBOX_ID
    fm_action(player, message.id, message)
  end
end

# Action implementation that handles lighting a fire.
class FiremakingAction < Action
  attr_reader :log, :ground_log, :region, :firemaking_level, :started

  # Creates the FiremakingAction.
  def initialize(player, log)
    super(3, true, player)
    @log = log
    @region = $world.region_repository.from_position(mob.position)
    @firemaking_level = mob.skill_set.get_skill(Skill::FIREMAKING).current_level
    @started = false
  end

  # Returns whether or not a light is successful.
  def successful_light
    # TODO: Develop more accurate formula
    [@firemaking_level - @log.level + 5, 30].min > rand(40)
  end

  # Returns the walking position based on the direction.
  def walk_coordinates(direction)
    pos = mob.position
    new_pos =
      case direction
        when Direction::NORTH then [0, 1]
        when Direction::SOUTH then [0, -1]
        when Direction::EAST then [1, 0]
        when Direction::WEST then [-1, 0]
        when Direction::NONE then [0, 0]
        else
          fail 'unexpected direction value'
      end
    Position.new(pos.x + new_pos[0], pos.y + new_pos[1], pos.z)
  end

  # Returns a boolean value describing if a log can be lighted on the Mob's current position.
  def can_light(direction)
    unless @region.get_entities(mob.position, EntityType::DYNAMIC_OBJECT,
      EntityType::STATIC_OBJECT).size == 0
      return false
    end

    if direction == Direction::NONE
      return true
    end

    if @region.traversable(mob.position, EntityType::PLAYER, direction)
      return true
    end

    false
  end

  # Attempts to light a fire, and returns a boolean value describing whether or not the
  # operation was successful.
  def light_fire(direction)
    if can_light(direction)
      fire = DynamicGameObject.create_public($world, FIRE_OBJECT_ID, mob.position, 10, 0)
      walk_to = walk_coordinates(direction)

      if walk_to != mob.position
        mob.walking_queue.add_first_step(walk_to)
      end

      @region.remove_entity(@ground_log)
      $world.spawn(fire)

      schedule 80 do |context| # TODO: random burn time
        @region.remove_entity(fire)
        $world.spawn(GroundItem.create($world, fire.position, ASHES_ITEM))
        context.stop
      end

      true
    end

    false
  end

  # TODO: No lighting fires within banks, certain minigames, etc.
  def execute
    mob.walking_queue.clear

    if @log.level > firemaking_level
      mob.send_message("You need a Firemaking level of #{@log.level} to light this log.")
      stop
      return
    end

    inventory = mob.inventory

    unless inventory.contains(TINDERBOX_ID)
      mob.send_message('You need a tinderbox in your inventory in order to light fires.')
      stop
      return
    end

    unless @started
      mob.send_message('You attempt to light the logs.')
      inventory.remove(@log.id)

      @ground_log = GroundItem.dropped($world, mob.position, Item.new(@log.id), mob)
      $world.spawn(@ground_log)

      @started = true
    end

    mob.play_animation(LIGHT_ANIMATION)

    if successful_light
      if light_fire(Direction::WEST) or
        light_fire(Direction::EAST) or
        light_fire(Direction::NORTH) or
        light_fire(Direction::SOUTH) or
        light_fire(Direction::NONE)
        mob.send_message('The fire catches and the logs begin to burn.')
      else
        mob.send_message('You cannot light a fire here.')
      end

      stop
    end
  end

  def stop
    super
    mob.stop_animation
  end

  def equals(other)
    get_class == other.get_class && log == other.log
  end
end

plugin.xml

<?xml version="1.0"?>
<plugin>
    <id>firemaking</id>
    <version>1</version>
    <name>Firemaking</name>
    <description>A plugin for the Firemaking skill.</description>
    <authors>
        <author>lare96</author>
    </authors>
    <scripts>
        <script>log.rb</script>
        <script>firemake.rb</script>
    </scripts>
    <dependencies/>
</plugin>
rlgenesis commented 7 years ago

Throws an error:

SEVERE: Uncaught exception thrown while handling message: org.apollo.game.message.impl.ItemOnItemMessage@39dcdd9e
org.jruby.exceptions.RaiseException: (ArgumentError) wrong number of arguments for constructor
    at RUBY.initialize(/home/nelson/Desktop/Directories/projects/applications/java/runescape/apollo-master/game/./data/plugins/skill/firemaking/firemake.rb:53)
    at RUBY.fm_action(/home/nelson/Desktop/Directories/projects/applications/java/runescape/apollo-master/game/./data/plugins/skill/firemaking/firemake.rb:31)
    at RUBY.block in (root)(/home/nelson/Desktop/Directories/projects/applications/java/runescape/apollo-master/game/./data/plugins/skill/firemaking/firemake.rb:43)
    at org.jruby.RubyProc.call(org/jruby/RubyProc.java:318)
    at RUBY.handle(/home/nelson/Desktop/Directories/projects/applications/java/runescape/apollo-master/game/./data/plugins/bootstrap.rb:91)

To fix go to line 31 in firemake.rb and change to

player.start_action(FiremakingAction.new(player, log))