Housekeeping

This commit is contained in:
KeepBotting
2019-03-26 14:05:40 -04:00
parent a0c78ced90
commit 739c331860
135 changed files with 4 additions and 4 deletions
+2 -2
View File
@@ -31,7 +31,7 @@ dependencies {
testImplementation group: 'org.assertj', name: 'assertj-core', version: assertjVersion
project(":game:plugin").subprojects { pluginProject ->
plugins.withId('apollo-plugin') {
if (pluginProject.buildFile.exists()) {
runtimeClasspath pluginProject
}
}
@@ -41,4 +41,4 @@ applicationDistribution.from("$rootDir/data") {
include '*.dat'
include '*.xml'
into "data/"
}
}
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+3
View File
@@ -0,0 +1,3 @@
<login>
<serializer>org.apollo.game.io.player.DummyPlayerSerializer</serializer>
</login>
+111
View File
@@ -0,0 +1,111 @@
<messages>
<message>
<type>org.apollo.game.message.impl.ButtonMessage</type>
<chain>
<handler>org.apollo.game.message.handler.DialogueButtonHandler</handler>
<handler>org.apollo.game.message.handler.BankButtonMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.PublicChatMessage</type>
<chain>
<handler>org.apollo.game.message.handler.PublicChatVerificationHandler</handler>
<handler>org.apollo.game.message.handler.PublicChatMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.ClosedInterfaceMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ClosedInterfaceMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.CommandMessage</type>
<chain>
<handler>org.apollo.game.message.handler.CommandMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.DialogueContinueMessage</type>
<chain>
<handler>org.apollo.game.message.handler.DialogueContinueMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.EnteredAmountMessage</type>
<chain>
<handler>org.apollo.game.message.handler.EnteredAmountMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.ItemActionMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ItemVerificationHandler</handler>
<handler>org.apollo.game.message.handler.RemoveEquippedItemHandler</handler>
<handler>org.apollo.game.message.handler.BankMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.ItemOnItemMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ItemVerificationHandler</handler>
<handler>org.apollo.game.message.handler.ItemOnItemVerificationHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.ItemOnObjectMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ItemOnObjectVerificationHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.ItemOptionMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ItemVerificationHandler</handler>
<handler>org.apollo.game.message.handler.EquipItemHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.MagicOnItemMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ItemVerificationHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.NpcActionMessage</type>
<chain>
<handler>org.apollo.game.message.handler.NpcActionVerificationHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.ObjectActionMessage</type>
<chain>
<handler>org.apollo.game.message.handler.ObjectActionVerificationHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.PlayerActionMessage</type>
<chain>
<handler>org.apollo.game.message.handler.PlayerActionVerificationHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.PlayerDesignMessage</type>
<chain>
<handler>org.apollo.game.message.handler.PlayerDesignVerificationHandler</handler>
<handler>org.apollo.game.message.handler.PlayerDesignMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.SwitchItemMessage</type>
<chain>
<handler>org.apollo.game.message.handler.SwitchItemMessageHandler</handler>
</chain>
</message>
<message>
<type>org.apollo.game.message.impl.WalkMessage</type>
<chain>
<handler>org.apollo.game.message.handler.WalkMessageHandler</handler>
</chain>
</message>
</messages>
+7
View File
@@ -0,0 +1,7 @@
<net>
<ports>
<http>80</http>
<service>43594</service>
<jaggrab>43595</jaggrab>
</ports>
</net>
+36
View File
@@ -0,0 +1,36 @@
Metrics/AbcSize:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/LineLength:
Max: 100
Metrics/MethodLength:
Max: 30
Metrics/PerceivedComplexity:
Enabled: false
Style/CaseIndentation:
IndentOneStep: true
Style/EmptyLinesAroundBlockBody:
Enabled: false
Style/EmptyLinesAroundClassBody:
Enabled: false
Style/EmptyLinesAroundModuleBody:
Enabled: false
Style/GlobalVars:
Enabled: false
Style/MethodName: # Disabled so we can override Java methods without rubocop complaining
Enabled: false
Style/ParallelAssignment:
Enabled: false
+49
View File
@@ -0,0 +1,49 @@
require 'java'
java_import 'org.apollo.game.message.impl.DisplayCrossbonesMessage'
java_import 'org.apollo.game.model.entity.Player'
# Registers an area action.
def area_action(name, &block)
AREA_ACTIONS[name] = action = AreaAction.new
action.instance_eval(&block)
end
AREA_ACTIONS = {}
private
# An action that is called when a player enters or exits an area.
class AreaAction
# Sets the block to be called when the player enters the area.
def on_entry(&block)
@on_enter = block
end
# Sets the block to be called while the player is in the area.
def while_in(&block)
@while_in = block
end
# Sets the block to be called when the player exits the area.
def on_exit(&block)
@on_exit = block
end
# Called when the player has entered an area this action is registered to.
def entered(player, position)
@on_enter.call(player, position) unless @on_enter.nil?
end
# Called while the player is in area this action is registered to.
def inside(player, position)
@while_in.call(player, position) unless @while_in.nil?
end
# Called when the player has exited an area this action is registered to.
def exited(player, position)
@on_exit.call(player, position) unless @on_exit.nil?
end
end
+108
View File
@@ -0,0 +1,108 @@
require 'java'
java_import 'org.apollo.game.model.Position'
java_import 'org.apollo.game.model.entity.EntityType'
java_import 'org.apollo.game.model.entity.Player'
# Creates a new area and registers it with the supplied coordinates.
def area(hash)
failure_message = 'Hash must contain a name, coordinates, and actions pair.'
fail failure_message unless hash.has_keys?(:name, :coordinates, :actions)
name, coordinates, actions = hash[:name], hash[:coordinates], hash[:actions]
actions = [actions] if actions.is_a?(Symbol)
actions.map! { |action| AREA_ACTIONS[action] }
@areas << Area.new(name, coordinates, actions)
end
private
# A map of coordinates (as an array) to areas.
@areas = []
# An area of the game world.
class Area
def initialize(name, coordinates, actions)
@name = name
@coordinates = coordinates
@actions = actions
end
def min_x # TODO: better data structure and methods than this
@coordinates[0]
end
def min_y
@coordinates[1]
end
def max_x
@coordinates[2]
end
def max_y
@coordinates[3]
end
def height
@coordinates[4]
end
# Called when the player has entered the area.
def entered(player, position)
@actions.each { |action| action.entered(player, position) }
end
# Called when the player has moved, but is still inside the area (and was in the area before).
def inside(player, position)
@actions.each { |action| action.inside(player, position) }
end
# Called when the player has exited the area.
def exited(player, position)
@actions.each { |action| action.exited(player, position) }
end
end
on :login do |event|
player = event.player
@areas.each do |area|
area.entered(player) if player.position.inside(area)
end
end
# Listen for the MobPositionUpdateEvent and update the area listeners if appropriate.
on :mob_position_update do |event|
mob = event.mob
next unless mob.entity_type == EntityType::PLAYER
old = mob.position
updated = event.next
@areas.each do |area|
was_inside = old.inside(area)
next_inside = updated.inside(area)
if was_inside
next_inside ? area.inside(mob, updated) : area.exited(mob, updated)
else
area.entered(mob, updated) if next_inside
end
end
end
# The existing Position class.
class Position
# Returns whether or not this Position is inside the specified Area.
def inside(area)
return false if x < area.min_x || x > area.max_x || y < area.min_y || y > area.max_y
z = area.height
z.nil? || z == height
end
end
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<plugin>
<id>areas</id>
<version>0.9</version>
<name>Areas</name>
<description>Adds support for areas.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>actions.rb</script>
<script>areas.rb</script>
</scripts>
<dependencies />
</plugin>
+47
View File
@@ -0,0 +1,47 @@
require 'java'
java_import 'org.apollo.game.action.DistancedAction'
java_import 'org.apollo.game.model.inter.bank.BankUtils'
BANK_BOOTH_ID = 2213
BANK_BOOTH_SIZE = 1
# The npcs with a 'bank' menu action.
BANKER_NPCS = [166, 494, 495, 496, 497, 498, 499, 1036, 1360, 1702, 2163, 2164, 2354, 2355, 2568,
2569, 2570]
# A distanced action to open a new bank.
class BankAction < DistancedAction
attr_reader :position
def initialize(mob, position)
super(0, true, mob, position, BANK_BOOTH_SIZE)
@position = position
end
def executeAction
mob.turn_to(@position)
BankUtils.open_bank(mob)
stop
end
def equals(other)
get_class == other.get_class && @position == other.position
end
end
# Intercepts the object action message
on :message, :second_object_action do |player, message|
if message.id == BANK_BOOTH_ID
player.start_action(BankAction.new(player, message.position))
message.terminate
end
end
on :message, :second_npc_action do |player, message|
npc = $world.npc_repository.get(message.index)
if BANKER_NPCS.include?(npc.id)
player.start_action(BankAction.new(player, npc.position))
message.terminate
end
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>bank</id>
<version>1</version>
<name>Bank</name>
<description>Opens the bank interface when players select 'use-quickly' on a bank booth.</description>
<authors>
<author>Graham</author>
</authors>
<scripts>
<script>bank.rb</script>
</scripts>
<dependencies />
</plugin>
+239
View File
@@ -0,0 +1,239 @@
# A script bootstrapper for the rest of the plugins, which wraps Apollo's
# verbose Java-style API in a Ruby-style API.
# ********************************** WARNING **********************************
# * If you do not really understand what this is for, do not edit it without *
# * creating a backup! Many plugins rely on the behaviour of this script, and *
# * will break if you mess it up. *
# * *
# * This is actually part of the core server and in an ideal world shouldn't *
# * be changed. *
# *****************************************************************************
require 'java'
java_import 'org.apollo.game.command.CommandListener'
java_import 'org.apollo.game.message.handler.MessageHandler'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.entity.Player'
java_import 'org.apollo.game.model.event.EventListener'
java_import 'org.apollo.game.model.event.PlayerEvent'
java_import 'org.apollo.game.model.event.impl.LoginEvent'
java_import 'org.apollo.game.model.event.ProxyEvent'
java_import 'org.apollo.game.model.event.ProxyEventListener'
java_import 'org.apollo.game.model.entity.setting.PrivilegeLevel'
java_import 'org.apollo.game.scheduling.ScheduledTask'
java_import 'org.apollo.game.plugin.PluginContext'
# Alias the privilege levels.
RIGHTS_ADMIN = PrivilegeLevel::ADMINISTRATOR
RIGHTS_MOD = PrivilegeLevel::MODERATOR
RIGHTS_STANDARD = PrivilegeLevel::STANDARD
# Extends the (Ruby) String class with a method to convert a lower case,
# underscore delimited string to camel-case.
class String
# Converts a ruby snake_case string to camel-case.
def camelize
gsub(/(?:^|_)(.)/) { $1.upcase }
end
end
# A CommandListener that executes a Proc object with two arguments: the player and the command.
class ProcCommandListener < CommandListener
# Creates the ProcCommandListener.
def initialize(rights, block)
super(rights)
@block = block
end
# Executes the block listening for the command.
def execute(player, command)
@block.call(player, command)
end
end
# A LogoutListener that executes a Proc object with the player argument.
class ProcEventListener
java_implements EventListener
# Creates the ProcEventListener.
def initialize(block)
super()
@block = block
end
# Executes the block handling the Event.
def handle(event)
args = [event]
args << event.player if event.is_a?(PlayerEvent)
@block.call(*args)
end
end
# A MessageHandler which executes a Proc object with two arguments: the player and the message.
class ProcMessageHandler < MessageHandler
# Creates the ProcMessageHandler.
def initialize(block, option)
super($world)
@block = block
@option = option
end
# Handles the message.
def handle(player, message)
@block.call(player, message) if @option == 0 || @option == message.option
end
end
# A ScheduledTask which executes a Proc object with one argument (itself).
class ProcScheduledTask < ScheduledTask
# Creates the ProcScheduledTask.
def initialize(delay, immediate, block)
super(delay, immediate)
@block = block
end
# Executes the block.
def execute
@block.call(self)
end
end
# Schedules a ScheduledTask. Can be used in two ways: passing an existing
# ScheduledTask object or passing a block along with one or two parameters: the
# delay (in pulses) and, optionally, the immediate flag.
#
# If the immediate flag is not given, it defaults to false.
#
# The ScheduledTask object is passed to the block so that methods such as
# setDelay and stop can be called. execute MUST NOT be called - if it is, the
# behaviour is undefined (and most likely it'll be bad).
def schedule(*args, &block)
if block_given?
fail 'Invalid combination of arguments.' unless (1..2).include?(args.length)
delay = args[0]
immediate = args.length == 2 ? args[1] : false
$world.schedule(ProcScheduledTask.new(delay, immediate, block))
elsif args.length == 1
$world.schedule(args[0])
else
fail 'Invalid combination of arguments.'
end
end
@@proxy_listener = ProxyEventListener.new
$world.listen_for(ProxyEvent.java_class, @@proxy_listener)
# Defines some sort of action to take upon an message. The following types of message are currently
# valid:
#
# * :command
# * :message
# * :button
# * Any valid Event, as a symbol in ruby snake_case form.
#
# A command takes one or two arguments (the command name and optionally the minimum rights level to
# use it). The minimum rights level defaults to STANDARD. The block should have two arguments:
# player and command.
#
# An message takes no arguments. The block should have two arguments: the player and the message
# object.
#
# A button takes one argument (the id). The block should have one argument: the player who clicked
# the button.
def on(type, *args, &block)
case type
when :command then on_command(args, block)
when :message then on_message(args, block)
when :button then on_button(args, block)
else
class_name = type.to_s.camelize.concat('Event')
begin
type = Java::JavaClass.for_name("org.apollo.game.model.event.impl.#{class_name}")
rescue
@@proxy_listener.add(class_name, ProcEventListener.new(block))
return
end
$world.listen_for(type, ProcEventListener.new(block))
end
end
# Contains extension methods for World.
module WorldExtensions
# Overrides World#submit, providing special-case behaviour for Events defined in Ruby, which
# need to be wrapped in a ProxyEvent, until https://github.com/jruby/jruby/issues/2359 is
# resolved.
def submit(event)
if event.java_class.name.end_with?(".Event", ".PlayerEvent")
event = ProxyEvent.new(event.class.name, event)
end
super(event)
end
end
# Prepend the methods defined in WorldExtensions to World.
class World
prepend WorldExtensions
end
private
# Defines an action to be taken upon a button press.
def on_button(args, proc)
fail 'Button must have one argument.' unless args.length == 1
id = args[0].to_i
on :message, :button do |player, message|
proc.call(player) if message.widget_id == id
end
end
# Defines an action to be taken upon a message.
# The message can either be a symbol with the lowercase underscored class name, or the class itself.
def on_message(args, proc)
fail 'Message must have one or two arguments.' unless (1..2).include?(args.length)
numbers = %w(first second third fourth fifth)
message = args[0].to_s
option = 0
(0...numbers.length).each do |index|
number = numbers[index]
if message.start_with?(number)
option = index + 1
message = message[number.length + 1, message.length]
break
end
end
class_name = message.camelize.concat('Message')
message = Java::JavaClass.for_name("org.apollo.game.message.impl.#{class_name}")
$ctx.add_message_handler(message, ProcMessageHandler.new(proc, option))
end
# Defines an action to be taken upon a command.
def on_command(args, proc)
fail 'Command message must have one or two arguments.' unless (1..2).include?(args.length)
rights = args.length == 2 ? args[1] : RIGHTS_STANDARD
$world.command_dispatcher.register(args[0].to_s, ProcCommandListener.new(rights, proc))
end
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>chat-privacy</id>
<version>1</version>
<name>Chat privacy</name>
<description>Adds chat privacy support.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>privacy.rb</script>
</scripts>
<dependencies>
<dependency>private-messaging</dependency>
</dependencies>
</plugin>
+12
View File
@@ -0,0 +1,12 @@
require 'java'
java_import 'org.apollo.game.model.entity.setting.PrivacyState'
java_import 'org.apollo.game.message.impl.SendFriendMessage'
on :message, :privacy_option do |player, message|
player.chat_privacy = message.chat_privacy
player.friend_privacy = message.friend_privacy
player.trade_privacy = message.trade_privacy
update_friends(player, message.friend_privacy == PrivacyState::OFF ? 0 : player.world_id)
end
@@ -0,0 +1,102 @@
require 'java'
java_import 'org.apollo.game.message.impl.FriendServerStatusMessage'
java_import 'org.apollo.game.message.impl.IgnoreListMessage'
java_import 'org.apollo.game.message.impl.SendFriendMessage'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.entity.setting.ServerStatus'
java_import 'org.apollo.game.model.entity.setting.PrivacyState'
java_import 'org.apollo.game.model.entity.Player'
# Processes an add friend message, updating the logged-in status of the player (and the person they
# added) if necessary.
on :message, :add_friend do |player, message|
friend_username = message.username
player_username = player.username
player.add_friend(friend_username)
friend = $world.get_player(friend_username)
if friend.nil? # the friend the player added is offline
player.send(SendFriendMessage.new(friend_username, 0))
elsif friend.friends_with(player_username)
# player's private chat state is not off, so notify the friend
unless player.friend_privacy == PrivacyState::OFF
friend.send(SendFriendMessage.new(player_username, player.world_id))
end
# new friend's private chat state is not off, so notify the player
unless friend.friend_privacy == PrivacyState::OFF
player.send(SendFriendMessage.new(friend_username, friend.world_id))
end
elsif friend.friend_privacy == PrivacyState::ON
# new friend doesn't have the player added but their private chat state is on, so inform the
# player of the world they are on.
player.send(SendFriendMessage.new(friend_username, friend.world_id))
end
end
# Processes a remove friend message, updating the logged-in status of the player if necessary.
on :message, :remove_friend do |player, message|
friend_username = message.username
player_username = player.username
player.remove_friend(friend_username)
if $world.is_player_online(friend_username)
friend = $world.get_player(friend_username)
remove = friend.friends_with(player_username) && player.friend_privacy != PrivacyState::ON
friend.send(SendFriendMessage.new(player_username, 0)) if remove
end
end
# Update the friend server status and send the friend/ignore lists of the player logging in.
on :login do |_event, player|
player.send(FriendServerStatusMessage.new(ServerStatus::CONNECTING))
player.send(IgnoreListMessage.new(player.ignored_usernames)) if player.ignored_usernames.size > 0
username = player.username
world = $world
iterator = player.friend_usernames.iterator
# Iterate the player's friend list and notify the player that they are online if they are
while iterator.has_next
friend_username = iterator.next
friend = world.get_player(friend_username)
friend_world_id = (friend.nil? || !viewable?(friend, username)) ? 0 : friend.world_id
player.send(SendFriendMessage.new(friend_username, friend_world_id))
end
player.send(FriendServerStatusMessage.new(ServerStatus::ONLINE))
update_friends(player, player.world_id)
end
# Notifies the player's friends that the player has logged out.
on :logout do |_event, player|
update_friends(player, 0)
end
# Notifies the currently logged in players that the specified player has logged into the specified
# world, unless the newly logged-in player has their friend privacy state set to 'off'.
def update_friends(player, world = 0)
privacy = player.friend_privacy
iterator = $world.player_repository.iterator
username = player.username
while iterator.has_next
other = iterator.next
next if !other.friends_with(username) || other == player
world = viewable?(player, other.username) ? world : 0
other.send(SendFriendMessage.new(username, world))
end
end
# Checks if the specified player can be viewed by the player with the specified other username
def viewable?(player, other_username)
privacy = player.friend_privacy
privacy != PrivacyState::OFF && player.friends_with(other_username) || privacy == PrivacyState::ON
end
@@ -0,0 +1,9 @@
on :message, :add_ignore do |player, message|
username = message.username
player.add_ignore(username)
end
on :message, :remove_ignore do |player, message|
username = message.username
player.remove_ignore(username)
end
@@ -0,0 +1,23 @@
require 'java'
java_import 'org.apollo.game.message.impl.ForwardPrivateChatMessage'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.entity.setting.PrivacyState'
on :message, :private_chat do |player, message|
friend = $world.get_player(message.username)
if interaction_permitted(player, friend)
chat = message.compressed_chat
friend.send(ForwardPrivateChatMessage.new(player.username, player.privilege_level, chat))
end
end
# Checks if the sender is permitted to interact with the friend they have added:
def interaction_permitted(sender, friend)
username = sender.username
return false if friend.nil? || friend.has_ignored(username)
privacy = friend.friend_privacy
friend.friends_with(username) ? (privacy != PrivacyState::OFF) : (privacy == PrivacyState::ON)
end
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>private-messaging</id>
<version>1</version>
<name>Private Messaging</name>
<description>Adds friend and ignore list support, and private messaging.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>friend.rb</script>
<script>ignore.rb</script>
<script>messaging.rb</script>
</scripts>
<dependencies />
</plugin>
+20
View File
@@ -0,0 +1,20 @@
require 'java'
java_import 'org.apollo.game.model.Animation'
java_import 'org.apollo.game.model.Graphic'
# Makes the player perform the animation with the specified id.
on :command, :animate, RIGHTS_MOD do |player, command|
args = command.arguments
next unless valid_arg_length(args, 1, player, 'Invalid syntax - ::animate [animation-id]')
player.play_animation(Animation.new(args[0].to_i))
end
# Makes the player perform the graphic with the specified id.
on :command, :graphic, RIGHTS_MOD do |player, command|
args = command.arguments
next unless valid_arg_length(args, 1, player, 'Invalid syntax - ::graphic [graphic-id]')
player.play_graphic(Graphic.new(args[0].to_i))
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-animate</id>
<version>1</version>
<name>Animate Commands</name>
<description>Adds animation-related commands.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>animate.rb</script>
</scripts>
<dependencies />
</plugin>
+6
View File
@@ -0,0 +1,6 @@
require 'java'
# Opens the player's bank.
on :command, :bank, RIGHTS_ADMIN do |player, _command|
player.open_bank
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-bank</id>
<version>1</version>
<name>Bank Command</name>
<description>Adds a ::bank command.</description>
<authors>
<author>Graham</author>
</authors>
<scripts>
<script>bank.rb</script>
</scripts>
<dependencies />
</plugin>
+45
View File
@@ -0,0 +1,45 @@
require 'java'
java_import 'org.apollo.cache.def.ItemDefinition'
# Adds the specified item to the player's inventory.
on :command, :item, RIGHTS_ADMIN do |player, command|
args = command.arguments
next unless valid_arg_length(args, (1..2), player, 'Invalid syntax - ::item [id] [amount]')
id = args[0].to_i
amount = args.length == 2 ? args[1].to_i : 1
if id < 0 || id >= ItemDefinition.count
player.send_message('The item id you specified is out of bounds!')
next
end
player.inventory.add(id, amount)
end
# Removes the specified item from the player's inventory.
on :command, :remove, RIGHTS_MOD do |player, command|
args = command.arguments
next unless valid_arg_length(args, (1..2), player, 'Invalid syntax - ::remove [id] [amount]')
id = args[0].to_i
amount = args.length == 2 ? args[1].to_i : 1
if id < 0 || id >= ItemDefinition.count
player.send_message('The item id you specified is out of bounds!')
next
end
player.inventory.remove(id, amount)
end
# Clears the player's inventory.
on :command, :empty, RIGHTS_MOD do |player, _command|
player.inventory.clear
end
# Gives the player 1,000 of each rune.
on :command, :runes, RIGHTS_ADMIN do |player, _command|
(554..566).each { |item| player.inventory.add(item, 1000) }
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-item</id>
<version>1</version>
<name>Item Commands</name>
<description>Adds ::item, ::remove and ::empty commands.</description>
<authors>
<author>Graham</author>
</authors>
<scripts>
<script>item.rb</script>
</scripts>
<dependencies />
</plugin>
+69
View File
@@ -0,0 +1,69 @@
require 'java'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.cache.def.ItemDefinition'
java_import 'org.apollo.cache.def.NpcDefinition'
java_import 'org.apollo.cache.def.ObjectDefinition'
java_import 'org.apollo.game.model.entity.Entity'
java_import 'org.apollo.game.model.entity.Player'
on :command, :lookup, RIGHTS_ADMIN do |player, command|
args = command.arguments.to_a
message = 'Invalid syntax - ::lookup [npc/object/item] [name]'
next unless valid_arg_length(args, (1..10), player, message)
type = args.shift.downcase
limit = args.first.to_i == 0 ? 5 : args.shift.to_i
name = args.join(' ').downcase
if %w(npc object item).index(type).nil?
player.send_message('Invalid syntax - ::lookup [npc/object/item] [name]')
next
end
ids = find_entities(type, name, limit).join(', ')
message = ids.empty? ? "Could not find an #{type} called #{name}." :
"Possible ids for \"#{name}\" are: #{ids}."
player.send_message(message)
end
# Sends the user a message with information about the item with the specified id.
on :command, :iteminfo, RIGHTS_ADMIN do |player, command|
args = command.arguments
next unless valid_arg_length(args, 1, player, 'Invalid syntax - ::iteminfo [item id]')
id = args[0].to_i
definition = ItemDefinition.lookup(id)
members = definition.is_members_only ? 'members' : 'not members'
player.send_message("Item #{id} is called #{definition.name}, is #{members} only, and has a "\
"team of #{definition.team}.")
player.send_message("Its description is \"#{definition.description}\".")
end
# Sends the user a message with information about the npc with the specified id.
on :command, :npcinfo, RIGHTS_ADMIN do |player, command|
args = command.arguments
next unless valid_arg_length(args, 1, player, 'Invalid syntax - ::npcinfo [npc id]')
id = args[0].to_i
definition = NpcDefinition.lookup(id)
is_combative = definition.has_combat_level ? "has a combat level of #{definition.combat_level}" :
'does not have a combat level'
player.send_message("Npc #{id} is called #{definition.name} and #{is_combative}.")
player.send_message("Its description is \"#{definition.description}\".")
end
# Sends the user a message with information about the object with the specified id.
on :command, :objectinfo, RIGHTS_ADMIN do |player, command|
args = command.arguments
next unless valid_arg_length(args, 1, player, 'Invalid syntax - ::objectinfo [object id]')
id = args[0].to_i
definition = ObjectDefinition.lookup(id)
player.send_message("Object #{id} is called #{definition.name} and its description is "\
"\"#{definition.description}\".")
player.send_message("Its width is #{definition.width} and its length is #{definition.length}.")
end
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-lookup</id>
<version>1</version>
<name>Lookup Command</name>
<description>Adds a ::lookup command.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>lookup.rb</script>
</scripts>
<dependencies>
<dependency>util</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,12 @@
require 'java'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.entity.Player'
# Adds a command to broadcast a message to every player on the server.
on :command, :broadcast, RIGHTS_ADMIN do |player, command|
message = command.arguments.to_a.join(' ')
broadcast = "[Broadcast] #{player.get_username.capitalize}: #{message}"
$world.player_repository.each { |other| other.send_message(broadcast) }
end
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<plugin>
<id>message</id>
<version>1</version>
<name>Messaging commands</name>
<description>Adds various message-related commands, such as enabling the server-side chat filter or broadcasting a message.</description>
<authors>
<author>Major</author>
<author>xEliqa</author>
</authors>
<scripts>
<script>broadcast.rb</script>
</scripts>
<dependencies />
</plugin>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-npc</id>
<version>1</version>
<name>Npc Commands</name>
<description>Adds npc-related commands.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>spawn.rb</script>
</scripts>
<dependencies />
</plugin>
+68
View File
@@ -0,0 +1,68 @@
require 'java'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.Position'
java_import 'org.apollo.game.model.entity.Npc'
# An array of npcs that cannot be spawned.
blacklist = []
# Spawns a non-blacklisted npc in the specified position, or the player's position if both 'x' and
# 'y' are not supplied.
on :command, :spawn, RIGHTS_ADMIN do |player, command|
args = command.arguments
unless [1, 3, 4].include?(args.length) && (id = args[0].to_i) > -1
player.send_message('Invalid syntax - ::spawn [npc id] [optional-x] [optional-y] [optional-z]')
return
end
if blacklist.include?(id)
player.send_message("Sorry, npc #{id} is blacklisted!")
return
end
if args.length == 1
position = player.position
else
height = args.length == 4 ? args[3].to_i : player.position.height
position = Position.new(args[1].to_i, args[2].to_i, height)
end
$world.register(Npc.new($world, id, position))
end
# Mass spawns npcs around the player.
on :command, :mass, RIGHTS_ADMIN do |player, command|
args = command.arguments
unless args.length == 2 && (id = args[0].to_i) > -1 && (1..5).include?(range = args[1].to_i)
player.send_message('Invalid syntax - ::spawn [npc id] [range (1-5)]')
return
end
if blacklist.include?(id)
player.send_message("Sorry, npc #{id} is blacklisted!")
return
end
center_position = player.position
min_x = center_position.x - range
min_y = center_position.y - range
max_x = center_position.x + range
max_y = center_position.y + range
z = center_position.height
(min_x..max_x).each do |x|
(min_y..max_y).each do |y|
$world.register(Npc.new($world, id, Position.new(x, y, z)))
end
end
player.send_message("Mass spawning npcs with id #{id}.")
end
# Unregisters all npcs from the world npc repository.
on :command, :clearnpcs, RIGHTS_ADMIN do |player, _command|
$world.npc_repository.each { |npc| $world.unregister(npc) }
player.send_message('Unregistered all npcs from the world.')
end
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>punishment</id>
<version>1</version>
<name>Punishment commands</name>
<description>Adds various punishment commands, such as banning or muting a player.</description>
<authors>
<author>lare96</author>
</authors>
<scripts>
<script>punish.rb</script>
</scripts>
<dependencies />
</plugin>
@@ -0,0 +1,53 @@
require 'java'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.entity.Player'
# Adds a command to mute a player. Admins cannot be muted.
on :command, :mute, RIGHTS_MOD do |player, command|
name = command.arguments.to_a.join(' ')
on_player = $world.get_player(name)
if validate(player, on_player)
on_player.muted = true
on_player.send_message('You have just been muted.')
player.send_message("You have just muted #{on_player.get_username}.")
end
end
# Adds a command to unmute a player.
on :command, :unmute, RIGHTS_MOD do |player, command|
name = command.arguments.to_a.join(' ')
on_player = $world.get_player(name)
if validate(player, on_player)
on_player.muted = false
on_player.send_message('You are no longer muted.')
player.send_message("You have just unmuted #{on_player.get_username}.")
end
end
# Adds a command to ban a player. Admins cannot be banned.
on :command, :ban, RIGHTS_ADMIN do |player, command|
name = command.arguments.to_a.join(' ')
on_player = $world.get_player(name)
if validate(player, on_player)
on_player.banned = true
on_player.logout # TODO force logout
player.send_message("You have just banned #{on_player.get_username}.")
end
end
# Ensures the player isn't nil, and that they aren't an Administrator.
def validate(player, on_player)
if on_player.nil?
player.send_message('That player does not exist.')
return false
elsif on_player.get_privilege_level == RIGHTS_ADMIN
player.send_message('You cannot perform this action on Administrators.')
return false
end
true
end
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-skill</id>
<version>1</version>
<name>Skill Commands</name>
<description>Adds skill-related commands.</description>
<authors>
<author>Graham</author>
<author>Major</author>
</authors>
<scripts>
<script>skill.rb</script>
</scripts>
<dependencies />
</plugin>
+44
View File
@@ -0,0 +1,44 @@
require 'java'
java_import 'org.apollo.game.model.entity.SkillSet'
java_import 'org.apollo.game.model.entity.Skill'
# Maximises the player's skill set.
on :command, :max, RIGHTS_ADMIN do |player, _command|
skills = player.skill_set
(0...skills.size).each do |skill|
skills.add_experience(skill, SkillSet::MAXIMUM_EXP)
end
end
# Levels the specified skill to the specified level, optionally updating the current level as well.
on :command, :level, RIGHTS_ADMIN do |player, command|
args = command.arguments
unless (2..3).include?(args.length) && (0..20).include?(skill_id = args[0].to_i) &&
(1..99).include?(level = args[1].to_i)
player.send_message('Invalid syntax - ::level [skill-id] [level]')
next
end
experience = SkillSet.get_experience_for_level(level)
current = level
if args.length == 3 && args[2].to_s == 'old'
skill = player.skill_set.skill(skill_id)
current = skill.current_level
end
player.skill_set.set_skill(skill_id, Skill.new(experience, current, level))
end
# Adds the specified amount of experience to the specified skill.
on :command, :xp, RIGHTS_ADMIN do |player, command|
args = command.arguments
unless args.length == 2 && (0..20).include?(skill_id = args[0].to_i) &&
(experience = args[1].to_i) >= 0
player.send_message('Invalid syntax - ::xp [skill-id] [experience]')
return
end
player.skill_set.add_experience(skill_id, experience)
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>cmd-teleport</id>
<version>1</version>
<name>Teleport Commands</name>
<description>Adds ::pos and ::tele commands.</description>
<authors>
<author>Graham</author>
</authors>
<scripts>
<script>teleport.rb</script>
</scripts>
<dependencies />
</plugin>
@@ -0,0 +1,20 @@
require 'java'
java_import 'org.apollo.game.model.Position'
# Sends the player's position.
on :command, :pos, RIGHTS_MOD do |player, _command|
player.send_message("You are at: #{player.position}.")
end
# Teleports the player to the specified position.
on :command, :tele, RIGHTS_ADMIN do |player, command|
args = command.arguments
next unless valid_arg_length(args, (2..3), player, 'Invalid syntax - ::tele [x] [y] [optional-z]')
x = args[0].to_i
y = args[1].to_i
z = args.length == 3 ? args[2].to_i : player.position.height
player.teleport(Position.new(x, y, z)) if (0..4).include?(z)
end
+17
View File
@@ -0,0 +1,17 @@
<?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>
</plugin>
+66
View File
@@ -0,0 +1,66 @@
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
@@ -0,0 +1,79 @@
require 'java'
# A map of item ids to consumables.
CONSUMABLES = {}
# The id of the food consumption animation.
CONSUME_ANIMATION_ID = 829
# Contains the different types of consumables
module ConsumableType
FOOD = 1
POTION = 2
DRINK = 3
end
# An item that can be consumed to produce a skill effect.
class Consumable
attr_reader :name, :id, :sound, :delay, :type
def initialize(name, id, sound, delay, type)
@name = name.to_s.gsub(/_/, ' ')
@id = id
@sound = sound
@delay = delay
@type = type
end
def consume(_player)
# Override to provide specific functionality.
end
end
# Appends a consumable to the map, with its id as the key.
def append_consumable(consumable)
CONSUMABLES[consumable.id] = consumable
end
# An Action used for food consumption.
class ConsumeAction < Action
attr_reader :consumable
def initialize(player, slot, consumable)
super(2, true, player)
@consumable = consumable
@slot = slot
@executions = 0
end
def execute
if @executions == 0
mob.inventory.reset(@slot)
@consumable.consume(mob)
mob.play_animation(Animation.new(CONSUME_ANIMATION_ID))
end
@executions += 1
if @executions >= @consumable.delay
stop
end
end
def equals(other)
get_class == other.get_class && mob == other.mob && @consumable.type == other.consumable.type
end
end
# Intercepts the first item option message and consumes the consumable, if necessary.
on :message, :first_item_option do |player, message|
consumable = CONSUMABLES[message.id]
unless consumable.nil?
player.start_action(ConsumeAction.new(player, message.slot, consumable))
message.terminate
end
end
+76
View File
@@ -0,0 +1,76 @@
require 'java'
java_import 'org.apollo.game.model.Animation'
java_import 'org.apollo.game.model.entity.Skill'
java_import 'org.apollo.game.model.entity.Player'
private
# The id the of the sound made when drinking something.
#TODO change sound if this id is incorrect
DRINK_SOUND = 334
# Represents something drinkable, such as a jug of wine or a nettle tea.
class Drink < Consumable
def initialize(name, id, restoration, replace, delay)
super(name, id, DRINK_SOUND, delay, ConsumableType::DRINK)
@restoration = restoration
@replace = replace
end
# Restore the appropriate amount of hitpoints when consumed.
def consume(player)
hitpoints = player.skill_set.skill(Skill::HITPOINTS)
hitpoints_current = player.skill_set.get_current_level(Skill::HITPOINTS)
new_curr = [hitpoints.current_level + @restoration, hitpoints.maximum_level].min
player.inventory.add(@replace) unless @replace == -1
player.send_message("You drink the #{name}.")
player.send_message('It heals some health.') if new_curr > hitpoints_current
skill = Skill.new(hitpoints.experience, new_curr, hitpoints.maximum_level)
player.skill_set.set_skill(Skill::HITPOINTS, skill)
end
end
# The default delay before the consumable is drunk.
DEFAULT_DELAY = 3
# Appends a drink item to the list of consumables.
def drink(hash)
unless hash.has_keys?(:name, :id, :restoration)
fail 'Hash must contain a name, id, and a restoration value.'
end
name, id, restoration = hash[:name], hash[:id], hash[:restoration];
replace = hash[:replace] || -1
delay = hash[:delay] || DEFAULT_DELAY # TODO: ??
append_consumable(Drink.new(name, id, restoration, replace, delay))
end
# Wine
drink name: :jug_of_wine, id: 1993, restoration: 11
# Hot Drinks
drink name: :nettle_tea, id: 4239, restoration: 3
drink name: :nettle_tea, id: 4240, restoration: 3
# Gnome Cocktails
drink name: :fruit_blast, id: 2034, restoration: 9
drink name: :fruit_blast, id: 2084, restoration: 9
drink name: :pineapple_punch, id: 2036, restoration: 9
drink name: :pineapple_punch, id: 2048, restoration: 9
drink name: :wizard_blizzard, id: 2040, restoration: 5 # -4 attack, +5 strength also
drink name: :wizard_blizzard, id: 2054, restoration: 5 # -4 attack, +5 strength also
drink name: :short_green_guy, id: 2038, restoration: 5 # -4 attack, +5 strength also
drink name: :short_green_guy, id: 2080, restoration: 5 # -4 attack, +5 strength also
drink name: :drunk_dragon, id: 2032, restoration: 5 # -4 attack, +6 strength also
drink name: :drunk_dragon, id: 2092, restoration: 5 # -4 attack, +6 strength also
drink name: :chocolate_saturday, id: 2030, restoration: 7 # -4 attack, +6 strength also
drink name: :chocolate_saturday, id: 2074, restoration: 7 # -4 attack, +6 strength also
drink name: :blurberry_special, id: 2028, restoration: 7 # -4 attack, +6 strength also
drink name: :blurberry_special, id: 2064, restoration: 7 # -4 attack, +6 strength also
+221
View File
@@ -0,0 +1,221 @@
require 'java'
java_import 'org.apollo.game.model.Animation'
java_import 'org.apollo.game.model.entity.Skill'
java_import 'org.apollo.game.model.entity.Player'
private
# The id the of the sound made when eating food.
EAT_FOOD_SOUND = 317
# Represents an edible piece of food, such as bread or fish.
# TODO: delay eating times
class Food < Consumable
def initialize(name, id, restoration, replace, delay)
super(name, id, EAT_FOOD_SOUND, delay, ConsumableType::FOOD)
@restoration = restoration
@replace = replace
end
# Restore the appropriate amount of hitpoints when consumed.
def consume(player)
hitpoints = player.skill_set.skill(Skill::HITPOINTS)
hitpoints_current = player.skill_set.get_current_level(Skill::HITPOINTS)
new_curr = [hitpoints.current_level + @restoration, hitpoints.maximum_level].min
player.inventory.add(@replace) unless @replace == -1
player.send_message("You eat the #{name}.")
player.send_message('It heals some health.') if new_curr > hitpoints_current
skill = Skill.new(hitpoints.experience, new_curr, hitpoints.maximum_level)
player.skill_set.set_skill(Skill::HITPOINTS, skill)
end
end
# The default delay before a piece of food is eaten
DEFAULT_DELAY = 3
# Appends a food item to the list of consumables.
def food(hash)
unless hash.has_keys?(:name, :id, :restoration)
fail 'Hash must contain a name, id, and a restoration value.'
end
name, id, restoration = hash[:name], hash[:id], hash[:restoration];
replace = hash[:replace] || -1
delay = hash[:delay] || DEFAULT_DELAY # TODO: ??
append_consumable(Food.new(name, id, restoration, replace, delay))
end
# TODO: special effects
food name: :cooked_slimy_eel, id: 3381, restoration: 6 # this has a chance to heal 6 to 10 hp
food name: :thin_snail_meat, id: 3369, restoration: 5 # this has a chance to heal 5 to 7 hp
food name: :fat_snail_meat, id: 3373, restoration: 2 # this has a chance to heal 7 to 9 hp
food name: :watermelon_slice, id: 5984, restoration: 0 # this heals 5% of player's life
food name: :cooked_karambwan, id: 3146, restoration: 0 # poisons player(50)
food name: :spider_on_stick, id: 6297, restoration: 7 # heals between 7 and 10
food name: :spider_on_shaft, id: 6299, restoration: 7 # heals between 7 and 10
# Meats/Fish
food name: :anchovies, id: 319, restoration: 1
food name: :crab_meat, id: 7521, restoration: 2, replace: 7523
food name: :crab_meat, id: 7523, restoration: 2, replace: 7524
food name: :crab_meat, id: 7524, restoration: 2, replace: 7525
food name: :crab_meat, id: 7525, restoration: 2, replace: 7526
food name: :crab_meat, id: 7526, restoration: 2
food name: :shrimp, id: 315, restoration: 3
food name: :sardine, id: 325, restoration: 3
food name: :cooked_meat, id: 2142, restoration: 3
food name: :cooked_chicken, id: 2140, restoration: 3
food name: :ugthanki_meat, id: 1861, restoration: 3
food name: :karambwanji, id: 3151, restoration: 3
food name: :cooked_rabbit, id: 3228, restoration: 5
food name: :herring, id: 347, restoration: 6
food name: :trout, id: 333, restoration: 7
food name: :cod, id: 339, restoration: 7
food name: :mackeral, id: 355, restoration: 7
food name: :roast_rabbit, id: 7223, restoration: 7
food name: :pike, id: 351, restoration: 8
food name: :lean_snail_meat, id: 3371, restoration: 8
food name: :salmon, id: 329, restoration: 9
food name: :tuna, id: 361, restoration: 10
food name: :lobster, id: 379, restoration: 12
food name: :bass, id: 365, restoration: 13
food name: :swordfish, id: 373, restoration: 14
food name: :cooked_jubbly, id: 7568, restoration: 15
food name: :monkfish, id: 7946, restoration: 16
food name: :cooked_karambwan, id: 3144, restoration: 18, delay: 0
food name: :shark, id: 385, restoration: 20
food name: :sea_turtle, id: 397, restoration: 21
food name: :manta_ray, id: 391, restoration: 22
# Breads/Wraps
food name: :bread, id: 2309, restoration: 5
food name: :oomlie_wrap, id: 2343, restoration: 14
food name: :ugthanki_kebab, id: 1883, restoration: 19
# Fruits
food name: :banana, id: 1963, restoration: 2
food name: :sliced_banana, id: 3162, restoration: 2
food name: :lemon, id: 2102, restoration: 2
food name: :lemon_chunks, id: 2104, restoration: 2
food name: :lemon_slices, id: 2106, restoration: 2
food name: :lime, id: 2120, restoration: 2
food name: :lime_chunks, id: 2122, restoration: 2
food name: :lime_slices, id: 2124, restoration: 2
food name: :strawberry, id: 5504, restoration: 5
food name: :papaya_fruit, id: 5972, restoration: 8
food name: :pineapple_chunks, id: 2116, restoration: 2
food name: :pineapple_ring, id: 2118, restoration: 2
food name: :orange, id: 2108, restoration: 2
food name: :orange_rings, id: 2110, restoration: 2
food name: :orange_slices, id: 2112, restoration: 2
# Pies
# TODO: pie special effects (e.g. fish pie raises fishing level)
food name: :redberry_pie, id: 2325, restoration: 5, replace: 2333, delay: 1
food name: :redberry_pie, id: 2333, restoration: 5, delay: 1
food name: :meat_pie, id: 2327, restoration: 6, replace: 2331, delay: 1
food name: :meat_pie, id: 2331, restoration: 6, delay: 1
food name: :apple_pie, id: 2323, restoration: 7, replace: 2335, delay: 1
food name: :apple_pie, id: 2335, restoration: 7, delay: 1
food name: :fish_pie, id: 7188, restoration: 6, replace: 7190, delay: 1
food name: :fish_pie, id: 7190, restoration: 6, delay: 1
food name: :admiral_pie, id: 7198, restoration: 8, replace: 7200, delay: 1
food name: :admiral_pie, id: 7200, restoration: 8, delay: 1
food name: :wild_pie, id: 7208, restoration: 11, replace: 7210, delay: 1
food name: :wild_pie, id: 7210, restoration: 11, delay: 1
food name: :summer_pie, id: 7218, restoration: 11, replace: 7220, delay: 1
food name: :summer_pie, id: 7220, restoration: 11, delay: 1
# Stews
food name: :stew, id: 2003, restoration: 11
food name: :banana_stew, id: 4016, restoration: 11
food name: :curry, id: 2011, restoration: 19
# Pizzas
food name: :plain_pizza, id: 2289, restoration: 7, replace: 2291
food name: :plain_pizza, id: 2291, restoration: 7
food name: :meat_pizza, id: 2293, restoration: 8, replace: 2295
food name: :meat_pizza, id: 2295, restoration: 8
food name: :anchovy_pizza, id: 2297, restoration: 9, replace: 2299
food name: :anchovy_pizza, id: 2299, restoration: 9
food name: :pineapple_pizza, id: 2301, restoration: 11, replace: 2303
food name: :pineapple_pizza, id: 2303, restoration: 11
# Cakes
food name: :fishcake, id: 7530, restoration: 11
food name: :cake, id: 1891, restoration: 4, replace: 1893
food name: :cake, id: 1893, restoration: 4, replace: 1895
food name: :cake, id: 1895, restoration: 4
food name: :chocolate_cake, id: 1897, restoration: 5, replace: 1899
food name: :chocolate_cake, id: 1899, restoration: 5, replace: 1901
food name: :chocolate_cake, id: 1901, restoration: 5
# Vegetables
food name: :potato, id: 1942, restoration: 1
food name: :spinach_roll, id: 1969, restoration: 2
food name: :baked_potato, id: 6701, restoration: 4
food name: :sweetcorn, id: 5988, restoration: 10
food name: :sweetcorn_bowl, id: 7088, restoration: 13
food name: :potato_with_butter, id: 6703, restoration: 14
food name: :chili_potato, id: 7054, restoration: 14
food name: :potato_with_cheese, id: 6705, restoration: 16
food name: :egg_potato, id: 7056, restoration: 16
food name: :mushroom_potato, id: 7058, restoration: 20
food name: :tuna_potato, id: 7060, restoration: 22
# Dairy
food name: :cheese, id: 1985, restoration: 2
food name: :pot_of_cream, id: 2130, restoration: 1
# Gnome Food
food name: :toads_legs, id: 2152, restoration: 3
# Gnome Bowls
food name: :worm_hole, id: 2191, restoration: 12
food name: :worm_hole, id: 2233, restoration: 12
food name: :vegetable_ball, id: 2195, restoration: 12
food name: :vegetable_ball, id: 2235, restoration: 12
food name: :tangled_toads_legs, id: 2187, restoration: 15
food name: :tangled_toads_legs, id: 2231, restoration: 15
food name: :chocolate_bomb, id: 2185, restoration: 15
food name: :chocolate_bomb, id: 2229, restoration: 15
# Gnome Crunchies
food name: :toad_crunchies, id: 2217, restoration: 7
food name: :toad_crunchies, id: 2243, restoration: 7
food name: :spicy_crunchies, id: 2213, restoration: 7
food name: :spicy_crunchies, id: 2241, restoration: 7
food name: :worm_crunchies, id: 2205, restoration: 8
food name: :worm_crunchies, id: 2237, restoration: 8
food name: :chocchip_crunchies, id: 2209, restoration: 7
food name: :chocchip_crunchies, id: 2239, restoration: 7
# Gnome Battas
food name: :fruit_batta, id: 2225, restoration: 11
food name: :fruit_batta, id: 2277, restoration: 11
food name: :toad_batta, id: 2221, restoration: 11
food name: :toad_batta, id: 2255, restoration: 11
food name: :worm_batta, id: 2219, restoration: 11
food name: :worm_batta, id: 2253, restoration: 11
food name: :vegetable_batta, id: 2227, restoration: 11
food name: :vegetable_batta, id: 2281, restoration: 11
food name: :cheese_tom_batta, id: 2223, restoration: 11
food name: :cheese_tom_batta, id: 2259, restoration: 11
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<plugin>
<id>consumables</id>
<version>0.1</version>
<name>Consumables</name>
<description>Adds support for consumables (e.g. potions, food).</description>
<authors>
<author>Major</author>
<author>Ryley</author>
</authors>
<scripts>
<script>consumable.rb</script>
<script>food.rb</script>
<script>potions.rb</script>
</scripts>
<dependencies />
</plugin>
+136
View File
@@ -0,0 +1,136 @@
require 'java'
java_import 'org.apollo.game.model.entity.Skill'
private
# Contains potion-related constants.
module Constants
# The id of the sound made when drinking a potion.
DRINK_POTION_SOUND = 334
# The id of an empty vial.
EMPTY_VIAL_ID = 229
# The delay between drinking potions
POTION_DELAY = 2
end
# A drinkable potion.
class Potion < Consumable
def initialize(id, name, doses)
super(name, id, Constants::DRINK_POTION_SOUND, Constants::POTION_DELAY, ConsumableType::POTION)
@doses = doses
end
def consume(player)
index = @doses.find_index(id) + 1
if index != @doses.length # Consumable removes the old potion for us, so don't do it here.
player.inventory.add(@doses[index])
player.send_message("You drink some of your #{name} potion.")
remaining = "You have #{@doses.length - index} dose#{'s' unless index == 3} of potion left."
player.send_message(remaining)
else
player.send_message('You drink the last of your potion.')
player.inventory.add(Constants::EMPTY_VIAL_ID)
end
drink(player)
end
def drink(_player)
# Override to provide functionality
end
end
# A potion that can be consumed to boost the current skill level of a stat (or stats).
class BoostingPotion < Potion
def initialize(id, name, doses, skills, boost)
super(id, name, doses)
@skill_ids = skills.is_a?(Array) ? skills : [skills]
@boost = boost
end
def drink(player)
@skill_ids.each do |id|
skill = player.skill_set.skill(id)
max = skill.maximum_level
level = [skill.current_level, max].min
new_current = @boost.call(max, level).floor
player.skill_set.set_skill(id, Skill.new(skill.experience, new_current, max))
end
end
end
# Returns the parameters for the potion, as an array. Fails if any of the specified keys do not
# exist.
def get_parameters(hash, keys)
fail "Hash must contain the following keys: #{keys.join(', ')}." unless hash.has_keys?(*keys)
keys.map { |key| hash[key] }
end
# Appends a potion to the list of consumables.
def potion(hash)
class_name = 'Potion'
keys = [:name, :doses]
unless (hash.size == 2)
keys << :skills << :boost
class_name.prepend('Boosting')
end
parameters = get_parameters(hash, keys)
doses = hash[:doses]
doses.each { |dose| append_consumable(Object.const_get(class_name).new(dose, *parameters)) }
end
# Some frequently-used boosts and skills
# Lambda parameters are | maximum_skill_level, current_skill_level |
basic_combat_boost = ->(_, level) { level * 1.10 + 3 }
super_combat_boost = ->(_, level) { level * 1.15 + 5 }
non_combat_boost = ->(_, level) { level + 3 }
ATTACK, STRENGTH, DEFENCE = Skill::ATTACK, Skill::STRENGTH, Skill::DEFENCE
MAGIC, RANGED, PRAYER = Skill::MAGIC, Skill::RANGED, Skill::PRAYER
all_skills = (Skill::ATTACK..Skill::RUNECRAFT).to_a
combat_skills = [ATTACK, STRENGTH, DEFENCE, MAGIC, RANGED]
# Boosting potions:
# Note that the order of the elements must be: :name, :doses, :skills, :boost.
potion name: :attack, doses: [2428, 121, 123, 125], skills: ATTACK, boost: basic_combat_boost
potion name: :strength, doses: [113, 115, 117, 119], skills: STRENGTH, boost: basic_combat_boost
potion name: :defence, doses: [2432, 133, 135, 137], skills: DEFENCE, boost: basic_combat_boost
potion name: :agility, doses: [3032, 3034, 3036, 3038], skills: Skill::AGILITY,
boost: non_combat_boost
potion name: :fishing, doses: [2438, 151, 153, 155], skills: Skill::FISHING,
boost: non_combat_boost
potion name: :prayer, doses: [2434, 139, 141, 143], skills: Skill::PRAYER,
boost: ->(_, level) { level / 4 + 7 }
potion name: :restore, doses: [2430, 127, 129, 131], skills: combat_skills,
boost: ->(_, level) { [level * 1.3 + 10, max].min }
potion name: :super_restore, doses: [3024, 3026, 3028, 3030], skills: all_skills,
boost: ->(_, level) { [level * 1.25 + 8, max].min }
potion name: :super_attack, doses: [2436, 145, 147, 149], skills: ATTACK, boost: super_combat_boost
potion name: :super_strength, doses: [2440, 157, 159, 161], skills: STRENGTH,
boost: super_combat_boost
potion name: :super_defence, doses: [2442, 163, 165, 167], skills: DEFENCE,
boost: super_combat_boost
potion name: :ranging, doses: [2444, 169, 171, 173], skills: RANGED,
boost: ->(_, level) { level * 1.10 + 4 }
potion name: :magic, doses: [3040, 3042, 3044, 3046], skills: MAGIC,
boost: ->(_, level) { level + 4 }
+567
View File
@@ -0,0 +1,567 @@
require 'java'
java_import 'org.apollo.game.model.inter.dialogue.DialogueAdapter'
java_import 'org.apollo.game.message.impl.CloseInterfaceMessage'
java_import 'org.apollo.game.message.impl.SetWidgetItemModelMessage'
java_import 'org.apollo.game.message.impl.SetWidgetNpcModelMessage'
java_import 'org.apollo.game.message.impl.SetWidgetPlayerModelMessage'
java_import 'org.apollo.game.message.impl.SetWidgetModelAnimationMessage'
java_import 'org.apollo.game.message.impl.SetWidgetTextMessage'
java_import 'org.apollo.game.action.DistancedAction'
# The map of conversation names to Conversations.
CONVERSATIONS = {}
# Declares a conversation.
def conversation(name, &block)
conversation = Conversation.new(name)
conversation.instance_eval(&block)
raise "Conversation named #{name} already exists." if CONVERSATIONS.has_key?(name)
CONVERSATIONS[name] = conversation
end
# A distanced action which opens the dialogue when getting into interaction distance of the given npc
class OpenDialogueAction < DistancedAction
attr_reader :player, :npc, :dialogue
def initialize(player, npc, dialogue)
super(0, true, player, npc.position, 1)
@player = player
@npc = npc
@dialogue = dialogue
end
def executeAction
@player.set_interacting_mob(@npc)
send_dialogue(@player, @dialogue)
stop
end
def equals(other)
return (@npc == other.npc && @dialogue == other.dialogue)
end
end
# A conversation held between two entities.
class Conversation
# Creates the Conversation.
def initialize(name)
@dialogues = {}
@starters = []
@name = name
end
# Defines a dialogue, with the specified name and block.
def dialogue(name, &block)
raise 'Dialogues must have a name and block.' if (name.nil? || block.nil?)
dialogue = Dialogue.new(name, self)
dialogue.instance_eval(&block)
dialogue.wrap
raise "Conversations #{@name} already has a dialogue named #{name}." if @dialogues.has_key?(name)
@dialogues[name] = dialogue
if ((@dialogues.empty? || dialogue.has_precondition?) && dialogue.type == :npc_speech)
npc_index = dialogue.npc
raise 'Npc cannot be null when opening a dialogue.' if npc_index.nil?
@starters << dialogue
on :message, :first_npc_action do |player, message|
npc = $world.npc_repository.get(message.index)
if npc_index == npc.id
@starters.each do |start|
if dialogue.precondition(player)
player.start_action(OpenDialogueAction.new(player, npc, dialogue))
message.terminate
break
end
end
end
end
end
end
# Gets part of a conversation (i.e. a dialogue).
def part(name)
raise "Conversation #{@name} does not contain a dialogue called #{name}." unless @dialogues.has_key?(name)
@dialogues[name]
end
end
# Declares an emote, with the specified name and id.
def declare_emote(name, id)
EMOTES[name] = id
end
# Sends the dialogue from the specified Conversation with the specified name.
def get_dialogue(conversation, name)
CONVERSATIONS[conversation].part(name)
end
# Sends the specified dialogue.
def send_dialogue(player, dialogue)
type = dialogue.type
action = dialogue.action
action.call(player) unless action.nil?
case type
when :message_with_item then send_item_dialogue(player, dialogue)
when :message_with_model then send_model_dialogue(player, dialogue)
when :npc_speech then send_npc_dialogue(player, dialogue)
when :options then send_options_dialogue(player, dialogue)
when :player_speech then send_player_dialogue(player, dialogue)
when :text
if dialogue.has_continue? then send_text_dialogue(player, dialogue) else send_statement_dialogue(player, dialogue) end
else raise "Unrecognised dialogue type #{type}."
end
end
private
# The hash of emote names to ids.
EMOTES = {}
# The maximum amount of lines of text that can be displayed on a dialogue.
MAXIMUM_LINE_COUNT = 4
# The maximum amount of options that can be displayed on a dialogue.
MAXIMUM_OPTION_COUNT = 5
# The maximum width of a line, in pixels, for a dialogue with media.
MAXIMUM_MEDIA_LINE_WIDTH = 350
# The maximum width of a line, in pixels, for a dialogue with no media.
MAXIMUM_LINE_WIDTH = 430
# The possible types of a dialogue.
DIALOGUE_TYPES = [ :message_with_item, :message_with_model, :npc_speech, :options, :player_speech, :text ]
# A type of dialogue.
class Dialogue
attr_reader :emote, :name, :media, :options, :text, :title, :type
# Initializes the Dialogue.
def initialize(name, conversation)
@name = name.to_s
@conversation = conversation
@text = []
@options = []
end
# An action that is executed when the dialogue is displayed.
def action(&block)
@action = block unless block.nil?
@action
end
# Closes the dialogue interface when the player clicks the 'Click here to continue...' text.
def close(&block)
continue(:close => true, &block)
end
# Defines the event that occurs when a player clicks the 'Click here to continue...' text.
def continue(type=nil, &block)
raise 'Cannot add a continue event on a dialogue with options.' if (@type == :options)
raise 'Must declare either a type or a block for a continue event.' if (type.nil? && block.nil?)
action = decode_next_dialogue(type) unless type.nil?
@options[0] = ->(player) { action.call(player) unless type.nil?; block.call(player) unless block.nil? }
end
# Sets the emote performed by the dialogue head.
def emote(emote=nil)
unless emote.nil?
raise 'Can only perform an emote on :player_speech or :npc_speech dialogues.' unless [ :npc_speech, :player_speech ].include?(@type)
@emote = emote.kind_of?(Symbol) ? EMOTES[emote] : emote
end
@emote
end
# Returns whether or not this Dialogue has a continue option.
def has_continue?
!@options.empty?
end
# Returns whether or not this dialogue has a precondition.
def has_precondition?
!@precondition.nil?
end
# Gets the media of this dialogue.
def media()
case @type
when :message_with_item then @item
when :npc_speech then @npc
when :message_with_model then @model
else raise "Cannot get media for #{@type}."
end
end
# Sets the id of the item displayed.
def item(item=nil, scale=100)
unless item.nil?
raise 'Can only display an item on :message_with_item dialogues.' unless @type == :message_with_item
@item = lookup_item(item)
@item_scale = scale
end
@item
end
# Gets the scale of the item.
def item_scale
@item_scale
end
# Sets the id of the model displayed.
def model(model=nil)
unless model.nil?
raise 'Can only display a model on :message_with_model dialogues.' unless @type == :message_with_model
@model = model
end
@model
end
# Sets the id of the npc displayed.
def npc(npc=nil)
unless npc.nil?
raise 'Can only display an npc on :npc_speech dialogues.' unless @type == :npc_speech
@npc = lookup_npc(npc)
end
@npc
end
# Defines an option, displaying the specified message.
def option(message, type)
raise 'Can only display options on an :options dialogue.' unless @type == :options
raise "Cannot display more than #{MAXIMUM_OPTION_COUNT} options on a dialogue." unless @options.size < MAXIMUM_OPTION_COUNT
@options[text.size] = decode_next_dialogue(type)
@text << message
end
# Gets the array of options.
def options
@options.dup
end
# Sets the precondition of this dialogue.
def precondition(player=nil, &block)
@precondition = block unless block.nil?
@precondition.call(player) unless player.nil?
end
# Appends a message to the text list.
def text(*message)
@text.concat(message) unless message.nil?
@text
end
# Sets the title of the dialogue.
def title(title=nil)
@title = title unless title.nil?
@title
end
# Sets the type of dialogue.
def type(type=nil)
unless type.nil?
verify_dialogue_type(type)
@type = type
end
@type
end
# Wraps text in this Dialogue, inserting extra Dialogues in the chain if necessary.
def wrap # TODO redo this
next if @type == :options
lines = []
maximum_lines = MAXIMUM_LINE_COUNT
text = @text.first
segments = segment_text(text)
if (segments.size <= maximum_lines)
lines = segments.clone
@text = @text[1..-1]
insert_copy(@text) if @text.size > 0
else
lines = segments.first(maximum_lines).clone
segments = [ segments.drop(maximum_lines).join() ]
insert_copy(segments << @text[1..-1].join())
end
@text = lines
end
protected
# Copies the value of every variable from the specified Dialogue, optionally updating the text array.
def copy_from(dialogue, text=nil)
@emote = dialogue.emote
@item = dialogue.item
@model = dialogue.model
@npc = dialogue.npc
@options = dialogue.options
@text = if text.nil? then dialogue.text.dup else text.dup end
@type = dialogue.type
end
private
def segment_text(text)
maximum_width = (@type == :text) ? MAXIMUM_LINE_WIDTH : MAXIMUM_MEDIA_LINE_WIDTH
segments = []
index = 0; width = 0; space = 0
while index < text.length
char = text[index]
space = index if char == ' '
width += get_width(char)
index += 1
if (width >= maximum_width)
segments << text[0..space]
text = text[(space + 1)..-1]
width = index = space = 0
end
end
segments << text if ! text.empty?
segments
end
# Inserts a copy of this Dialogue into the chain, but with different text.
def insert_copy(text)
name = @name
index = name.index('-auto-inserted-')
id = if index.nil? then 0 else name[name.rindex('-')..-1].to_i + 1 end
index ||= -1
name = "#{name[0..index]}-auto-inserted-#{id}"
dialogue = Dialogue.new(name, @conversation)
dialogue.copy_from(self, text.dup)
dialogue.wrap()
@options[0] = ->(player) { send_dialogue(player, dialogue) }
end
# Decodes the next dialogue interface from the hash, returning a proc.
def decode_next_dialogue(hash)
hash.each_pair do |key, value|
case key
when :disabled then return ->(player) { }
when :close then return ->(player) { player.send(CloseInterfaceMessage.new) }
when :dialogue then return ->(player) { send_dialogue(player, @conversation.part(value)) }
else raise "Unrecognised dialogue continue type #{key}."
end
end
end
end
# The dialogue interface ids for dialogues that only display text, but with no 'Click here to continue...' message.
STATEMENT_DIALOGUE_IDS = [ 12788, 12790, 12793, 12797, 6179 ] # TODO
# The dialogue interface ids for dialogues that display an item and text, ordered by line count.
ITEM_DIALOGUE_IDS = [ 306, 310, 315, 321 ]
# The dialogue interface ids for dialogues that only display text, ordered by line count.
TEXT_DIALOGUE_IDS = [ 356, 359, 363, 368, 374 ]
# The dialogue interface ids for dialogues that display the head of the player, ordered by line count.
PLAYER_DIALOGUE_IDS = [ 968, 973, 979, 986 ]
# The dialogue interface ids for dialogues that display the head of an npc, ordered by line count.
NPC_DIALOGUE_IDS = [ 4882, 4887, 4893, 4900 ]
# The dialogue interface ids for option dialogues, ordered by (option_count - 1)
OPTIONS_DIALOGUE_IDS = [ 2459, 2469, 2480, 2492 ]
## TODO separate this into different Dialogue types ##
# Sends a dialogue displaying an item model and text.
def send_item_dialogue(player, dialogue)
text = dialogue.text
dialogue_id = ITEM_DIALOGUE_IDS[text.size - 1]
player.send(SetWidgetItemModelMessage.new(dialogue_id + 1, dialogue.item, dialogue.item_scale))
indices = [ dialogue_id + 1 + 2, dialogue_id + 1 + 1, dialogue_id + 1 + 4, dialogue_id + 1 + 5 ]
text.each_with_index { |line, index| set_text(player, indices[index], line) }
player.interface_set.open_dialogue(ContinueDialogueAdapter.new(player, dialogue.options[0]), dialogue_id)
end
# Sends a dialogue displaying only text, with no 'Click here to continue...' button.
def send_statement_dialogue(player, dialogue)
text = dialogue.text
dialogue_id = STATEMENT_DIALOGUE_IDS[text.size]
set_text(player, dialogue_id + 1, dialogue.title)
text.each_with_index { |line, index| set_text(player, dialogue_id + 2 + index, line) }
player.interface_set.open_dialogue_overlay(dialogue_id)
end
# Sends a dialogue displaying only text.
def send_text_dialogue(player, dialogue)
text = dialogue.text
dialogue_id = TEXT_DIALOGUE_IDS[text.size - 1]
text.each_with_index { |line, index| set_text(player, dialogue_id + 1 + index, line) }
player.interface_set.open_dialogue(ContinueDialogueAdapter.new(player, dialogue.options[0]), dialogue_id)
end
# Sends a dialogue displaying the player's head.
def send_player_dialogue(player, dialogue)
emote = dialogue.emote
send_generic_dialogue player, dialogue, player.username, PLAYER_DIALOGUE_IDS do |id|
player.send(SetWidgetPlayerModelMessage.new(id + 1))
player.send(SetWidgetModelAnimationMessage.new(id + 1, emote)) unless emote.nil?
end
end
# Sends a dialogue displaying the head of an npc.
def send_npc_dialogue(player, dialogue)
npc = dialogue.npc
emote = dialogue.emote
name = NpcDefinition.lookup(npc).name.to_s
name = "" if (name.nil? || name == "null")
send_generic_dialogue player, dialogue, name, NPC_DIALOGUE_IDS do |id|
player.send(SetWidgetNpcModelMessage.new(id + 1, npc))
player.send(SetWidgetModelAnimationMessage.new(id + 1, emote)) unless emote.nil?
end
end
# Sends a dialogue displaying an event.
def send_generic_dialogue(player, dialogue, title, ids, &event)
text = dialogue.text
dialogue_id = ids[text.size - 1]
event.call(dialogue_id) if block_given?
set_text(player, dialogue_title_id(dialogue_id), title)
text.each_with_index { |line, index| set_text(player, dialogue_text_id(dialogue_id, index), line) }
player.interface_set.open_dialogue(ContinueDialogueAdapter.new(player, dialogue.options[0]), dialogue_id)
end
# Sends an options dialogue interface.
def send_options_dialogue(player, dialogue)
options = dialogue.options
size = options.size
raise 'Illegal options count: must be between 2 and 5, inclusive.' unless (2..5).include?(size)
text = dialogue.text
dialogue_id = OPTIONS_DIALOGUE_IDS[size - 1]
question = dialogue.title
set_text(player, dialogue_question_id(dialogue_id), question)
text.each_with_index { |line, index| set_text(player, dialogue_option_id(dialogue_id, index), line) }
player.interface_set.open_dialogue(OptionDialogueAdapter.new(player, options), dialogue_id)
end
# A DialogueAdapter for dialogues with a 'Click here to continue...' message.
class ContinueDialogueAdapter < DialogueAdapter
# Creates the ContinueDialogueAdadpter.
def initialize(player, continue)
super()
@player = player
@continue = continue
end
# Executes the 'continue' lambda when the player clicks the 'Click here to continue...' message.
def continued()
@continue.call(@player)
end
end
# A DialogueAdapter for dialogues with a set of options that can be selected.
class OptionDialogueAdapter < DialogueAdapter
# Creates the OptionDialogueAdadpter.
def initialize(player, options)
super()
@player = player
@options = options.dup
end
# Executes an option.
def button_clicked(button)
option = OPTIONS_DIALOGUE_IDS.find_index(button)
options[option].call(@player)
end
end
# Gets the widget id of the question, for an options dialogue interface.
def dialogue_question_id(id)
id + 1
end
# Gets the widget id of a dialogue option.
def dialogue_option_id(id, option)
id + 1 + option
end
# Gets the widget id of a dialogue text line.
def dialogue_text_id(id, line)
id + 3 + line
end
# Gets the widget id of a dialogue title.
def dialogue_title_id(id)
id + 2
end
# Sets the text of a widget.
def set_text(player, id, message)
player.send(SetWidgetTextMessage.new(id, message))
end
# Verifies that the dialogue type exists.
def verify_dialogue_type(type)
raise "Unrecognised dialogue type #{type}, expected one of #{DIALOGUE_TYPES}." unless DIALOGUE_TYPES.include?(type)
end
# The spacing of each character glyph, for the font used for dialogue. TODO decode the font from the cache.
GLYPH_SPACING = [ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 7, 14, 9, 12, 12, 4, 5,
5, 10, 8, 4, 8, 4, 7, 9, 7, 9, 8, 8, 8, 9, 7, 9, 9, 4, 5, 7,
9, 7, 9, 14, 9, 8, 8, 8, 7, 7, 9, 8, 6, 8, 8, 7, 10, 9, 9, 8,
9, 8, 8, 6, 9, 8, 10, 8, 8, 8, 6, 7, 6, 9, 10, 5, 8, 8, 7, 8,
8, 7, 8, 8, 4, 7, 7, 4, 10, 8, 8, 8, 8, 6, 8, 6, 8, 8, 9, 8,
8, 8, 6, 4, 6, 12, 3, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 8, 11, 8, 8, 4, 8, 7, 12, 6, 7, 9, 5, 12, 5, 6, 10, 6, 6, 6,
8, 8, 4, 5, 5, 6, 7, 11, 11, 11, 9, 9, 9, 9, 9, 9, 9, 13, 8, 8,
8, 8, 8, 4, 4, 5, 4, 8, 9, 9, 9, 9, 9, 9, 8, 10, 9, 9, 9, 9,
8, 8, 8, 8, 8, 8, 8, 8, 8, 13, 6, 8, 8, 8, 8, 4, 4, 5, 4, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ]
# Gets the width of a single character.
def get_width(char)
return GLYPH_SPACING[char.ord]
end
+31
View File
@@ -0,0 +1,31 @@
# Declares all of the possible emotes
declare_emote(:calm, 588)
declare_emote(:anxious, 589)
declare_emote(:calm_talk, 590)
declare_emote(:default, 591)
declare_emote(:evil, 592)
declare_emote(:bad, 593)
declare_emote(:wicked, 594)
declare_emote(:annoyed, 595)
declare_emote(:distressed, 596)
declare_emote(:afflicted, 597)
declare_emote(:almost_crying, 598)
declare_emote(:drunk_left, 600)
declare_emote(:drunk_right, 601)
declare_emote(:not_interested, 602)
declare_emote(:sleepy, 603)
declare_emote(:plain_evil, 604)
declare_emote(:laugh, 605)
declare_emote(:snigger, 606)
declare_emote(:have_fun, 607)
declare_emote(:guffaw, 608)
declare_emote(:evil_laugh, 609)
declare_emote(:sad, 610)
declare_emote(:more_sad, 611)
declare_emote(:on_one_hand, 612)
declare_emote(:nearly_crying, 613)
declare_emote(:angry, 614)
declare_emote(:furious, 615)
declare_emote(:enraged, 616)
declare_emote(:mad, 617)
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<plugin>
<id>dialogue</id>
<version>0.1</version>
<name>Dialogue</name>
<description>Adds dialogue support.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>dialogue.rb</script>
<script>emotes.rb</script>
</scripts>
<dependencies>
<dependency>util</dependency>
</dependencies>
</plugin>
+51
View File
@@ -0,0 +1,51 @@
require 'java'
java_import 'org.apollo.game.action.DistancedAction'
java_import 'org.apollo.game.model.Animation'
DUMMY_ID = 823
DUMMY_SIZE = 1
PUNCH_ANIMATION = Animation.new(422)
ANIMATION_PULSES = 0
LEVEL_THRESHOLD = 8
EXP_PER_HIT = 5
# A DistancedAction for attacking a training dummy.
class DummyAction < DistancedAction
attr_reader :position
def initialize(mob, position)
super(ANIMATION_PULSES, true, mob, position, DUMMY_SIZE)
@position = position
@started = false
end
def executeAction
if @started
skills = mob.skill_set
if (skills.skill(Skill::ATTACK).maximum_level >= LEVEL_THRESHOLD)
mob.send_message('There is nothing more you can learn from hitting a dummy.')
else
skills.add_experience(Skill::ATTACK, EXP_PER_HIT)
end
stop
else
@started = true
mob.send_message('You hit the dummy.')
mob.turn_to(position)
mob.play_animation(PUNCH_ANIMATION)
end
end
def equals(other)
get_class == other.get_class && @position == other.position
end
end
on :message, :second_object_action do |player, message|
player.start_action(DummyAction.new(player, message.position)) if message.id == DUMMY_ID
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>dummy</id>
<version>1</version>
<name>Dummies</name>
<description>Allows low-level players to train on dummies.</description>
<authors>
<author>Graham</author>
</authors>
<scripts>
<script>dummy.rb</script>
</scripts>
<dependencies />
</plugin>
+57
View File
@@ -0,0 +1,57 @@
require 'java'
java_import 'org.apollo.game.model.Animation'
# Animation constants.
ANGRY = Animation.new(859)
BECKON = Animation.new(864)
BLOW_KISS = Animation.new(1368)
BOW = Animation.new(858)
CHEER = Animation.new(862)
CLAP = Animation.new(865)
CLIMB_ROPE = Animation.new(1130)
CRY = Animation.new(860)
DANCE = Animation.new(866)
GLASS_BOX = Animation.new(1131)
GLASS_WALL = Animation.new(1128)
GOBLIN_BOW = Animation.new(2127)
GOBLIN_DANCE = Animation.new(2128)
HEAD_BANG = Animation.new(2108)
JIG = Animation.new(2106)
JOY_JUMP = Animation.new(2109)
LAUGH = Animation.new(861)
LEAN = Animation.new(1129)
NO = Animation.new(856)
PANIC = Animation.new(2105)
RASPBERRY = Animation.new(2110)
SALUTE = Animation.new(2112)
SHRUG = Animation.new(2113)
SPIN = Animation.new(2107)
THINKING = Animation.new(857)
WAVE = Animation.new(863)
YAWN = Animation.new(2111)
YES = Animation.new(855)
# A map of buttons to animations.
ANIMATIONS = {
162 => THINKING, 6_503 => CLIMB_ROPE, 169 => NO,
164 => BOW, 13_384 => GOBLIN_DANCE, 161 => CRY,
170 => LAUGH, 171 => CHEER, 163 => WAVE,
167 => BECKON, 3_362 => PANIC, 172 => CLAP,
166 => DANCE, 13_363 => JIG, 13_364 => SPIN,
13_365 => HEAD_BANG, 6_506 => LEAN, 165 => ANGRY,
13_368 => YAWN, 13_366 => JOY_JUMP, 667 => GLASS_BOX,
13_367 => RASPBERRY, 13_369 => SALUTE, 13_370 => SHRUG,
11_100 => BLOW_KISS, 666 => GLASS_WALL, 168 => YES,
13_383 => GOBLIN_BOW
}
# Intercept the button message.
on :message, :button do |player, message|
anim = ANIMATIONS[message.widget_id]
unless anim.nil?
player.play_animation(anim)
message.terminate
end
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>emote-tab</id>
<version>1</version>
<name>Emote Tab</name>
<description>Adds emote tab functionality.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>emote_tab.rb</script>
</scripts>
<dependencies />
</plugin>
@@ -0,0 +1,79 @@
require 'java'
java_import 'org.apollo.game.model.entity.Mob'
java_import 'org.apollo.game.model.entity.Npc'
java_import 'org.apollo.game.model.entity.Player'
java_import 'org.apollo.game.model.entity.attr.Attribute'
java_import 'org.apollo.game.model.entity.attr.AttributeDefinition'
java_import 'org.apollo.game.model.entity.attr.AttributeMap'
java_import 'org.apollo.game.model.entity.attr.AttributePersistence'
java_import 'org.apollo.game.model.entity.attr.AttributeType'
java_import 'org.apollo.game.model.entity.attr.BooleanAttribute'
java_import 'org.apollo.game.model.entity.attr.NumericalAttribute'
java_import 'org.apollo.game.model.entity.attr.StringAttribute'
# Declares an attribute and adds its definition.
def declare_attribute(name, default, persistence = :transient)
if Player.method_defined?(name) || Mob.method_defined?(name) || Npc.method_defined?(name)
fail "Attribute #{name} clashes with an existing variable."
end
definition = AttributeDefinition.new(default, get_persistence(persistence), get_type(default))
AttributeMap::define(name.to_s, definition)
end
private
# The existing Mob class.
class Mob
# Overrides method_missing to implement the functionality.
def method_missing(symbol, *args)
name = symbol.to_s.strip
if name[-1] == '='
fail "Expected argument count of 1, received #{args.length}" unless args.length == 1
name = name[0...-1].strip # Drop the equals and trim whitespace.
set_attribute(name, to_attribute(args[0]))
elsif AttributeMap::get_definition(name).nil?
super(symbol, *args)
else
attribute = get_attribute(name)
value = attribute.value
(attribute.type == AttributeType::SYMBOL) ? value.to_sym : value
end
end
end
# Gets the appropriate attribute for the specified value.
def to_attribute(value)
case value
when String, Symbol then return StringAttribute.new(value.to_s, value.is_a?(Symbol))
when Integer, Float then return NumericalAttribute.new(value)
when TrueClass, FalseClass then return BooleanAttribute.new(value)
else fail "Undefined attribute type #{value.class}."
end
end
# Gets the attribute type of the specified value.
def get_type(value)
case value
when String then return AttributeType::STRING
when Symbol then return AttributeType::SYMBOL
when Integer then return AttributeType::LONG
when Float then return AttributeType::DOUBLE
when TrueClass, FalseClass then return AttributeType::BOOLEAN
else fail "Undefined attribute type #{value.class}."
end
end
# Gets the Persistence type of the specified value.
def get_persistence(persistence)
unless [:persistent, :transient].include?(persistence)
fail "Undefined persistence type #{persistence}."
end
(persistence == :persistent) ? AttributePersistence::PERSISTENT : AttributePersistence::TRANSIENT
end
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>attributes</id>
<version>0.9</version>
<name>Attributes</name>
<description>Adds an attribute system for entites.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>attributes.rb</script>
</scripts>
<dependencies />
</plugin>
@@ -0,0 +1,20 @@
require 'java'
java_import 'org.apollo.game.model.entity.Mob'
module MobExtension
MOB_EXTENSIONS = []
def self.register(extension)
fail 'Provided extension object is not a module' unless extension.is_a?(Module)
new_mixins = extension.public_instance_methods
current_mixins = MOB_EXTENSIONS.map { |e| {e.to_s => e.public_instance_methods} }
current_mixins.each do |ext, methods|
methods.each {|m| fail "Extension #{ext} already provides method #{m}" if new_mixins.include?(m) }
end
Mob.include(extension)
end
end
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<plugin>
<id>mob-extension</id>
<version>0.1</version>
<name>Mob Extensions</name>
<description>Adds support for extending Mobs with new functionality</description>
<authors>
<author>Gary Tierney</author>
</authors>
<scripts>
<script>extension.rb</script>
</scripts>
<dependencies>
</dependencies>
</plugin>
+58
View File
@@ -0,0 +1,58 @@
on :message, :walk do |player, msg|
player.reset_interacting_mob
end
on :message, :player_action do |player, msg|
## todo: need a better way of mapping option numbers to their purpose
player.follow($world.player_repository.get(msg.index)) if msg.option == 3
end
##
# A <code>MobExtension</code> for making a <code>Mob</code> trail behind, or chase a given mob.
module FollowingMobExtension
##
# Follow a mob and trail behind them.
def follow(mob)
do_follow(self, mob, behind: true)
end
##
# Chase a mob (with the intention of getting in front of them), also optionally
# stopping within a projectile distance when at a position where a projectile can
# reach the target.
def chase(mob, projectile_distance: nil)
do_follow(self, mob, front: true, projectile_distance: projectile_distance)
end
end
MobExtension::register(FollowingMobExtension)
private
def do_follow(source, mob, behind: false, front: false, projectile_distance: nil)
source.interacting_mob = mob
schedule 0, true do |task|
# stop the task unless we're still interacting with the other mob.
unless self.interacting_mob.eql? mob
task.stop
next
end
next unless source.walking_queue.size <= 1
distance = mob.position.get_distance(source.position)
unless projectile_distance.nil?
next if distance <= projectile_distance &&
$world.collision_manager.raycast(source.position, mob.position)
end
if distance > 15
reset_interacting_mob
end
walk_to(mob, behind: behind, front: front)
end
end
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>mob-following</id>
<version>1</version>
<name>Following</name>
<description>Adds following for mobs.</description>
<authors>
<author>Steve Soltys</author>
</authors>
<scripts>
<script>following.rb</script>
</scripts>
<dependencies>
<dependency>mob-walk-to</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>mob-walk-to</id>
<version>0.1</version>
<name>Mob path generation</name>
<description>Adds a mixin for making mobs walk to another entity.</description>
<authors>
<author>Gary Tierney</author>
</authors>
<scripts>
<script>walk_to.rb</script>
</scripts>
<dependencies>
<dependency>mob-extension</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,83 @@
java_import 'org.apollo.game.model.entity.path.AStarPathfindingAlgorithm'
java_import 'org.apollo.game.model.entity.path.EuclideanHeuristic'
java_import 'org.apollo.game.model.entity.path.SimplePathfindingAlgorithm'
java_import 'org.apollo.game.model.entity.obj.GameObject'
java_import 'org.apollo.game.model.entity.Mob'
java_import 'org.apollo.game.model.entity.Player'
java_import 'org.apollo.game.model.entity.EntityType'
java_import 'org.apollo.game.model.Direction'
##
# A pathfinder used for simple following (i.e.: An NPC attacking a player).
SIMPLE_PATH_FINDER = SimplePathfindingAlgorithm.new($world.collision_manager)
##
# A pathfinder used for precise following (i.e.: A player attacking another player).
FOLLOW_PATH_FINDER = AStarPathfindingAlgorithm.new($world.collision_manager, EuclideanHeuristic.new)
##
# The directions that we use as a random offset when not walking to the facing direction.
OFFSET_DIRECTIONS = Direction::NESW.to_a
module WalkToMobExtension
def walk_to(entity, front: false, behind: false)
if [front, behind].count { |option| option == true } > 1
fail 'Can only specify one of "front" or "behind"'
end
position = self.position
target_position = entity.position
target_distance = position.get_distance(target_position)
target_offset = walk_to_offset(entity)
target_offset_direction = OFFSET_DIRECTIONS.sample
target_facing_direction = walk_to_facing_direction(entity)
unless target_facing_direction.nil?
if front
target_offset_direction = target_facing_direction
elsif behind
target_offset_direction = target_facing_direction.opposite
end
end
target_offset_position = target_position.step(target_offset, target_offset_direction)
return if target_offset_position.eql?(position)
pathfinder = FOLLOW_PATH_FINDER
path = pathfinder.find(position, target_offset_position)
path.each { |tile| self.walking_queue.add_step(tile) }
end
end
MobExtension::register(WalkToMobExtension)
private
##
# Gets the number of tiles away from an entity's actual origin that we can
# walk to.
def walk_to_offset(entity)
case entity.entity_type
when EntityType::DYNAMIC_OBJECT, EntityType::STATIC_OBJECT
return max(entity.definition.width, entity.definition.length) + 1
when EntityType::NPC
return entity.definition.size + 1
when EntityType::PLAYER
return 1
else
fail "walk_to_offset called with invalid entity type: #{type.to_s}"
end
end
##
# Gets the direction that an entity is facing, so a mob can walk up infront of them.
def walk_to_facing_direction(entity)
case entity.entity_type
when EntityType::DYNAMIC_OBJECT, EntityType::STATIC_OBJECT
return Direction::WNES[entity.orientation]
when EntityType::NPC, EntityType::PLAYER
return entity.last_direction
else
fail "walk_to_offset called with invalid entity type: #{type.to_s}"
end
end
@@ -0,0 +1,197 @@
require 'java'
java_import 'org.apollo.cache.def.NpcDefinition'
java_import 'org.apollo.game.action.Action'
java_import 'org.apollo.game.model.Animation'
java_import 'org.apollo.game.model.Graphic'
java_import 'org.apollo.game.model.Position'
java_import 'org.apollo.game.model.World'
java_import 'org.apollo.game.model.entity.Npc'
# Information about npc spawning
#
# Npcs are passed to spawn npc as a hash. Every key and every non-integer value must be a Symbol.
# Every hash must implement the following:
# :name - the name of the npc. If this npc shares its name with another, append the specific id
# after the name (e.g. :woman_4)
# :x - the x coordinate where the npc will spawn.
# :y - the y coordinate where the npc will spawn.
# Optional arguments are as follows:
# :face - the direction the npc should face when it spawns. Supported options are :north,
# :north_east, :east, :south_east, :south, :south_west, :west, and :north_west
# :bounds - the rectangular bound that the npc can wander about in. Order is
# [bottom-left x-coordinate, bottom-left y-coordinate, top-right x-coordinate,
# top-right y-coordinate]
# :delta_bounds - the rectangular bound that the npc can wander about in, as a difference from
# the spawn point. Order is [x-delta, y-delta]. Should not be used with :bounds.
# :spawn_animation - the animation that will be played when the npc spawns.
# :spawn_graphic - the graphic that will be played when the npc spawns.
# Spawns an npc with the properties specified in the hash.
def spawn_npc(hash)
unless (hash.key?(:name) || hash.key?(:id)) && hash.has_keys?(:x, :y)
fail 'A name (or id), x coordinate, and y coordinate must be specified to spawn an npc.'
end
npc = get_npc(hash)
spawn(npc, hash)
npc
end
# Spawns the specified npc and applies the properties in the hash.
def spawn(npc, hash)
unless hash.empty?
hash = decode_hash(npc.position, hash)
apply_decoded_hash(npc, hash)
end
$world.register(npc)
end
# Returns an npc with the id and position specified by the hash.
def get_npc(hash)
id = lookup_npc(hash.delete(:name))
z = hash.delete(:z)
position = Position.new(hash.delete(:x), hash.delete(:y), z.nil? ? 0 : z)
Npc.new($world, id, position)
end
# Applies a decoded hash (one aquired using parse_hash) to the specified npc.
def apply_decoded_hash(npc, hash)
hash.each do |key, value|
case key
when :face then npc.turn_to(value)
when :boundary then npc.boundaries = value
when :spawn_animation then npc.play_animation(Animation.new(value))
when :spawn_graphic then npc.play_graphic(Graphic.new(value))
else fail "Unrecognised key #{key} - value #{value}."
end
end
end
# Parses the remaining key-value pairs in the hash.
def decode_hash(position, hash)
decoded = {}
hash.each do |key, value|
case key
when :face
decoded[:face] = direction_to_position(value, position)
when :delta_bounds
fail ':delta_bounds must have two values.' unless value.length == 2
dx, dy, x, y, z = value[0], value[1], position.x, position.y, position.height
fail 'Delta values cannot be less than 0.' if dx < 0 || dy < 0
decoded[:boundary] = [Position.new(x - dx, y - dy, z), Position.new(x + dx, y + dy, z)]
when :bounds
fail ':bounds must have four values.' unless value.length == 4
min_x, min_y, max_x, max_y = value[0], value[1], value[2], value[3]
decoded[:boundary] = [Position.new(min_x, min_y), Position.new(max_x, max_y)]
when :spawn_animation then decoded[:spawn_animation] = Animation.new(value)
when :spawn_graphic then decoded[:spawn_graphic] = Graphic.new(value)
else fail "Unrecognised key #{key} - value #{value}."
end
end
decoded
end
# Returns a position that an entity at the specified position should be facing towards if they are
# looking in the specified direction.
def direction_to_position(direction, position)
x, y, z = position.x, position.y, position.height
case direction
when :north then return Position.new(x, y + 1, z)
when :north_east then return Position.new(x + 1, y + 1, z)
when :east then return Position.new(x + 1, y, z)
when :south_east then return Position.new(x + 1, y - 1, z)
when :south then return Position.new(x, y - 1, z)
when :south_west then return Position.new(x - 1, y - 1, z)
when :west then return Position.new(x - 1, y, z)
when :north_west then return Position.new(x - 1, y + 1, z)
else return position
end
end
# An action that spawns an npc temporarily, before executing an action.
class TemporaryNpcAction < Action
attr_reader :executions, :combative
def initialize(delay, immediate, hash)
super(delay, immediate, get_npc(hash))
@executions = 0
@hash = hash
end
def execute
if @executions == 0
spawn(mob, @hash)
execute_spawn_action
else
execute_action
end
@executions += 1
end
def execute_action
# Override to provide functionality.
end
def execute_spawn_action
# Overridden to provide functionality for when the npc spawns.
end
end
# A random event that spawns and executes some sort of action.
class RandomEvent < TemporaryNpcAction
def initialize(delay, immediate, hash, combative, target)
super(delay, immediate, hash)
@combative = combative
@target = target
end
def execute_spawn_action
mob.turn_to(target.position)
mob.update_interacting_mob(target.index)
end
end
# Adds a random event to the array.
def register_random_event(event)
RANDOM_EVENTS << event
end
# Contains random event npcs
RANDOM_EVENTS = []
# Spawns a random event for the specified player.
def send_random_event(player)
position = player.position
npc_position = Position.new(position.x + 1, position.y, position.height)
# TODO: Find an unoccupied tile instead of the assumption that (x + 1) is traversable!!
spawn_random_event(npc_position, false)
end
# Spawns a random event in the specified position.
# If 'combat' is false, only non-combat events will be spawned.
def spawn_random_event(_position, _combat)
event = RANDOM_EVENTS[rand(RANDOM_EVENTS.size)]
attempts = 0
while event.combative && attempts < 5
event = RANDOM_EVENTS[rand(RANDOM_EVENTS.size)]
attempts += 1
end
event.execute unless attempts == 5 # 5 iterations is plenty, just give up at this point
end
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>entity-spawning</id>
<version>0.1</version>
<name>Entity Spawning</name>
<description>Adds support for easy ruby entity spawning.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>npc-spawn.rb</script>
</scripts>
<dependencies>
<dependency>util</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,89 @@
# Generic npcs
spawn_npc name: :man, x: 3276, y: 3186
spawn_npc name: :man, x: 3282, y: 3197
spawn_npc name: :man_3, x: 3301, y: 3200
spawn_npc name: :man_3, x: 3300, y: 3208
spawn_npc name: :man_2, x: 3297, y: 3196
spawn_npc name: :man_16, x: 3294, y: 3204
spawn_npc name: :spider_61, x: 3319, y: 3145
spawn_npc name: :spider_61, x: 3319, y: 3140
spawn_npc name: :spider_61, x: 3323, y: 3138
spawn_npc name: :scorpion, x: 3282, y: 3149
# Camels
spawn_npc name: :cam_the_camel, x: 3295, y: 3232
spawn_npc name: :elly_the_camel, x: 3312, y: 3210
spawn_npc name: :camel, x: 3285, y: 3198
spawn_npc name: :ollie_the_camel, x: 3291, y: 3209
spawn_npc name: :al_the_camel, x: 3275, y: 3162
# Quest npc
spawn_npc name: :osman, x: 3286, y: 3180, face: :east
spawn_npc name: :hassan, x: 3302, y: 3163
spawn_npc name: :father_reen, x: 3272, y: 3158
spawn_npc name: :man_663, x: 3297, y: 3287
# Boarder guards
spawn_npc name: 'border_guard', x: 3268, y: 3226, face: :north
spawn_npc name: 'border_guard', x: 3267, y: 3226, face: :north
spawn_npc name: 'border_guard', x: 3268, y: 3229, face: :south
spawn_npc name: 'border_guard', x: 3267, y: 3229, face: :south
# Palace guards
spawn_npc name: 'Al-Kharid warrior', x: 3285, y: 3174
spawn_npc name: 'Al-Kharid warrior', x: 3283, y: 3168
spawn_npc name: 'Al-Kharid warrior', x: 3285, y: 3169
spawn_npc name: 'Al-Kharid warrior', x: 3290, y: 3162
spawn_npc name: 'Al-Kharid warrior', x: 3295, y: 3162
spawn_npc name: 'Al-Kharid warrior', x: 3295, y: 3170
spawn_npc name: 'Al-Kharid warrior', x: 3300, y: 3175
spawn_npc name: 'Al-Kharid warrior', x: 3300, y: 3171
spawn_npc name: 'Al-Kharid warrior', x: 3301, y: 3168
# Shanty pass
spawn_npc name: :shantay_guard, x: 3301, y: 3120
spawn_npc name: :shantay_guard, x: 3302, y: 3126
spawn_npc name: :shantay_guard, x: 3306, y: 3126
spawn_npc name: :shantay_guard_838, x: 3303, y: 3118, face: :north
# Mine
spawn_npc name: :scorpion, x: 3296, y: 3294
spawn_npc name: :scorpion, x: 3298, y: 3280
spawn_npc name: :scorpion, x: 3299, y: 3299
spawn_npc name: :scorpion, x: 3299, y: 3309
spawn_npc name: :scorpion, x: 3300, y: 3287
spawn_npc name: :scorpion, x: 3300, y: 3315
spawn_npc name: :scorpion, x: 3301, y: 3305
spawn_npc name: :scorpion, x: 3301, y: 3312
# Functional npcs
spawn_npc name: :gnome_pilot, x: 3279, y: 3213
spawn_npc name: :banker_496, x: 3267, y: 3164, face: :east
spawn_npc name: :banker_497, x: 3267, y: 3166, face: :east
spawn_npc name: :banker_496, x: 3267, y: 3167, face: :east
spawn_npc name: :banker_497, x: 3267, y: 3168, face: :east
spawn_npc name: :banker_496, x: 3267, y: 3169, face: :east
spawn_npc name: :gem_trader, x: 3287, y: 3210
spawn_npc name: :zeke, x: 3289, y: 3189
spawn_npc name: :shantay, x: 3304, y: 3124
spawn_npc name: :rug_merchant_2296, x: 3311, y: 3109, face: :west
spawn_npc name: :ranael, x: 3315, y: 3163, face: :north
spawn_npc name: :shop_assistant_525, x: 3315, y: 3178, face: :north
spawn_npc name: :shop_keeper_524, x: 3315, y: 3180, face: :west
spawn_npc name: :louie_legs, x: 3316, y: 3175, face: :west
spawn_npc name: :ellis, x: 3274, y: 3192
spawn_npc name: :dommik, x: 3321, y: 3193
spawn_npc name: :tool_leprechaun, x: 3319, y: 3204
spawn_npc name: :ali_morrisane, x: 3304, y: 3211, face: :east
spawn_npc name: :silk_trader, x: 3300, y: 3203
spawn_npc name: :karim, x: 3273, y: 3180
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>location-al-kharid</id>
<version>0.1</version>
<name>Al-Kharid</name>
<description>Adds functionality to Al-Kharid.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>npcs.rb</script>
</scripts>
<dependencies>
<dependency>entity-spawning</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,45 @@
# Generic npcs
spawn_npc name: :man, x: 3095, y: 3508
spawn_npc name: :man, x: 3095, y: 3511
spawn_npc name: :man, x: 3098, y: 3509
spawn_npc name: :man_2, x: 3093, y: 3511
spawn_npc name: :man_3, x: 3097, y: 3508
spawn_npc name: :man_3, x: 3092, y: 3508
spawn_npc name: :man_3, x: 3097, y: 3512
spawn_npc name: :guard, x: 3086, y: 3516
spawn_npc name: :guard, x: 3094, y: 3518
spawn_npc name: :guard, x: 3108, y: 3514
spawn_npc name: :guard, x: 3110, y: 3514
spawn_npc name: :guard, x: 3113, y: 3514
spawn_npc name: :guard, x: 3113, y: 3516
spawn_npc name: :sheep_43, x: 3050, y: 3516
spawn_npc name: :sheep_43, x: 3051, y: 3514
spawn_npc name: :sheep_43, x: 3056, y: 3517
spawn_npc name: :ram_3673, x: 3048, y: 3515
spawn_npc name: :monk, x: 3044, y: 3491
spawn_npc name: :monk, x: 3045, y: 3483
spawn_npc name: :monk, x: 3045, y: 3497
spawn_npc name: :monk, x: 3050, y: 3490
spawn_npc name: :monk, x: 3054, y: 3490
spawn_npc name: :monk, x: 3058, y: 3497
# Functional npcs
spawn_npc name: :richard, x: 3098, y: 3516
spawn_npc name: :doris, x: 3079, y: 3491
spawn_npc name: :brother_jered, x: 3045, y: 3488
spawn_npc name: :brother_althric, x: 3054, y: 3504
spawn_npc name: :abbot_langley, x: 3059, y: 3484
spawn_npc name: :oziach, x: 3067, y: 3518, face: :east
spawn_npc name: :shop_keeper_528, x: 3079, y: 3509
spawn_npc name: :shop_assistant_529, x: 3082, y: 3513
spawn_npc name: :banker, x: 3096, y: 3489, face: :west
spawn_npc name: :banker_495, x: 3096, y: 3491, face: :west
spawn_npc name: :banker_495, x: 3096, y: 3492, face: :north
spawn_npc name: :banker, x: 3098, y: 3492, face: :north
spawn_npc name: :mage_of_zamorak, x: 3106, y: 3560
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<plugin>
<id>location-edgeville</id>
<version>0.1</version>
<name>Edgeville</name>
<description>Adds functionality to Edgeville.</description>
<authors>
<author>xEliqa</author>
<author>Major</author>
</authors>
<scripts>
<script>npcs.rb</script>
</scripts>
<dependencies>
<dependency>entity-spawning</dependency>
</dependencies>
</plugin>
+157
View File
@@ -0,0 +1,157 @@
# Generic npcs
spawn_npc name: :chicken, x: 2965, y: 3345
spawn_npc name: :duck, x: 2988, y: 3383
spawn_npc name: :duck, x: 2992, y: 3383
spawn_npc name: :duck, x: 2993, y: 3385
spawn_npc name: :drunken_man, x: 2957, y: 3368, z: 1
spawn_npc name: :dwarf_118, x: 3023, y: 3334
spawn_npc name: :dwarf_118, x: 3027, y: 3341
spawn_npc name: :dwarf_118, x: 3012, y: 3341
spawn_npc name: :dwarf_118, x: 3017, y: 3346, z: 1
spawn_npc name: :dwarf_118, x: 3011, y: 3341, z: 1
spawn_npc name: :dwarf_121, x: 3027, y: 3341
spawn_npc name: :dwarf_382, x: 3017, y: 3340
spawn_npc name: :dwarf_3294, x: 3022, y: 3338
spawn_npc name: :dwarf_3295, x: 3021, y: 3341
spawn_npc name: :gardener, x: 2998, y: 3385
spawn_npc name: :gardener, x: 3019, y: 3369
spawn_npc name: :gardener_3234, x: 3016, y: 3386
spawn_npc name: :guard, x: 2965, y: 3394
spawn_npc name: :guard, x: 2964, y: 3396
spawn_npc name: :guard, x: 2966, y: 3397
spawn_npc name: :guard, x: 2964, y: 3384
spawn_npc name: :guard, x: 2963, y: 3380
spawn_npc name: :guard, x: 3006, y: 3325
spawn_npc name: :guard, x: 3008, y: 3320
spawn_npc name: :guard, x: 3006, y: 3322
spawn_npc name: :guard, x: 3038, y: 3356
spawn_npc name: :guard_10, x: 2942, y: 3375
spawn_npc name: :guard_10, x: 3040, y: 3352
spawn_npc name: :guard_3230, x: 2967, y: 3395
spawn_npc name: :guard_3230, x: 2966, y: 3392
spawn_npc name: :guard_3230, x: 2963, y: 3376
spawn_npc name: :guard_3230, x: 2954, y: 3382
spawn_npc name: :guard_3231, x: 2950, y: 3377
spawn_npc name: :guard_3231, x: 2968, y: 3381
spawn_npc name: :guard_3231, x: 3033, y: 3389, z: 1
spawn_npc name: :guard_3231, x: 3041, y: 3388, z: 1
spawn_npc name: :guard_3231, x: 3048, y: 3389, z: 1
spawn_npc name: :guard_3231, x: 3056, y: 3389, z: 1
spawn_npc name: :guard_3231, x: 3062, y: 3386, z: 1
spawn_npc name: :guard_3231, x: 3058, y: 3329, z: 1
spawn_npc name: :guard_3231, x: 3050, y: 3329, z: 1
spawn_npc name: :guard_3231, x: 3038, y: 3329, z: 1
spawn_npc name: :guard_3231, x: 3029, y: 3329, z: 1
spawn_npc name: :swan, x: 2960, y: 3359
spawn_npc name: :swan, x: 2963, y: 3360
spawn_npc name: :swan, x: 2968, y: 3359
spawn_npc name: :swan, x: 2971, y: 3360
spawn_npc name: :swan, x: 2976, y: 3358
spawn_npc name: :swan, x: 2989, y: 3384
spawn_npc name: :man_3223, x: 2991, y: 3365
spawn_npc name: :man_3225, x: 3037, y: 3345, z: 1
spawn_npc name: :white_knight, x: 2983, y: 3343
spawn_npc name: :white_knight, x: 2981, y: 3334
spawn_npc name: :white_knight, x: 2988, y: 3335
spawn_npc name: :white_knight, x: 2996, y: 3342
spawn_npc name: :white_knight, x: 2960, y: 3340
spawn_npc name: :white_knight, x: 2962, y: 3336
spawn_npc name: :white_knight, x: 2974, y: 3342
spawn_npc name: :white_knight, x: 2972, y: 3345
spawn_npc name: :white_knight, x: 2977, y: 3348
spawn_npc name: :white_knight_3348, x: 2971, y: 3340
spawn_npc name: :white_knight_3348, x: 2978, y: 3350
spawn_npc name: :white_knight, x: 2964, y: 3330, z: 1
spawn_npc name: :white_knight, x: 2968, y: 3334, z: 1
spawn_npc name: :white_knight, x: 2969, y: 3339, z: 1
spawn_npc name: :white_knight, x: 2978, y: 3332, z: 1
spawn_npc name: :white_knight, x: 2958, y: 3340, z: 1
spawn_npc name: :white_knight, x: 2960, y: 3343, z: 1
spawn_npc name: :white_knight_3348, x: 2987, y: 3334, z: 1
spawn_npc name: :white_knight_3348, x: 2983, y: 3336, z: 1
spawn_npc name: :white_knight_3348, x: 2987, y: 3334, z: 1
spawn_npc name: :white_knight_3348, x: 2979, y: 3348, z: 1
spawn_npc name: :white_knight_3348, x: 2964, y: 3337, z: 1
spawn_npc name: :white_knight_3349, x: 2989, y: 3344, z: 1
spawn_npc name: :white_knight, x: 2985, y: 3342, z: 2
spawn_npc name: :white_knight_3348, x: 2979, y: 3348, z: 2
spawn_npc name: :white_knight_3348, x: 2974, y: 3329, z: 2
spawn_npc name: :white_knight_3348, x: 2982, y: 3341, z: 2
spawn_npc name: :white_knight_3349, x: 2990, y: 3341, z: 2
spawn_npc name: :white_knight_3349, x: 2971, y: 3330, z: 2
spawn_npc name: :white_knight_3349, x: 2965, y: 3350, z: 2
spawn_npc name: :white_knight_3349, x: 2965, y: 3329, z: 2
spawn_npc name: :white_knight_3350, x: 2961, y: 3347, z: 2
spawn_npc name: :white_knight_3349, x: 2962, y: 3339, z: 3
spawn_npc name: :white_knight_3350, x: 2960, y: 3336, z: 3
spawn_npc name: :white_knight_3350, x: 2984, y: 3349, z: 3
spawn_npc name: :woman_3226, x: 2991, y: 3384
# Functional npcs
spawn_npc name: :apprentice_workman_3235, x: 2971, y: 3369, z: 1
spawn_npc name: :banker_495, x: 2945, y: 3366, face: :north
spawn_npc name: :banker_495, x: 2946, y: 3366, face: :north
spawn_npc name: :banker_495, x: 2947, y: 3366, face: :north
spawn_npc name: :banker_495, x: 2948, y: 3366, face: :north
spawn_npc name: :banker, x: 2949, y: 3366, face: :north
spawn_npc name: :banker, x: 3015, y: 3353, face: :north
spawn_npc name: :banker, x: 3014, y: 3353, face: :north
spawn_npc name: :banker, x: 3013, y: 3353, face: :north
spawn_npc name: :banker, x: 3012, y: 3353, face: :north
spawn_npc name: :banker, x: 3011, y: 3353, face: :north
spawn_npc name: :banker, x: 3010, y: 3353, face: :north
spawn_npc name: :cassie, x: 2976, y: 3383
spawn_npc name: :emily, x: 2954, y: 3372
spawn_npc name: :flynn, x: 2950, y: 3387
spawn_npc name: :hairdresser, x: 2944, y: 3380
spawn_npc name: :herquin, x: 2945, y: 3335
spawn_npc name: :heskel, x: 3007, y: 3374
spawn_npc name: :kaylee, x: 2957, y: 3372
spawn_npc name: :tina, x: 2955, y: 3371, z: 1
spawn_npc name: :tool_leprechaun, x: 3005, y: 3370
spawn_npc name: :squire, x: 2977, y: 3343
spawn_npc name: :sir_tiffy_cashien, x: 2997, y: 3373, face: :north
spawn_npc name: :sir_amik_varze, x: 2960, y: 3336, z: 2
spawn_npc name: :sir_vyvin, x: 2983, y: 3335, z: 2
spawn_npc name: :shop_asssistant_525, x: 2955, y: 3389
spawn_npc name: :shop_keeper_524, x: 2957, y: 3387
spawn_npc name: :wayne, x: 2972, y: 3312
spawn_npc name: :workman_3236, x: 2975, y: 3369, z: 1
spawn_npc name: :wyson_the_gardener, x: 3028, y: 3381
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>location-falador</id>
<version>0.1</version>
<name>Falador</name>
<description>Adds functionality to Falador.</description>
<authors>
<author>Wizard Jesse</author>
</authors>
<scripts>
<script>npcs.rb</script>
</scripts>
<dependencies>
<dependency>entity-spawning</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,16 @@
# Generic npcs
spawn_npc name: :woman_4, x: 3232, y: 3207 # southernmost house
spawn_npc name: :man_1, x: 3231, y: 3237 # house by willow tree
spawn_npc name: :man_2, x: 3224, y: 3240 # house by willow tree
spawn_npc name: :woman_5, x: 3229, y: 2329 # house by willow tree
# Functional npcs
spawn_npc name: :hans, x: 3221, y: 3221
spawn_npc name: :father_aereck, x: 3243, y: 3210
spawn_npc name: :bob, x: 3231, y: 3203
spawn_npc name: :shop_keeper, x: 3212, y: 3247
spawn_npc name: :shop_assistant, x: 3211, y: 3245
spawn_npc name: :lumbridge_guide, x: 3232, y: 3229
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>location-lumbridge</id>
<version>0.1</version>
<name>Lumbridge</name>
<description>Adds functionality to Lumbridge.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>npcs.rb</script>
</scripts>
<dependencies>
<dependency>entity-spawning</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,158 @@
require 'java'
java_import 'org.apollo.game.message.impl.HintIconMessage'
java_import 'org.apollo.game.message.impl.MobHintIconMessage'
java_import 'org.apollo.game.message.impl.PositionHintIconMessage'
java_import 'org.apollo.game.message.impl.SwitchTabInterfaceMessage'
java_import 'org.apollo.game.model.entity.EntityType'
java_import 'org.apollo.game.model.Position'
private
# Contains constants used during the Runescape Guide part of Tutorial Island.
module GuideConstants
# The Runescape Guide Npc.
RUNESCAPE_GUIDE = spawn_npc name: :runescape_guide, x: 3093, y: 3107
# The character design interface id.
CHARACTER_DESIGN_INTERFACE = 3559
# The id of the door of the house new players spawn in.
DOOR_ID = 3014
# The Position of the door of the house new players spawn in.
DOOR_POSITION = Position.new(3098, 3107)
# The HintIconMessage sent to display a hint arrow above the door of the house new players spawn
# in.
DOOR_HINT = PositionHintIconMessage.new(HintIconMessage::Type::WEST, DOOR_POSITION, 120)
# The ids of tabs that are displayed when the player has yet to start the tutorial.
INITIAL_TABS = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2449, 904, -1, -1].freeze
# The HintIconMessage sent to remove a hint arrow from above the door.
RESET_DOOR_HINT = PositionHintIconMessage.reset
# The HintIconMessage sent to remove a hint arrow from an Npc.
RESET_NPC_HINT = MobHintIconMessage.reset(EntityType::NPC)
end
# Sends the appropriate data to the client when the player logs in to the game.
on :login do |_event, player|
if player.in_tutorial_island && player.privilege_level != RIGHTS_ADMIN
TutorialInstructions.show_instruction(player)
GuideConstants::INITIAL_TABS.each_with_index do |tab, index|
player.send(SwitchTabInterfaceMessage.new(index, tab))
end
if player.tutorial_island_progress == :not_started
player.interface_set.open_window(GuideConstants::CHARACTER_DESIGN_INTERFACE)
player.send(MobHintIconMessage.create(GuideConstants::RUNESCAPE_GUIDE))
end
end
end
# The conversation with the Runescape Guide, when on tutorial island.
conversation :tutorial_runescape_guide do
# The first dialogue of the Runescape Guide, greeting the Player.
dialogue :greetings do
type :npc_speech
npc :runescape_guide
emote :calm
precondition { |player| player.tutorial_island_progress == :not_started }
text 'Greetings! I see you are a new arrival to this land. My job is to welcome all new '\
'visitors. So welcome!'
continue dialogue: :talk_to_people do |player|
player.tutorial_island_progress = :talk_to_people
end
end
# The Guide welcomes back the Player if they speak to him after they have already gone through
# the conversation once.
dialogue :welcome_back do
type :npc_speech
npc :runescape_guide
emote :calm
precondition { |player| player.tutorial_island_progress != :not_started }
text 'Welcome back.'
continue dialogue: :talk_to_people
end
# The Guide tells Players to speak to people in order to succeed.
dialogue :talk_to_people do
type :npc_speech
npc :runescape_guide
emote :calm
text 'You have already learned the first thing you need to succeed in this world: talking to '\
'people!',
'You will find many inhabitants of this world have useful things to say to you. By '\
'clicking on them with your mouse you can talk to them.',
'I would also suggest reading through some of the supporting information on the website.'\
' There you can find maps, a bestiary, and much more.'
continue dialogue: :go_through_door
end
# The Guide tells Players to go through the door, advancing the tutorial progress if this is the
# first time the Player has heard this.
dialogue :go_through_door do
type :npc_speech
npc :runescape_guide
emote :calm
text 'To continue the tutorial go through that door over there, and speak to your first '\
'instructor.'
close do |player|
if player.tutorial_island_progress < :runescape_guide_finished
player.send(GuideConstants::RESET_NPC_HINT)
player.send(GuideConstants::DOOR_HINT)
player.tutorial_island_progress = :runescape_guide_finished
end
TutorialInstructions.show_instruction(player)
end
end
# The dialogue displayed if the player attempts to leave the Runescape Guide's house before they
# have spoken to him.
dialogue :talk_to_guide do
type :text
text 'You need to talk to the Runescape Guide before you are allowed to proceed through this '\
'door.'
close do |player|
TutorialInstructions.show_instruction(player)
end
end
end
on :open_door do |event, player|
door = event.door
if player.in_tutorial_island && door.position.equals(GuideConstants::DOOR_POSITION)
if player.tutorial_island_progress < :runescape_guide_finished
send_dialogue(player, get_dialogue(:tutorial_runescape_guide, :talk_to_guide))
event.terminate
elsif player.tutorial_island_progress == :runescape_guide_finished
player.tutorial_island_progress = :moving_around
player.send(GuideConstants::RESET_DOOR_HINT)
TutorialInstructions.show_instruction(player)
end
end
end
@@ -0,0 +1,141 @@
# Contains members related to the instructions issues during tutorial island.
module TutorialInstructions
# Sends the appropriate instruction to the player.
def self.show_instruction(player)
instructions = CONVERSATIONS[:tutorial_island_instructions]
progress = player.tutorial_island_progress.name
name = case progress
# The Runescape Guide instructions.
when :not_started then :getting_started
when :runescape_guide_finished then :scenery
when :moving_around then :moving_around
# The Survival Guide instructions.
when :given_axe then :viewing_items
when :cut_tree then :cut_tree
when :cutting_tree then :please_wait
else raise "No dialogue for current stage #{progress} exists."
end
dialogue = instructions.part(name)
send_dialogue(player, dialogue)
end
# The one-sided 'conversation' of instruction instructions.
conversation :tutorial_island_instructions do
# The initial instruction displayed when the player logs in, before they have spoken to the
# guide.
dialogue :getting_started do
type :text
title 'Getting started'
text 'To start the tutorial, use your left mouse button to click on the Runescape Guide in '\
'this room. He is indicated by a flashing yellow arrow above his head. If you can\'t '\
'see him, use your keyboard\'s arrow keys to rotate the view.'
end
# The instruction displayed after the player has spoken to the Runescape Guide.
dialogue :scenery do
type :text
title 'Interacting with scenery'
text 'You can interact with many items of the scenery by simply clicking on them. Right '\
'clicking will also give more options. Click on the door indicated with the yellow '\
'arrow to go through to the next area and speak with your next instructor.'
end
# The instruction displayed after the player has left the initial building.
dialogue :moving_around do
type :text
title 'Moving around'
text 'Follow the path to find the next instructor. Clicking on the ground will walk you to '\
'that point. Talk to the survival expert by the pond to continue the tutorial. '\
'Remember you can rotate the view by pressing the arrow keys.'
end
# The instruction displayed after the player has been given the tinderbox and bronze axe by the
# Survival Guide.
dialogue :viewing_items do
type :text
title 'Viewing the items you were given'
text 'Click on the flashing backpack icon to the right side of the main window to view your '\
'inventory. Your inventory is a list of everything you have in your backpack.'
end
# The instruction displayed if the player tries to cut a tree before having the axe.
dialogue :try_cut_tree do
type :text
title 'Follow the guide\'s instructions'
text 'You cannot cut down this tree, you must first follow the guide\'s instructions.'
end
# The instruction displayed before the player has begun to cut the tree.
dialogue :cut_tree do
type :text
title 'Cut down a tree'
text 'You can click on the backpack icon at any time to view the items that you currently '\
'have in your inventory. You will see that you now have an axe in your inventory. '\
'Use this to get some logs by clicking on the indicated tree.'
end
# The instruction displayed when the player begins to cut the tree.
dialogue :please_wait do
type :text
title 'Please wait...'
text 'Your character is now attempting to cut down the tree. Sit back for a moment whilst '\
'he does all the hard work.' # TODO: she instead of he if applicable
end
# The instruction displayed after the player has successfully cut logs from the tree.
dialogue :make_a_fire do
type :text
title 'Making a fire'
text 'Well done! You managed to cut some logs from the tree! Next, use the tinderbox in '\
'your inventory to light the logs. First click on the tinderbox to \'use\' it.'\
'Then click on the logs in your inventory to light them.'
end
# The instruction displayed when the player begins to light the fire.
dialogue :lighting_fire do
type :text
title 'Please wait...'
# TODO: she instead of he if applicable
text 'Your character is now attempting to light the logs. Sit back for a moment whilst he '\
'does all the hard work.'
end
# The instruction displayed when the has lit the logs.
dialogue :gained_experience do
type :text
text 'You gained some experience.'\
'Click on the flashing bar graph icon near the inventory button to see your skill '\
'stats.'
end
# The dialogue displayed when the Player has clicked the flashing skill tab icon.
dialogue :skill_stats do
type :text
title 'Your skill stats.'
text 'Here you will see how good your skills are. As you move your mouse over any of the '\
'icons in this panel, the small yellow popup box will show you the exact amount of '\
'experience you have and how much is needed to get to the next level. Speak to the '\
'Survival Expert to continue.'
end
end
end
@@ -0,0 +1,40 @@
# Functional npcs
# 'Above-ground' npcs
spawn_npc name: :master_chef, x: 3076, y: 3085
spawn_npc name: :quest_guide, x: 3086, y: 3122, face: :north
spawn_npc name: :financial_advisor, x: 3127, y: 3124, face: :west
spawn_npc name: :brother_brace, x: 3124, y: 3107, face: :east
spawn_npc name: :magic_instructor, x: 3140, y: 3085
# 'Below-ground' npcs
# Note: They aren't actually on a different plane, they're just in a different location that
# pretends to be underground.
spawn_npc name: :mining_instructor, x: 3081, y: 9504
spawn_npc name: :combat_instructor, x: 3104, y: 9506
# Non-humanoid npcs
spawn_npc name: :fishing_spot_316, x: 3102, y: 3093
spawn_npc name: :chicken, x: 3140, y: 3095
spawn_npc name: :chicken, x: 3140, y: 3093
spawn_npc name: :chicken, x: 3138, y: 3092
spawn_npc name: :chicken, x: 3137, y: 3094
spawn_npc name: :chicken, x: 3138, y: 3095
# 'Below-ground' npcs
# Note: They aren't actually on a different plane, they're just in a different location that
# pretends to be underground.
spawn_npc name: :giant_rat_87, x: 3105, y: 9514
spawn_npc name: :giant_rat_87, x: 3105, y: 9517
spawn_npc name: :giant_rat_87, x: 3106, y: 9514
spawn_npc name: :giant_rat_87, x: 3104, y: 9514
spawn_npc name: :giant_rat_87, x: 3105, y: 9519
spawn_npc name: :giant_rat_87, x: 3109, y: 9516
spawn_npc name: :giant_rat_87, x: 3108, y: 9520
spawn_npc name: :giant_rat_87, x: 3102, y: 9517
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<plugin>
<id>location-tutorial-island</id>
<version>0.1</version>
<name>Tutorial Island</name>
<description>Adds functionality to Tutorial island.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>instructions.rb</script>
<script>guide.rb</script>
<script>npcs.rb</script>
<script>stages.rb</script>
<script>survival.rb</script>
<script>utils.rb</script>
</scripts>
<dependencies>
<dependency>dialogue</dependency>
<dependency>door</dependency>
<dependency>entity-spawning</dependency>
<dependency>quest</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,16 @@
private
# The array of stages in tutorial island.
@stages = []
# The stages that are used when interacting with the Runescape Guide.
RUNESCAPE_GUIDE = [:not_started, :talk_to_people, :go_through_door, :runescape_guide_finished,
:moving_around].freeze
@stages.concat(RUNESCAPE_GUIDE)
# The stages that are used when interacting with the Survival Expert.
SURVIVAL_EXPERT = [:given_axe, :cut_tree, :cutting_tree].freeze
@stages.concat(SURVIVAL_EXPERT)
quest :tutorial_island, @stages
@@ -0,0 +1,154 @@
require 'java'
java_import 'org.apollo.game.message.impl.FlashTabInterfaceMessage'
java_import 'org.apollo.game.message.impl.FlashingTabClickedMessage'
java_import 'org.apollo.game.message.impl.SwitchTabInterfaceMessage'
private
# Contains Survival Expert-related constants.
module SurvivalConstants
# The Survival Expert Npc.
@survival_expert = spawn_npc name: :survival_expert, x: 3104, y: 3095, face: :north
# The inventory tab index.
INVENTORY_TAB_INDEX = 3
# The inventory tab id.
INVENTORY_TAB_ID = 3213
# The id of the tree the Player will cut down.
TREE_ID = 3033
# The id of the bronze axe.
BRONZE_AXE = lookup_item(:bronze_axe)
# The id of the tinderbox.
TINDERBOX = lookup_item(:tinderbox)
end
# The conversation with the Survival Expert, when on tutorial island.
conversation :tutorial_survival_expert do
dialogue :introduction do
type :npc_speech
npc :survival_expert
precondition { |player| player.tutorial_island_progress == :moving_around }
text 'Hello there, newcomer. My name is Brynna. My job is to teach you a few survival tips and'\
' tricks. First off we\'re going to start with the most basic survival skill of all: '\
'making a fire.'
close { |player| add_survival_items(player) }
end
dialogue :hello_again do
type :npc_speech
npc :survival_expert
precondition { |player| player.tutorial_island_progress == :moving_around }
text 'Hello again. I\'m here to teach you a few survival tips and tricks. First off we\'re '\
'going to start with the most basic survival skill of all: making a fire.'
close { |player| add_survival_items(player) }
end
# The dialogue displayed when the Survival Expert gives the player a bronze axe.
dialogue :give_bronze_axe do
type :message_with_item
item :bronze_axe
text 'The Survival Expert gives you a bronze axe!'
close { |player| TutorialInstructions.show_instruction(player) }
end
# The dialogue displayed when the Survival Expert gives the player a tinderbox.
dialogue :give_tinderbox do
type :message_with_item
item :tinderbox
text 'The Survival Expert gives you a tinderbox!'
close { |player| TutorialInstructions.show_instruction(player) }
end
# The dialogue displayed when the Survival Expert gives the player both a bronze axe and a
# tinderbox.
dialogue :give_axe_and_tinderbox do
type :message_with_item
item :bronze_axe
# TODO: the tinderbox is also displayed - find this dialogue id. Scale looks like the default
# http://i.imgur.com/i1abN5X.png
text 'The Survival Expert gives you a tinderbox and a bronze axe!'
close do |player|
if player.tutorial_island_progress < :given_axe
player.tutorial_island_progress = :given_axe
index = SurvivalConstants::INVENTORY_TAB_INDEX
player.send(SwitchTabInterfaceMessage.new(index, SurvivalConstants::INVENTORY_TAB_ID))
player.send(FlashTabInterfaceMessage.new(index))
end
TutorialInstructions.show_instruction(player)
end
end
# The dialogue displayed when the player has succesfully cut down a tree.
dialogue :get_logs do
type :message_with_item
item :logs
text 'You get some logs.'
close { |player| TutorialInstructions.show_instruction(player) }
end
end
# Add the survival items (bronze axe and tinderbox) to the inventory of the player, if they do not
# already have them.
def add_survival_items(player)
inventory = player.inventory
unless inventory.contains(SurvivalConstants::BRONZE_AXE)
inventory.add(SurvivalConstants::BRONZE_AXE)
dialogue = :give_bronze_axe
end
unless inventory.contains(SurvivalConstants::TINDERBOX)
inventory.add(SurvivalConstants::TINDERBOX)
dialogue = (dialogue == :give_bronze_axe) ? :give_axe_and_tinderbox : :give_tinderbox
end
send_dialogue(player, get_dialogue(:tutorial_survival_expert, dialogue))
end
# Intercept the FirstObjectActionMessage to send tutorial-only events if the player is chopping
# down a tree.
on :message, :first_object_action do |player, message|
if player.in_tutorial_island && message.id == SurvivalConstants::TREE_ID
progress = player.tutorial_island_progress
if progress < :cut_tree
send_dialogue(player, get_dialogue(:tutorial_island_instructions, :try_cut_tree))
elsif player.tutorial_island_progress == :cut_tree
# Don't break the chain, so that the Woodcutting event actually happens.
player.tutorial_island_progress = :cutting_tree
end
end
end
# Intercept the FlashingTabClickedMessage to update the player's progress, if applicable.
on :message, :flashing_tab_clicked do |player, message|
if player.in_tutorial_island && message.tab == SurvivalConstants::INVENTORY_TAB_INDEX &&
player.tutorial_island_progress == :given_axe
player.tutorial_island_progress = :cut_tree
message.terminate
end
end
@@ -0,0 +1,30 @@
require 'java'
java_import 'org.apollo.game.model.entity.Player'
# Declare the tutorial island progress attribute.
declare_attribute(:tutorial_island_progress, :not_started, :persistent)
# The existing player class.
class Player
# Returns whether or not this Player is currently on tutorial island.
def in_tutorial_island
x = position.x
y = position.y
above_ground(x, y) || below_ground(x, y)
end
end
private
# Returns whether or not the specified coordinate pair is above ground on tutorial island.
def above_ground(x, y)
x >= 3053 && x <= 3156 && y >= 3056 && y <= 3136
end
# Returns whether or not the specified coordinate pair is 'below' ground on tutorial island.
def below_ground(x, y)
x >= 3072 && x <= 3118 && y >= 9492 && y <= 9535
end
+258
View File
@@ -0,0 +1,258 @@
# Generic Npc
spawn_npc name: :barbarian_woman, x: 3222, y: 3399
spawn_npc name: :bear_106, x: 3289, y: 3351
spawn_npc name: :black_knight, x: 3238, y: 3514
spawn_npc name: :black_knight, x: 3227, y: 3518
spawn_npc name: :black_knight, x: 3279, y: 3502
spawn_npc name: :dark_wizard_174, x: 3230, y: 3366
spawn_npc name: :dark_wizard_174, x: 3228, y: 3368
spawn_npc name: :dark_wizard_174, x: 3225, y: 3367
spawn_npc name: :dark_wizard_174, x: 3226, y: 3365
spawn_npc name: :dark_wizard_174, x: 3226, y: 3372
spawn_npc name: :dark_wizard_174, x: 3231, y: 3371
spawn_npc name: :dark_wizard_172, x: 3229, y: 3372
spawn_npc name: :dark_wizard_172, x: 3224, y: 3370
spawn_npc name: :dark_wizard_172, x: 3228, y: 3366
spawn_npc name: :dark_wizard_172, x: 3232, y: 3368
spawn_npc name: :dark_wizard_172, x: 3226, y: 3369
spawn_npc name: :giant_rat_87, x: 3292, y: 3375
spawn_npc name: :giant_rat_87, x: 3265, y: 3384
spawn_npc name: :giant_rat_87, x: 3267, y: 3381
spawn_npc name: :guard_368, x: 3263, y: 3407, face: :south
spawn_npc name: :jeremy_clerksin, x: 3253, y: 3477
spawn_npc name: :martina_scorsby, x: 3256, y: 3481
spawn_npc name: :man, x: 3281, y: 3500
spawn_npc name: :man, x: 3193, y: 3394
spawn_npc name: :man, x: 3159, y: 3429
spawn_npc name: :man, x: 3245, y: 3394
spawn_npc name: :man, x: 3283, y: 3492, z: 1
spawn_npc name: :man_2, x: 3263, y: 3400
spawn_npc name: :man_3, x: 3227, y: 3395, z: 1
spawn_npc name: :man_3, x: 3231, y: 3399, z: 1
spawn_npc name: :mugger, x: 3251, y: 3390
spawn_npc name: :mugger, x: 3177, y: 3363
spawn_npc name: :tramp_2792, x: 3228, y: 3410
spawn_npc name: :woman, x: 3221, y: 3396
spawn_npc name: :woman_5, x: 3279, y: 3497
spawn_npc name: :woman_25, x: 3278, y: 3492
spawn_npc name: :thief, x: 3285, y: 3500
spawn_npc name: :thief, x: 3234, y: 3389
spawn_npc name: :thief, x: 3188, y: 3383
spawn_npc name: :thief, x: 3184, y: 3390
spawn_npc name: :thief, x: 3188, y: 3394
spawn_npc name: :unicorn, x: 3286, y: 3342
spawn_npc name: :unicorn, x: 3279, y: 3345
# North Guards
spawn_npc name: :guard, x: 3244, y: 3500
spawn_npc name: :guard, x: 3247, y: 3503
# East Guards
spawn_npc name: :guard, x: 3271, y: 3431
spawn_npc name: :guard, x: 3270, y: 3425
spawn_npc name: :guard, x: 3274, y: 3421
spawn_npc name: :guard, x: 3274, y: 3427
# South Guards
spawn_npc name: :guard, x: 3210, y: 3382
spawn_npc name: :guard, x: 3212, y: 3380
spawn_npc name: :guard, x: 3207, y: 3376
# West Guards
spawn_npc name: :guard, x: 3174, y: 3427
spawn_npc name: :guard, x: 3176, y: 3430
spawn_npc name: :guard, x: 3176, y: 3427
spawn_npc name: :guard, x: 3180, y: 3399
spawn_npc name: :guard, x: 3175, y: 3415, z: 1
spawn_npc name: :guard, x: 3174, y: 3403, z: 1
# Varrock Palace
spawn_npc name: :guard, x: 3210, y: 3461
spawn_npc name: :guard, x: 3211, y: 3465
spawn_npc name: :guard, x: 3214, y: 3462
spawn_npc name: :guard, x: 3216, y: 3464
spawn_npc name: :guard, x: 3220, y: 3461
spawn_npc name: :guard, x: 3206, y: 3461
spawn_npc name: :guard, x: 3204, y: 3495
spawn_npc name: :guard, x: 3204, y: 3495, z: 1
spawn_npc name: :guard, x: 3205, y: 3492, z: 1
spawn_npc name: :guard, x: 3203, y: 3492, z: 1
spawn_npc name: :guard, x: 3205, y: 3497, z: 1
spawn_npc name: :guard, x: 3221, y: 3471, z: 2
spawn_npc name: :guard, x: 3214, y: 3474, z: 2
spawn_npc name: :guard, x: 3215, y: 3471, z: 2
spawn_npc name: :guard, x: 3211, y: 3471, z: 2
spawn_npc name: :guard, x: 3209, y: 3473, z: 2
spawn_npc name: :guard, x: 3212, y: 3475, z: 2
spawn_npc name: :guard, x: 3207, y: 3477, z: 2
spawn_npc name: :guard, x: 3203, y: 3476, z: 2
spawn_npc name: :guard, x: 3205, y: 3479, z: 2
spawn_npc name: :guard, x: 3203, y: 3483, z: 2
spawn_npc name: :guard, x: 3221, y: 3485, z: 2
spawn_npc name: :monk_of_zamorak_189, x: 3213, y: 3476
spawn_npc name: :warrior_woman, x: 3203, y: 3490
spawn_npc name: :warrior_woman, x: 3205, y: 3493
# Varrock/Lumbridge Pen
spawn_npc name: :swan, x: 3261, y: 3354
spawn_npc name: :swan, x: 3260, y: 3356
spawn_npc name: :ram_3673, x: 3238, y: 3346
spawn_npc name: :ram_3673, x: 3248, y: 3352
spawn_npc name: :ram_3673, x: 3260, y: 3348
spawn_npc name: :sheep_42, x: 3263, y: 3347
spawn_npc name: :sheep_42, x: 3268, y: 3350
spawn_npc name: :sheep_42, x: 3252, y: 3352
spawn_npc name: :sheep_42, x: 3243, y: 3344
spawn_npc name: :sheep_42, x: 3235, y: 3347
spawn_npc name: :sheep_3579, x: 3234, y: 3344
spawn_npc name: :sheep_3579, x: 3241, y: 3347
spawn_npc name: :sheep_3579, x: 3257, y: 3350
# Champions Guild
spawn_npc name: :chicken, x: 3195, y: 3359
spawn_npc name: :chicken, x: 3198, y: 3356
spawn_npc name: :chicken, x: 3195, y: 3355
spawn_npc name: :chicken_1017, x: 3196, y: 3353
spawn_npc name: :chicken_1017, x: 3197, y: 3356
spawn_npc name: :evil_chicken, x: 3198, y: 3359
# Function Npc
spawn_npc name: :apothecary, x: 3196, y: 3403
spawn_npc name: :captain_rovin, x: 3204, y: 3496, z: 2
spawn_npc name: :curator, x: 3256, y: 3447
spawn_npc name: :dimintheis, x: 3280, y: 3403
spawn_npc name: :dr_harlow, x: 3224, y: 3398
spawn_npc name: :ellamaria, x: 3228, y: 3475
spawn_npc name: :father_lawrence, x: 3253, y: 3484
spawn_npc name: :guidors_wife_342, x: 3280, y: 3382
spawn_npc name: :guidor, x: 3284, y: 3381, face: :south
spawn_npc name: :guild_master, x: 3189, y: 3360
spawn_npc name: :gypsy, x: 3203, y: 3423
spawn_npc name: :hooknosed_jack, x: 3268, y: 3400
spawn_npc name: :jonny_the_beard, x: 3223, y: 3395
spawn_npc name: :johnathon, x: 3278, y: 3503, z: 1
spawn_npc name: :katrine, x: 3185, y: 3386
spawn_npc name: :king_roald, x: 3223, y: 3473
spawn_npc name: :master_farmer, x: 3243, y: 3349
spawn_npc name: :pox, x: 3267, y: 3399
spawn_npc name: :reldo, x: 3210, y: 3492
spawn_npc name: :romeo, x: 3211, y: 3423
spawn_npc name: :shilop, x: 3221, y: 3435
spawn_npc name: :sir_prysin, x: 3204, y: 3472
spawn_npc name: :tarquin, x: 3203, y: 3344, face: :south
spawn_npc name: :tool_leprechaun, x: 3182, y: 3355
spawn_npc name: :tool_leprechaun, x: 3229, y: 3455, face: :north
spawn_npc name: :tramp_641, x: 3207, y: 3392
spawn_npc name: :wilough, x: 3222, y: 3437
# Shop Npc
spawn_npc name: :aubury, x: 3253, y: 3401
spawn_npc name: :baraek, x: 3217, y: 3434
spawn_npc name: :bartender, x: 3226, y: 3400
spawn_npc name: :bartender_1921, x: 3277, y: 3487
spawn_npc name: :fancy_dress_shop_owner, x: 3281, y: 3398
spawn_npc name: :horvik, x: 3229, y: 3438
spawn_npc name: :lowe, x: 3233, y: 3421
spawn_npc name: :scavvo, x: 3192, y: 3353, z: 1
spawn_npc name: :shop_keeper_551, x: 3206, y: 3399
spawn_npc name: :shop_assistant_552, x: 3207, y: 3396
spawn_npc name: :shop_keeper_522, x: 3216, y: 3414
spawn_npc name: :shop_assistant_523, x: 3216, y: 3417
spawn_npc name: :tea_seller, x: 3271, y: 3411
spawn_npc name: :thessalia, x: 3206, y: 3417
spawn_npc name: :zaff, x: 3203, y: 3434
# Juliet House
spawn_npc name: :draul_leptoc, x: 3228, y: 3475
spawn_npc name: :juliet, x: 3159, y: 3425, z: 1
spawn_npc name: :phillipa, x: 3160, y: 3429, z: 1
# Gertrude House
spawn_npc name: :gertrude, x: 3153, y: 3413
spawn_npc name: :kanel, x: 3155, y: 3405, face: :east
spawn_npc name: :philop, x: 3150, y: 3405, face: :south
# Small Bank
spawn_npc name: :banker_495, x: 3252, y: 3418, face: :north
spawn_npc name: :banker_494, x: 3253, y: 3418, face: :north
spawn_npc name: :banker_494, x: 3254, y: 3418, face: :north
spawn_npc name: :banker_494, x: 3256, y: 3418, face: :north
# Big Bank
spawn_npc name: :banker_494, x: 3187, y: 3436, face: :west
spawn_npc name: :banker_495, x: 3187, y: 3438, face: :west
spawn_npc name: :banker_494, x: 3187, y: 3440, face: :west
spawn_npc name: :banker_495, x: 3187, y: 3442, face: :west
spawn_npc name: :banker_494, x: 3187, y: 3444, face: :west
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<plugin>
<id>location-varrock</id>
<version>0.1</version>
<name>Varrock</name>
<description>Adds functionality to Varrock.</description>
<authors>
<author>Wizard Jesse</author>
</authors>
<scripts>
<script>npcs.rb</script>
<script>shops.rb</script>
</scripts>
<dependencies>
<dependency>entity-spawning</dependency>
<dependency>shops</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,44 @@
create_shop npcs: :aubury, name: "Aubury's Rune Shop.", items: [
[:fire_rune, 5_000], [:water_rune, 5_000], [:air_rune, 5_000], [:earth_rune, 5_000],
[:mind_rune, 5_000], [:body_rune, 5_000 ], [:chaos_rune, 250], [:death_rune, 250 ]
]
create_shop npcs: :lowe, name: "Lowe's Archery Emporium", items: [
[:bronze_arrow, 2_000], [:iron_arrow, 1_500], [:steel_arrow, 1_000],
[:mithril_arrow, 800], [:adamant_arrow, 600], [:shortbow, 4], [:longbow, 4],
[:oak_shortbow, 3], [:oak_longbow, 3], [:willow_shortbow, 2], [:willow_longbow, 2],
[:maple_shortbow, 1], [:maple_longbow, 1], [:crossbow, 2]
]
create_shop npcs: :horvik, buys: :all, name: "Horvik's Armour Shop.", items: [
[:bronze_chainbody, 5], [:iron_chainbody, 3], [:steel_chainbody, 3],
[:mithril_chainbody, 1], [:bronze_platebody, 3], [:iron_platebody, 1],
[:steel_platebody, 1], [:black_platebody, 1], [:mithril_platebody, 1],
[:iron_platelegs, 1], [:studded_body, 1], [:studded_chaps, 1]
]
create_shop npcs: :thessalia, name: "Thessalia's Fine Clothes.", items: [
[:white_apron, 3], [:leather_body, 12], [:leather_gloves, 10], [:leather_boots, 10],
[:brown_apron, 1], [:pink_skirt, 5], [:black_skirt, 3], [:blue_skirt, 2], [:cape, 4],
[:silk, 5], [:priest_gown_428, 3], [:priest_gown_426, 3]
]
create_shop npcs: [:shop_keeper_522, :shop_assistant_523], buys: :all,
name: 'Varrock General Store', items: [
[:pot, 5], [:jug, 2], [:shears, 2], [:bucket, 3], [:bowl, 2], [:cake_tin, 2],
[:tinderbox, 2], [:chisel, 2], [:hammer, 5], [:newcomer_map, 5]
]
create_shop npcs: [:shop_keeper_551, :shop_assistant_552], name: 'Varrock Swordshop', items: [
[:bronze_sword, 5], [:iron_sword, 4], [:steel_sword, 4], [:black_sword, 3],
[:mithril_sword, 3], [:adamant_sword, 2], [:bronze_longsword, 4], [:iron_longsword, 3],
[:steel_longsword, 3], [:black_longsword, 2], [:mithril_longsword, 2],
[:adamant_longsword, 1], [:bronze_dagger, 10], [:iron_dagger, 6], [:steel_dagger, 5],
[:black_dagger, 4], [:mithril_dagger, 3], [:adamant_dagger, 2]
]
create_shop npcs: :zaff, name: "Zaff's Superior Staffs!", items: [
[:battlestaff, 5], [:staff, 5], [:magic_staff, 5], [:staff_of_air, 2],
[:staff_of_water, 2], [:staff_of_earth, 2], [:staff_of_fire, 2]
]
+5
View File
@@ -0,0 +1,5 @@
LOGOUT_BUTTON_ID = 2458
on :button, LOGOUT_BUTTON_ID do |player|
player.logout
end
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<plugin>
<id>logout</id>
<version>1</version>
<name>Logout Button</name>
<description>Adds the logout button.</description>
<authors>
<author>Graham</author>
</authors>
<scripts>
<script>logout.rb</script>
</scripts>
<dependencies />
</plugin>
@@ -0,0 +1,44 @@
require 'set'
# Contains door-related constants.
module DoorConstants
# TODO: GameObjectOrientation enumeration in Apollo's core?
# The orientation of a door.
module Orientation
WEST = 0
NORTH = 1
EAST = 2
SOUTH = 3
end
# The size of a door object.
DOOR_SIZE = 1
# Door object ids that have a hinge on the left side.
LEFT_HINGE_DOORS = Set.new [1516, 1536, 1533]
# Door object ids that have a hinge on the right side.
RIGHT_HINGE_DOORS = Set.new [1519, 1530, 4465, 4467, 3014, 3017, 3018, 3019]
# The hash of orientations that a door will translate to when opened.
ORIENTATIONS = {
# Orientations for doors that have a hinge on the left side.
left_side_hinge: {
Orientation::NORTH => Orientation::WEST,
Orientation::SOUTH => Orientation::EAST,
Orientation::WEST => Orientation::SOUTH,
Orientation::EAST => Orientation::NORTH
},
# Orientations for doors that have a hinge on the right side.
right_side_hinge: {
Orientation::NORTH => Orientation::EAST,
Orientation::SOUTH => Orientation::WEST,
Orientation::WEST => Orientation::NORTH,
Orientation::EAST => Orientation::SOUTH
}
}
end
+54
View File
@@ -0,0 +1,54 @@
java_import 'org.apollo.game.action.DistancedAction'
java_import 'org.apollo.game.model.event.Event'
private
# A distanced action which opens a door.
class OpenDoorAction < DistancedAction
include DoorConstants
attr_reader :door
def initialize(mob, door)
super(0, true, mob, door.position, DOOR_SIZE)
@door = door
end
def executeAction
if $world.submit(OpenDoorEvent.new(mob, @door))
mob.turn_to(@door.position)
DoorUtil.toggle(@door)
end
stop
end
def equals(other)
get_class == other.get_class && @door == other.door
end
end
# A PlayerEvent that is fired when a player attempts to open a door.
class OpenDoorEvent < PlayerEvent
attr_reader :door
def initialize(player, door)
super(player)
@door = door
end
end
# Listens for FirstObjectActions performed on doors.
on :message, :first_object_action do |player, message|
id = message.id
if DoorUtil.door?(id)
door = DoorUtil.get_door_object(message.position, id)
player.start_action(OpenDoorAction.new(player, door)) unless door.nil?
end
end
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>door</id>
<version>1</version>
<name>Doors</name>
<description>Adds support for doors throughout the game.</description>
<authors>
<author>Shiver</author>
</authors>
<scripts>
<script>constants.rb</script>
<script>util.rb</script>
<script>door.rb</script>
</scripts>
<dependencies/>
</plugin>
+83
View File
@@ -0,0 +1,83 @@
require 'java'
java_import 'org.apollo.game.model.Position'
java_import 'org.apollo.game.model.area.Region'
java_import 'org.apollo.game.model.entity.Entity'
java_import 'org.apollo.game.model.entity.obj.DynamicGameObject'
# Contains door-related utility methods.
module DoorUtil
include DoorConstants
# A hash containing currently toggled door objects mapped to the original door objects.
TOGGLED_DOORS = {}
# Translates a door's position in the direction of its orientation.
def self.translate_door_position(door)
position = door.position
case door.orientation
when Orientation::WEST
Position.new(position.x - 1, position.y, position.height)
when Orientation::EAST
Position.new(position.x + 1, position.y, position.height)
when Orientation::NORTH
Position.new(position.x, position.y + 1, position.height)
when Orientation::SOUTH
Position.new(position.x, position.y - 1, position.height)
else fail "Unsupported orientation #{door.orientation}."
end
end
# Translates the orientation of a door to a toggled position.
def self.translate_door_orientation(door)
object_id = door.id
orientation = door.orientation
if RIGHT_HINGE_DOORS.include?(object_id)
return ORIENTATIONS[:right_side_hinge][orientation]
elsif LEFT_HINGE_DOORS.include?(object_id)
return ORIENTATIONS[:left_side_hinge][orientation]
end
fail 'Given object was not registered as a door.'
end
# Toggles the given door.
def self.toggle(door)
region = $world.region_repository.from_position(door.position)
region.remove_entity(door)
if TOGGLED_DOORS.include?(door)
original_door = TOGGLED_DOORS.delete(door)
original_region = $world.region_repository.from_position(original_door.position)
original_region.add_entity(original_door)
else
position = translate_door_position(door)
orientation = translate_door_orientation(door)
type = door.type
toggled_door = DynamicGameObject.create_public($world, door.id, position, type, orientation)
toggled_region = $world.region_repository.from_position(position)
toggled_region.add_entity(toggled_door)
TOGGLED_DOORS[toggled_door] = door
end
end
# Gets the door object at the given position, if it exists.
def self.get_door_object(position, object_id)
region = $world.region_repository.from_position(position)
objects = region.get_entities(position, EntityType::DYNAMIC_OBJECT, EntityType::STATIC_OBJECT)
objects.each { |game_object| return game_object if game_object.id == object_id }
nil
end
# Checks if the given game object id is a door.
def self.door?(object_id)
RIGHT_HINGE_DOORS.include?(object_id) || LEFT_HINGE_DOORS.include?(object_id)
end
end
+61
View File
@@ -0,0 +1,61 @@
require 'java'
java_import 'org.apollo.game.message.impl.SetPlayerActionMessage'
java_import 'org.apollo.game.model.entity.Player'
# A right-click action for a Player.
class PlayerAction
attr_reader :slot, :primary, :name
def initialize(slot, primary, name)
index = [:first, :second, :third, :fourth, :fifth].find_index(slot)
fail "Unsupported action slot #{slot}." if index.nil?
@slot = index
@primary = primary
@name = name
end
end
ATTACK_ACTION = PlayerAction.new(:second, true, 'Attack')
CHALLENGE_ACTION = PlayerAction.new(:second, true, 'Challenge')
FOLLOW_ACTION = PlayerAction.new(:fourth, true, 'Follow')
TRADE_ACTION = PlayerAction.new(:fifth, true, 'Trade with')
# Shows multiple context menu action for the specified player
def show_actions(player, *actions)
fail 'Must specify at least one action.' if actions.nil?
actions.each do |action|
player.add_action(action)
player.send(SetPlayerActionMessage.new(action.name, action.slot, action.primary))
end
end
# Shows a single context menu action for the specified player
def show_action(player, action)
show_actions(player, action)
end
# Hides a context menu action for the specified player
def hide_action(player, action)
player.send(SetPlayerActionMessage.new('null', action.slot, action.primary))
end
# Monkey-patch Player to provide action utility methods.
class Player
def actions
@actions ||= {}
end
def add_action(action)
actions[action.slot] = action.name
end
def action?(action)
actions[action.slot] == action.name
end
end
+6
View File
@@ -0,0 +1,6 @@
java_import 'org.apollo.game.model.entity.Player'
on :login do |_event, player|
show_action(player, TRADE_ACTION)
show_action(player, FOLLOW_ACTION)
end
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<plugin>
<id>player-action</id>
<version>1</version>
<name>Player actions</name>
<description>Manages player right click actions</description>
<authors>
<author>Ryley</author>
</authors>
<scripts>
<script>action.rb</script>
<script>login.rb</script>
</scripts>
<dependencies />
</plugin>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>quest</id>
<version>0.9</version>
<name>Quest</name>
<description>Adds </description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>repository.rb</script>
</scripts>
<dependencies>
<dependency>attributes</dependency>
</dependencies>
</plugin>
+132
View File
@@ -0,0 +1,132 @@
# Defines a quest with the specified name.
def quest(name, stage_names)
stages = {}
stage_names.each_with_index { |stage, index| stages[stage] = QuestStage.new(stage, index, name) }
QUESTS[name] = Quest.new(name, stages)
end
private
# The repository of quests.
QUESTS = {}
# An ingame Quest.
class Quest
attr_reader :name
# Creates the Quest.
def initialize(name, stages)
fail "Quest name must be a symbol, received '#{name}'." unless name.is_a?(Symbol)
@name = name
@stages = stages
end
# Gets the finishing quest stage (i.e. the stage that indicates the Player has completed the
# quest).
def final_stage
@stages.last
end
# Gets the starting quest stage.
def initial_stage
@stages.first
end
# Gets the QuestStage with the specified name.
def stage(name)
stage = @stages[name]
fail "No stage named #{name} exists in #{@name}." if stage.nil?
stage
end
end
# A stage in a quest, indicating the progress of a Player.
class QuestStage
attr_reader :name, :index
# Creates the QuestProgress.
def initialize(name, index, quest, log_text = nil)
@name = name
@index = index
@quest = quest
@log_text = log_text
end
# Returns whether or no this quest stage should be logged.
def logged
!@log_text.nil?
end
# Gets the log text for this stage.
def log_text
fail 'Cannot get the log text from an unlogged quest stage.' unless logged
@log_text
end
# Defines the equality operator.
def ==(other)
@index == index_of(other)
end
# Defines the not equal operator.
def !=(other)
@index != index_of(other)
end
# Defines the greater than or equal to operator.
def >=(other)
@index >= index_of(other)
end
# Defines the greater than operator.
def >(other)
@index > index_of(other)
end
# Defines the less than operator.
def <(other)
@index < index_of(other)
end
# Defines the less than or equal to operator.
def <=(other)
@index <= index_of(other)
end
private
# Gets the index of the QuestStage with the specified name.
def index_of(name)
QUESTS[@quest].stage(name).index
end
end
# Define method_missing for player
class Player
# Override method_missing to return a QuestStage if the method name indicates quest.
def method_missing(symbol, *args)
unless args.nil?
arg = args[0]
args[0] = arg.name if arg.is_a?(QuestStage)
end
result = super(symbol, *args)
string = symbol.to_s
if string.end_with?('_progress')
name = string[0..-10] # Cut the '_progress' from the end
quest = QUESTS[name.to_sym]
fail "No Quest with the name '#{name}' exists." if quest.nil?
result = quest.stage(result)
end
result
end
end
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>run</id>
<version>1</version>
<name>Running</name>
<description>Adds support for running via the options interface.</description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>run.rb</script>
</scripts>
<dependencies>
<dependency>attributes</dependency>
</dependencies>
</plugin>
+10
View File
@@ -0,0 +1,10 @@
WALK_BUTTON_ID = 152
RUN_BUTTON_ID = 153
on :button, WALK_BUTTON_ID do |player|
player.toggle_running
end
on :button, RUN_BUTTON_ID do |player|
player.toggle_running
end
+35
View File
@@ -0,0 +1,35 @@
require 'java'
java_import 'org.apollo.cache.def.ItemDefinition'
# A currency that can be used to purchase items in a Shop.
class Currency
attr_reader :name
# Creates the Currency.
def initialize(id, name = ItemDefinition.lookup(id).name)
fail 'Currency must have a name.' if name.nil?
@id = id
@name = name.to_s
end
# Adds the specified amount of this `Currency` to the specified `Player`'s inventory.
def add(player, amount)
player.inventory.add(@id, amount)
end
# Removes the specified amount of this `Currency` from the specified `Player`'s inventory.
def remove(player, amount)
player.inventory.remove(@id, amount)
end
# Gets the amount of this Currency in the specified player's inventory.
def total(player)
player.inventory.get_amount(@id)
end
def sell_value(id)
(ItemDefinition.lookup(id).value * 0.60).floor
end
end
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<plugin>
<id>shops</id>
<version>0.1</version>
<name>Shops</name>
<description>Adds shop support.</description>
<authors>
<author>Stuart</author>
<author>Major</author>
</authors>
<scripts>
<script>currency.rb</script>
<script>shop.rb</script>
<script>shop_item.rb</script>
<script>shops.rb</script>
</scripts>
<dependencies>
<dependency>util</dependency>
</dependencies>
</plugin>
+28
View File
@@ -0,0 +1,28 @@
require 'java'
java_import 'org.apollo.game.model.inv.Inventory'
# A shop containing items that can be sold.
class Shop
attr_reader :buys, :currency, :items, :inventory, :name, :npc_options
def initialize(name, items, currency, options, buys)
@name = name
@items = items
@currency = currency
@buys = buys
@npc_options = options
@inventory = Inventory.new(DEFAULT_CAPACITY, Inventory::StackMode::STACK_ALWAYS)
items.each { |item| @inventory.add(item.id, item.amount) }
end
end
private
# The `Currency` used by default.
DEFAULT_CURRENCY = Currency.new(995, 'coins')
# The default capacity of a shop.
DEFAULT_CAPACITY = 30
+20
View File
@@ -0,0 +1,20 @@
require 'java'
java_import 'org.apollo.cache.def.ItemDefinition'
java_import 'org.apollo.game.model.Item'
# An Item in a Shop.
class ShopItem
attr_reader :amount, :cost, :id, :name
# Creates the ShopItem.
def initialize(id, amount, cost = nil)
definition = ItemDefinition.lookup(id)
@id = id
@amount = amount
@cost = cost.nil? ? definition.value : cost
@name = definition.name
end
end
+301
View File
@@ -0,0 +1,301 @@
require 'java'
java_import 'org.apollo.cache.def.ItemDefinition'
java_import 'org.apollo.game.action.DistancedAction'
java_import 'org.apollo.game.message.impl.SetWidgetTextMessage'
java_import 'org.apollo.game.message.handler.ItemVerificationHandler'
java_import 'org.apollo.game.model.inv.SynchronizationInventoryListener'
java_import 'org.apollo.game.model.inter.InterfaceListener'
# The hash of npc ids to Shops.
SHOPS = {}
# Creates the Shop from the specified Hash.
def create_shop(hash)
unless hash.has_keys?(:items, :name, :npcs)
fail 'Shop name, npcs, and items must be specified to create a shop.'
end
npcs, name = hash[:npcs], hash[:name]
npcs = [npcs] unless npcs.is_a?(Array)
currency = hash[:currency] || DEFAULT_CURRENCY
options = hash[:npc_options] || [1]
buys = hash[:buys] || :own
items = hash.delete(:items).collect { |data| ShopItem.new(lookup_item(data[0]), data[1]) }
shop = Shop.new(name, items, currency, options, buys)
npcs.map { |name| lookup_npc(name) }.each { |npc| SHOPS[npc] = shop }
end
private
# The sidebar id for the inventory, when a Shop window is open.
INVENTORY_SIDEBAR = 3822
# The container id for the above inventory, when a Shop window is open.
INVENTORY_CONTAINER = 3823
# The Shop interface id.
SHOP_INTERFACE = 3824
# The container id for the Shop interface.
SHOP_CONTAINER = 3900
# The widget that displays the shop name.
SHOP_NAME_WIDGET = 3901
# The delay before a Shop is opened when the Player is in range of the Npc, in ticks.
SHOP_OPEN_DELAY = 0
# The distance, in tiles, the Player must reach before a Shop can be opened.
SHOP_DISTANCE = 1
# An `InventorySupplier` for a `Shop`.
class ShopInventorySupplier
java_implements ItemVerificationHandler::InventorySupplier
def getInventory(player)
shop = player.open_shop
shop == -1 ? nil : SHOPS[shop].inventory
end
end
# An `InventorySupplier` for a `Player` with the shop window open.
class PlayerInventorySupplier
java_implements ItemVerificationHandler::InventorySupplier
def getInventory(player)
player.open_shop == -1 ? nil : player.inventory
end
end
ItemVerificationHandler.add_inventory(SHOP_CONTAINER, ShopInventorySupplier.new)
ItemVerificationHandler.add_inventory(INVENTORY_CONTAINER, PlayerInventorySupplier.new)
# A DistancedAction causing a Player to open a shop.
class OpenShopAction < DistancedAction
attr_reader :player, :npc, :shop
# Creates the OpenShopAction.
def initialize(player, npc, shop)
super(SHOP_OPEN_DELAY, true, player, npc.position, 1)
@npc = npc
@shop = shop
end
# Executes this DistancedAction, opening the shop.
def executeAction
mob.interacting_mob = @npc
open_shop(mob, @npc.id)
stop
end
# Returns whether or not this DistancedAction is equal to the specified Object.
def equals(other)
get_class == other.get_class && @npc == other.npc && @shop == other.shop
end
end
# An InterfaceListener for when a Shop is closed.
class ShopCloseInterfaceListener
java_implements InterfaceListener
# Creates the ShopCloseInterfaceListener.
def initialize(player, inventory_listener, shop_listener)
@player = player
@inventory_listener = inventory_listener
@shop_listener = shop_listener
end
# Executed when the Shop interface is closed.
def interface_closed
@player.inventory.remove_listener(@inventory_listener)
SHOPS[@player.open_shop].inventory.remove_listener(@shop_listener)
@player.open_shop = -1
@player.reset_interacting_mob
end
end
# Intercept the npc action message.
on :message, :first_npc_action do |player, message|
npc = $world.npc_repository.get(message.index)
if SHOPS.key?(npc.id)
shop = SHOPS[npc.id]
valid = shop.npc_options.empty? || shop.npc_options.include?(message.option)
player.start_action(OpenShopAction.new(player, npc, SHOPS[npc.id])) if valid
end
end
# Opens the Shop registered to the specified npc.
def open_shop(player, npc)
shop = SHOPS[npc]
fail "No shop registered to npc #{npc} exists." if shop.nil?
player.open_shop = npc
inventory_listener = SynchronizationInventoryListener.new(player, INVENTORY_CONTAINER)
shop_listener = SynchronizationInventoryListener.new(player, SHOP_CONTAINER)
player_inventory, shop_inventory = player.inventory, shop.inventory
player_inventory.add_listener(inventory_listener)
player_inventory.force_refresh
shop_inventory.add_listener(shop_listener)
shop_inventory.force_refresh
player.send(SetWidgetTextMessage.new(SHOP_NAME_WIDGET, shop.name))
listener = ShopCloseInterfaceListener.new(player, inventory_listener, shop_listener)
player.interface_set.open_window_with_sidebar(listener, SHOP_INTERFACE, INVENTORY_SIDEBAR)
end
# Intercept the Item action.
on :message, :item_action do |player, message|
interface = message.interface_id
if player.open_shop == -1 || !SHOPS.key?(player.open_shop)
message.terminate
next
end
if interface != INVENTORY_CONTAINER && interface != SHOP_CONTAINER
message.terminate
next
end
shop = SHOPS[player.open_shop]
inventory = shop.inventory
currency = shop.currency
slot = message.slot
player_inventory = player.inventory
if interface == INVENTORY_CONTAINER
id = message.id
contains = inventory.contains(id)
if !shop.buys == :none || shop.buys == :own && !contains
player.send_message('You can\'t sell this item to this shop.')
message.terminate
next
end
if !contains && inventory.free_slots == 0
player.send_message('The shop is currently full at the moment.')
message.terminate
next
end
item = player_inventory.get(slot)
value = currency.sell_value(id)
option = message.option
if option == 1
player.send_message("#{item.definition.name}: shop will buy for #{value} #{currency.name}.")
next
end
sell_amount = case option
when 2 then 1
when 3 then 5
when 4 then 10
else next
end
available = player_inventory.get_amount(id)
sell_amount = available if sell_amount > available
total_value = (value * sell_amount).floor
player_inventory.remove(id, sell_amount)
inventory.add(id, sell_amount)
currency.add(player, total_value) if total_value > 0
message.terminate
elsif interface == SHOP_CONTAINER
buy(shop, player, message, currency)
end
end
# Buys the item from the `Shop`.
def buy(shop, player, message, currency)
inventory, slot = shop.inventory, message.slot
shop_item, invent_item = shop.items[slot], inventory.get(slot)
id = shop_item.id
option = message.option
if option == 1
player.send_message("#{shop_item.name}: currently costs #{shop_item.cost} #{currency.name}.")
return
end
buy_amount = case option
when 2 then 1
when 3 then 5
when 4 then 10
else next
end
no_stock = false
if buy_amount > invent_item.amount
buy_amount = invent_item.amount
no_stock = true
end
player_inventory = player.inventory
has_item = player_inventory.get_amount(id) == 0
definition = invent_item.definition
space_required = if definition.stackable && has_item then 0
elsif !definition.stackable then buy_amount
else 1
end
free_slots = player_inventory.free_slots
not_enough_space = false
if space_required > free_slots
not_enough_space = true
buy_amount = free_slots
end
total_currency = shop.currency.total(player)
too_poor = false
total_cost = buy_amount * shop_item.cost
if total_cost > total_currency
buy_amount = (total_currency / shop_item.cost).floor
too_poor = true
end
if buy_amount > 0
currency.remove(player, buy_amount * shop_item.cost)
player_inventory.add(id, buy_amount)
keep = invent_item.amount == buy_amount && shop.buys == :own
keep ? inventory.set(slot, Item.new(id, 0)) : inventory.remove(id, buy_amount)
end
warning = if too_poor then "You don't have enough #{currency.name}."
elsif no_stock then 'The shop has run out of stock.'
elsif not_enough_space then 'You don\'t have enough inventory space.'
end
player.send_message(warning) unless warning.nil?
message.terminate
end
# Declares the open_shop attribute, which contains the id of the currently open shop.
declare_attribute(:open_shop, -1)
+41
View File
@@ -0,0 +1,41 @@
# The hash of names to fish.
CATCHABLE_FISH = {}
# A fish that can be caught.
class Fish
attr_reader :id, :level, :experience, :name
# Creates the Fish.
def initialize(id, level, experience)
@id = id
@level = level
@experience = experience
@name = name_of(:item, id)
end
end
# Appends a Fish to the hash.
def append_fish(name, hash)
unless hash.has_keys?(:id, :level, :experience)
fail 'Hash must contain an id, level, and experience.'
end
CATCHABLE_FISH[name] = Fish.new(hash[:id], hash[:level], hash[:experience])
end
append_fish :shrimp, id: 317, level: 1, experience: 10
append_fish :sardine, id: 327, level: 5, experience: 20
append_fish :herring, id: 345, level: 10, experience: 30
append_fish :anchovy, id: 321, level: 15, experience: 40
append_fish :mackerel, id: 353, level: 16, experience: 20
append_fish :trout, id: 335, level: 20, experience: 50
append_fish :cod, id: 341, level: 23, experience: 45
append_fish :pike, id: 349, level: 25, experience: 60
append_fish :salmon, id: 331, level: 30, experience: 70
append_fish :tuna, id: 359, level: 35, experience: 80
append_fish :lobster, id: 377, level: 40, experience: 90
append_fish :bass, id: 363, level: 46, experience: 100
append_fish :swordfish, id: 371, level: 50, experience: 100
append_fish :shark, id: 383, level: 76, experience: 110
+120
View File
@@ -0,0 +1,120 @@
require 'java'
java_import 'org.apollo.game.action.DistancedAction'
java_import 'org.apollo.game.model.Animation'
java_import 'org.apollo.game.model.entity.Skill'
# An action that causes a mob to fish at a spot.
class FishingAction < DistancedAction
attr_reader :position, :options, :spot, :started, :tool
# Creates the FishingAction.
def initialize(mob, position, spot, option)
super(4, true, mob, position, 1)
@position = position
@spot = spot
@tool = spot.tools[option - 1]
@options = (option == 1) ? spot.first_fish : spot.second_fish
@minimum_level = @options.map(&:level).min
end
# Returns whether or not a catch is successful.
def successful_catch(level, requirement)
[level - requirement + 5, 30].min > rand(40)
end
# Starts the fishing process.
def start_fishing
@started = true
mob.send_message(tool.message)
end
# Executes the action.
def executeAction
skills = mob.skill_set
fishing_level = skills.get_skill(Skill::FISHING).current_level
mob.turn_to(position)
if @minimum_level > fishing_level
mob.send_message("You need a fishing level of #{@minimum_level} to fish at this spot.")
stop
return
end
inventory = mob.inventory
if inventory.free_slots.zero?
inventory.force_capacity_exceeded
stop
return
end
unless inventory.contains(@tool.id)
mob.send_message("You need a #{@tool.name.downcase} to fish at this spot.")
stop
return
end
bait = find_bait
if bait == -1
mob.send_message("You need #{name_of(:item, bait).downcase}s to fish at this spot.")
stop
return
end
if @started
options = @options.reject { |fish| fish.level > fishing_level }
# Player may level up mid-action so reject here, not at initialisation.
fish = options.sample # TODO: it's a ~70/30 chance, not 50/50
if successful_catch(fishing_level, fish.level)
inventory.remove(bait) unless bait.nil?
inventory.add(fish.id)
name = fish.name
mob.send_message("You catch #{name.end_with?('s') ? 'some' : 'a'} #{name.downcase}.")
skills.add_experience(Skill::FISHING, fish.experience)
if find_bait == -1
mob.send_message("You need more #{name_of(:item, bait).downcase}s to fish at this spot.")
stop
return
end
end
else
start_fishing
end
mob.play_animation(@tool.animation)
end
# Finds the id of the first piece of bait in the player's inventory, or nil if no bait is
# required, or -1 if the player's inventory does not contain any valid bait.
def find_bait
baits = @tool.bait
baits.empty? ? nil : baits.find(-1) { |bait| mob.inventory.contains(bait) }
end
# Stops this action.
def stop
super
mob.stop_animation
end
def equals(other)
get_class == other.get_class && @spot == other.spot && @position == other.position &&
@options == @other.options
end
end
# Intercepts the NpcAction message to determine whether or not a clicked npc was a fishing spot.
on :message, :npc_action do |player, message|
npc = $world.npc_repository.get(message.index)
spot = FISHING_SPOTS[npc.id]
unless spot.nil?
player.start_action(FishingAction.new(player, npc.position, spot, message.option))
message.terminate
end
end

Some files were not shown because too many files have changed in this diff Show More