mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Housekeeping
This commit is contained in:
+2
-2
@@ -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.
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,3 @@
|
||||
<login>
|
||||
<serializer>org.apollo.game.io.player.DummyPlayerSerializer</serializer>
|
||||
</login>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<net>
|
||||
<ports>
|
||||
<http>80</http>
|
||||
<service>43594</service>
|
||||
<jaggrab>43595</jaggrab>
|
||||
</ports>
|
||||
</net>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
require 'java'
|
||||
|
||||
# Opens the player's bank.
|
||||
on :command, :bank, RIGHTS_ADMIN do |player, _command|
|
||||
player.open_bank
|
||||
end
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
@@ -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
|
||||
@@ -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]
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
LOGOUT_BUTTON_ID = 2458
|
||||
|
||||
on :button, LOGOUT_BUTTON_ID do |player|
|
||||
player.logout
|
||||
end
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user