Merge branch 'master' into pathfinding.

Conflicts:.
	src/org/apollo/game/model/area/Sector.java.
This commit is contained in:
Ryley Kimmel
2015-03-03 15:03:51 -05:00
117 changed files with 1561 additions and 865 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
<login>
<loader>org.apollo.io.player.impl.DummyPlayerLoader</loader>
<saver>org.apollo.io.player.impl.DiscardPlayerSaver</saver>
<serializer>org.apollo.io.player.DummyPlayerSerializer</serializer>
</login>
+1
View File
@@ -10,6 +10,7 @@
<scripts>
<script>actions.rb</script>
<script>areas.rb</script>
<script>wilderness.rb</script>
</scripts>
<dependencies />
</plugin>
+52
View File
@@ -0,0 +1,52 @@
require 'java'
java_import 'org.apollo.game.model.entity.Player'
java_import 'org.apollo.game.message.impl.OpenOverlayMessage'
java_import 'org.apollo.game.message.impl.SetWidgetTextMessage'
private
MIN_X = 2945
MIN_Y = 3522
MAX_X = 3390
MAX_Y = 3972
OVERLAY_INTERFACE_ID = 197
LEVEL_STRING_ID = 199
declare_attribute(:wilderness_level, 0, :transient)
# Determines the wilderness level for the specified player's position
def wilderness_level(player)
return (player.position.y - (MIN_Y - 1) / 8).ceil
end
area_action :wilderness_level do
on_entry do |player|
player.wilderness_level = wilderness_level(player)
player.interface_set.open_overlay(OVERLAY_INTERFACE_ID)
player.send(SetWidgetTextMessage.new(LEVEL_STRING_ID, "Level: #{player.wilderness_level}"))
show_action(ATTACK_ACTION)
end
while_in do |player|
current = player.wilderness_level
updated = wilderness_level(player)
if (current != updated)
player.wilderness_level = updated
player.send(SetWidgetTextMessage.new(LEVEL_STRING_ID, "Level: #{player.wilderness_level}"))
end
end
on_exit do |player|
player.wilderness_level = 0
player.interface_set.close() # TODO: Will this cause issues with other potentially open interfaces?
hide_action(ATTACK_ACTION)
end
end
area :name => :wilderness, :coordinates => MIN_X, MIN_Y, MAX_X, MAX_Y, 0, => :actions => :wilderness_level
+68 -18
View File
@@ -6,6 +6,7 @@ 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.SetWidgetTextMessage'
java_import 'org.apollo.game.action.DistancedAction'
# The map of conversation names to Conversations.
CONVERSATIONS = {}
@@ -20,6 +21,30 @@ def conversation(name, &block)
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
@@ -42,15 +67,16 @@ class Conversation
@dialogues[name] = dialogue
if ((@dialogues.empty? || dialogue.has_precondition?) && dialogue.type == :npc_speech)
npc = dialogue.npc
raise 'Npc cannot be null when opening a dialogue.' if npc.nil?
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 |ctx, player, event|
if npc == $world.npc_repository.get(event.index).id
npc = $world.npc_repository.get(event.index)
if npc_index == npc.id
@starters.each do |start|
if dialogue.precondition(player)
send_dialogue(player, dialogue)
player.start_action(OpenDialogueAction.new(player, npc, dialogue))
ctx.break_handler_chain()
break
end
@@ -74,6 +100,12 @@ def declare_emote(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
@@ -144,17 +176,6 @@ class Dialogue
@options << ->(player) { action.call(player) unless type.nil?; block.call(player) unless block.nil? }
end
# 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
# Sets the emote performed by the dialogue head.
def emote(emote=nil)
unless emote.nil?
@@ -186,16 +207,21 @@ class Dialogue
end
# Sets the id of the item displayed.
def item(item=nil, scale=nil)
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 = 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?
@@ -228,7 +254,7 @@ class Dialogue
def options
@options.dup
end
# Sets the precondition of this dialogue.
def precondition(player=nil, &block)
@precondition = block unless block.nil?
@@ -296,6 +322,19 @@ class Dialogue
@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
# Inserts a copy of this Dialogue into the chain, but with different text.
@@ -350,6 +389,17 @@ 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)
+13 -7
View File
@@ -16,7 +16,7 @@ java_import 'org.apollo.game.model.entity.Npc'
# :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 [top-left x-coordinate, top-left y-coordinate, bottom-right x-coordinate, bottom-right y-coordinate]
# :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.
@@ -33,11 +33,12 @@ end
# Spawns the specified npc and applies the properties in the hash.
def spawn(npc, hash)
$world.register(npc)
unless hash.empty?
hash = decode_hash(npc.position, hash) # Use npc.position here because sector registry events (called by World.register) can be hooked
apply_decoded_hash(npc, hash) # into and someone might do something daft like move the npc immediately after it gets spawned.
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.
@@ -54,7 +55,7 @@ def apply_decoded_hash(npc, hash)
hash.each do |key, value|
case key
when :face then npc.turn_to(value)
when :boundary then npc.boundary = 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 raise "Unrecognised key #{key} - value #{value}."
@@ -71,11 +72,16 @@ def decode_hash(position, hash)
when :face
decoded[:face] = direction_to_position(value, position)
when :delta_bounds
raise ':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
raise 'Delta values cannot be less than 0.' if (dx < 0 || dy < 0)
decoded[:boundary] = [ Position.new(x + dx, y, z), Position.new(x, y + dy, z), Position.new(x - dx, y, z), Position.new(x, y - dy, z) ]
when :bounds then decoded[:boundary] = value
decoded[:boundary] = [ Position.new(x - dx, y - dy, z), Position.new(x + dx, y + dy, z) ]
when :bounds
raise ':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 raise "Unrecognised key #{key} - value #{value}."
+1 -1
View File
@@ -6,7 +6,7 @@
# :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 [top-left x-coordinate, top-left y-coordinate, bottom-right x-coordinate, bottom-right y-coordinate]
# :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.
+1 -1
View File
@@ -6,7 +6,7 @@
# :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 [top-left x-coordinate, top-left y-coordinate, bottom-right x-coordinate, bottom-right y-coordinate]
# :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.
+1 -1
View File
@@ -6,7 +6,7 @@
# :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 [top-left x-coordinate, top-left y-coordinate, bottom-right x-coordinate, bottom-right y-coordinate]
# :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.
@@ -0,0 +1,119 @@
require 'java'
java_import 'org.apollo.game.message.impl.HintIconMessage'
java_import 'org.apollo.game.message.impl.SwitchTabInterfaceMessage'
private
# 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 ]
# The character design interface id.
CHARACTER_DESIGN = 3559
# The Runescape guide Npc
@runescape_guide = spawn_npc :name => :runescape_guide, :x => 3093, :y => 3107
# 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
TutorialInstructions::show_instruction(player)
# INITIAL_TABS.each_with_index { |tab, index| player.send(SwitchTabInterfaceMessage.new(index, tab)) } # This is commented so the lame hack works
if (player.tutorial_island_progress == :not_started)
show_hint_icon(player)
player.interface_set.open_window(CHARACTER_DESIGN)
end
end
end
## TODO lame hack to get round the lack of door support - must remove
on :message, :fifth_item_option do |ctx, player, message|
player.teleport(Position.new(3100, 3107)) if message.id == 1
player.tutorial_island_progress = :moving_around
player.interface_set.open_dialogue_overlay(-1)
end
on :login do |event, player|
player.inventory.add(1)
end
## END lame hack
# 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
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 => :go_through_door 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
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
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
text "To continue the tutorial go through that door over there, and speak to your first instructor."
continue :close => true do |player|
if (player.tutorial_island_progress < :runescape_guide_finished)
reset_hint_icon(player)
# TODO door hint icon
player.tutorial_island_progress = :runescape_guide_finished
end
TutorialInstructions.show_instruction(player)
end
end
end
# Enables the hint icon above the Runescape guide.
def show_hint_icon(player)
player.send(HintIconMessage.for_npc(@runescape_guide.index))
end
# Resets the hint icon.
def reset_hint_icon(player)
player.send(HintIconMessage.reset_npc())
end
@@ -0,0 +1,121 @@
# TODO update the status to :moving_around when the door is opened
# 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
## TODO !! When we have door support, the player's tutorial island progress needs to be set to :moving_around when they walk through the door !!
# 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 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..."
text "Your character is now attempting to light the logs. Sit back for a moment whilst he does all the hard work." # TODO she instead of he if applicable
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 "" # TODO !!
end
end
end
@@ -6,7 +6,7 @@
# :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 [top-left x-coordinate, top-left y-coordinate, bottom-right x-coordinate, bottom-right y-coordinate]
# :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.
@@ -16,8 +16,6 @@
# 'Above-ground' npcs
spawn_npc :name => :runescape_guide, :x => 3093, :y => 3107
spawn_npc :name => :survival_expert, :x => 3104, :y => 3095, :face => :north
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
@@ -3,14 +3,21 @@
<id>location-tutorial-island</id>
<version>0.1</version>
<name>Tutorial Island</name>
<description>Adds funtionality to Tutorial island.</description>
<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>entity-spawning</dependency>
<dependency>quest</dependency>
</dependencies>
</plugin>
@@ -0,0 +1,15 @@
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 ]
STAGES.concat(RUNESCAPE_GUIDE)
# The stages that are used when interacting with the Survival Expert.
SURVIVAL_EXPERT = [ :given_axe, :cut_tree, :cutting_tree, ]
STAGES.concat(SURVIVAL_EXPERT)
quest :tutorial_island, STAGES
@@ -0,0 +1,108 @@
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
# 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
# TODO prevent axe equipping
# The conversation with the Survival Expert, when on tutorial island.
conversation :tutorial_surivival_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."
continue :dialogue => :items
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."
continue :close => true, &:add_survival_items
end
# The dialogue displayed when the Survival Guide hands both a bronze axe and a tinderbox to the player.
dialogue :items do
type :message_with_item
item :bronze_axe, 200 # TODO the tinderbox is also displayed - find this dialogue id. Scale looks like the default http://i.imgur.com/i1abN5X.png
text "The Survival Guide gives you a tinderbox and a bronze axe!"
continue :close => true do |player|
if (player.tutorial_island_progress != :given_axe)
player.tutorial_island_progress = :given_axe
player.send(SwitchTabInterfaceMessage.new(INVENTORY_TAB_INDEX, INVENTORY_TAB_ID))
player.send(FlashTabInterfaceMessage.new(INVENTORY_TAB_INDEX))
end
add_survival_items(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."
continue :close => true do |player|
TutorialInstructions.show_instruction(player)
end
end
end
# The id of the bronze axe.
BRONZE_AXE = lookup_item(:bronze_axe)
# The id of the tinderbox.
TINDERBOX = lookup_item(:tinderbox)
# 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
[ BRONZE_AXE, TINDERBOX ].each { |item| inventory.add(item) unless inventory.contains(item) }
TutorialInstructions.show_instruction(player)
end
on :message, :first_object_action do |ctx, player, message|
# TODO display "You cannot cut down this tree; you must first follow the guide's instructions." if the player has yet to get the axe
if (player.in_tutorial_island && player.tutorial_island_progress == :cut_tree && message.id == TREE_ID)
player.tutorial_island_progress = :cutting_tree # Don't break the chain, so that the Woodcutting event actually happens.
end
end
# Intercept the FlashingTabClickedMessage to update the player's progress, if applicable.
on :message, :flashing_tab_clicked do |ctx, player, message|
if (player.in_tutorial_island && message.tab == INVENTORY_TAB_INDEX)
player.tutorial_island_progress = :cut_tree
ctx.break_handler_chain()
end
end
@@ -0,0 +1,34 @@
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, y = self.position.x, self.position.y
return 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)
return (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)
return (x >= 3072 && x <= 3118 && y >= 9492 && y <= 9535)
end
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<plugin>
<id>quest</id>
<version>0.9</version>
<name>Quest</name>
<description>Adds </description>
<authors>
<author>Major</author>
</authors>
<scripts>
<script>repository.rb</script>
</scripts>
<dependencies>
<dependency>attributes</dependency>
</dependencies>
</plugin>
+133
View File
@@ -0,0 +1,133 @@
# 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)
raise "Quest name must be a symbol, received '#{name}'." unless name.kind_of?(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]
raise "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
raise "Cannot get the log text from an unlogged quest stage." unless logged
@log_text
end
# Defines the equality operator.
def ==(name)
@index == index_of(name)
end
# Defines the not equal operator.
def !=(name)
@index != index_of(name)
end
# Defines the greater than or equal to operator.
def >=(name)
@index >= index_of(name)
end
# Defines the greater than operator.
def >(name)
@index > index_of(name)
end
# Defines the less than operator.
def <(name)
@index < index_of(name)
end
# Defines the less than or equal to operator.
def <=(name)
@index <= index_of(name)
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.kind_of?(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]
raise "No Quest with the name '#{name}' exists." if quest.nil?
result = quest.stage(result)
end
result
end
end
+3 -3
View File
@@ -25,12 +25,12 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.8.1</version>
<version>1.9</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
<version>1.7.15</version>
<version>1.7.19</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -40,7 +40,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
<version>4.0.25.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
@@ -27,11 +27,6 @@ public final class FileSystemConstants {
*/
public static final int BLOCK_SIZE = HEADER_SIZE + CHUNK_SIZE;
/**
* The number of caches.
*/
public static final int CACHE_COUNT = 5;
/**
* The size of an index.
*/
@@ -122,13 +122,13 @@ public final class NpcDefinitionDecoder {
buffer.getShort();
} else if (opcode == 106) {
@SuppressWarnings("unused")
int morphVariableBitsIndex = wrapMorphism(buffer.getShort());
int morphVariableBitsIndex = wrap(buffer.getShort());
@SuppressWarnings("unused")
int morphismCount = wrapMorphism(buffer.getShort());
int morphismCount = wrap(buffer.getShort());
int count = buffer.get() & 0xFF;
int[] morphisms = new int[count + 1];
Arrays.setAll(morphisms, index -> wrapMorphism(buffer.getShort()));
Arrays.setAll(morphisms, index -> wrap(buffer.getShort()));
} else if (opcode == 107) {
@SuppressWarnings("unused")
boolean clickable = false;
@@ -137,12 +137,12 @@ public final class NpcDefinitionDecoder {
}
/**
* Wraps a morphism value around, returning -1 if the specified value is 65,535. TODO name
* Wraps a morphism value around, returning -1 if the specified value is 65,535.
*
* @param value The value.
* @return -1 if {@code value} is 65,535, otherwise {@code value}.
*/
private static int wrapMorphism(int value) {
private static int wrap(int value) {
return value == 65_535 ? -1 : value;
}
+1 -2
View File
@@ -1,5 +1,4 @@
/**
* Contains classes which deal with the file system that the client uses to
* store game data files.
* Contains classes which deal with the file system that the client uses to store game data files.
*/
package org.apollo.fs;
+2 -4
View File
@@ -49,8 +49,7 @@ public final class GameService extends Service {
/**
* The scheduled executor service.
*/
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory(
"GameService"));
private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("GameService"));
/**
* The {@link ClientSynchronizer}.
@@ -170,8 +169,7 @@ public final class GameService extends Service {
*/
@Override
public void start() {
scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, GameConstants.PULSE_DELAY,
TimeUnit.MILLISECONDS);
scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, GameConstants.PULSE_DELAY, TimeUnit.MILLISECONDS);
}
/**
+1 -2
View File
@@ -1,5 +1,4 @@
/**
* Contains classes related to actions, specialised scheduled tasks which
* mobs perform.
* Contains classes related to actions, specialised scheduled tasks which mobs perform.
*/
package org.apollo.game.action;
@@ -41,8 +41,8 @@ public final class MessageHandlerChain<M extends Message> {
}
/**
* Handles the message, passing it down the chain until the chain is broken
* or the message reaches the end of the chain.
* Handles the message, passing it down the chain until the chain is broken or the message reaches the end of the
* chain.
*
* @param player The player.
* @param message The message.
@@ -1,8 +1,7 @@
package org.apollo.game.message.handler;
/**
* Provides operations specific to a {@link MessageHandler} in a
* {@link MessageHandlerChain}.
* Provides operations specific to a {@link MessageHandler} in a {@link MessageHandlerChain}.
*
* @author Graham
* @author Ryley
@@ -24,8 +23,7 @@ public final class MessageHandlerContext {
/**
* Returns whether or not this handler chain is broken.
*
* @return {@code true} if this handler chain is broken, otherwise
* {@code false}.
* @return {@code true} if this handler chain is broken, otherwise {@code false}.
*/
public boolean isBroken() {
return broken;
@@ -25,16 +25,16 @@ public final class BankMessageHandler extends MessageHandler<ItemActionMessage>
*/
private static final int optionToAmount(int option) {
switch (option) {
case 1:
return 1;
case 2:
return 5;
case 3:
return 10;
case 4:
return Integer.MAX_VALUE;
case 5:
return -1;
case 1:
return 1;
case 2:
return 5;
case 3:
return 10;
case 4:
return Integer.MAX_VALUE;
case 5:
return -1;
}
throw new IllegalArgumentException("Invalid option supplied.");
@@ -21,19 +21,19 @@ public final class ItemOnItemVerificationHandler extends MessageHandler<ItemOnIt
Inventory inventory;
switch (message.getInterfaceId()) {
case SynchronizationInventoryListener.INVENTORY_ID:
case BankConstants.SIDEBAR_INVENTORY_ID:
inventory = player.getInventory();
break;
case SynchronizationInventoryListener.EQUIPMENT_ID:
inventory = player.getEquipment();
break;
case BankConstants.BANK_INVENTORY_ID:
inventory = player.getBank();
break;
default:
ctx.breakHandlerChain();
return;
case SynchronizationInventoryListener.INVENTORY_ID:
case BankConstants.SIDEBAR_INVENTORY_ID:
inventory = player.getInventory();
break;
case SynchronizationInventoryListener.EQUIPMENT_ID:
inventory = player.getEquipment();
break;
case BankConstants.BANK_INVENTORY_ID:
inventory = player.getBank();
break;
default:
ctx.breakHandlerChain();
return;
}
int slot = message.getTargetSlot();
@@ -18,8 +18,7 @@ public final class ItemOnObjectVerificationHandler extends MessageHandler<ItemOn
@Override
public void handle(MessageHandlerContext ctx, Player player, ItemOnObjectMessage message) {
if (message.getInterfaceId() != SynchronizationInventoryListener.INVENTORY_ID
&& message.getInterfaceId() != BankConstants.SIDEBAR_INVENTORY_ID) {
if (message.getInterfaceId() != SynchronizationInventoryListener.INVENTORY_ID && message.getInterfaceId() != BankConstants.SIDEBAR_INVENTORY_ID) {
ctx.breakHandlerChain();
return;
}
@@ -22,23 +22,22 @@ public final class SwitchItemMessageHandler extends MessageHandler<SwitchItemMes
boolean insertPermitted = false;
switch (message.getInterfaceId()) {
case SynchronizationInventoryListener.INVENTORY_ID:
case BankConstants.SIDEBAR_INVENTORY_ID:
inventory = player.getInventory();
break;
case SynchronizationInventoryListener.EQUIPMENT_ID:
inventory = player.getEquipment();
break;
case BankConstants.BANK_INVENTORY_ID:
inventory = player.getBank();
insertPermitted = true;
break;
default:
return; // not a known inventory, ignore
case SynchronizationInventoryListener.INVENTORY_ID:
case BankConstants.SIDEBAR_INVENTORY_ID:
inventory = player.getInventory();
break;
case SynchronizationInventoryListener.EQUIPMENT_ID:
inventory = player.getEquipment();
break;
case BankConstants.BANK_INVENTORY_ID:
inventory = player.getBank();
insertPermitted = true;
break;
default:
return; // not a known inventory, ignore
}
if (message.getOldSlot() >= 0 && message.getNewSlot() >= 0 && message.getOldSlot() < inventory.capacity()
&& message.getNewSlot() < inventory.capacity()) {
if (message.getOldSlot() >= 0 && message.getNewSlot() >= 0 && message.getOldSlot() < inventory.capacity() && message.getNewSlot() < inventory.capacity()) {
// events must be fired for it to work if a sidebar inventory overlay is used
inventory.swap(insertPermitted && message.isInserting(), message.getOldSlot(), message.getNewSlot());
}
@@ -36,6 +36,11 @@ public final class WalkMessageHandler extends MessageHandler<WalkMessage> {
player.stopAction();
}
player.getInterfaceSet().close();
if (player.getInteractingMob() != null) {
player.resetInteractingMob();
}
}
}
@@ -53,8 +53,7 @@ public final class PlayerSynchronizationMessage extends Message {
* @param localPlayers The number of local players.
* @param segments A list of segments.
*/
public PlayerSynchronizationMessage(Position lastKnownSector, Position position, boolean sectorChanged,
SynchronizationSegment segment, int localPlayers, List<SynchronizationSegment> segments) {
public PlayerSynchronizationMessage(Position lastKnownSector, Position position, boolean sectorChanged, SynchronizationSegment segment, int localPlayers, List<SynchronizationSegment> segments) {
this.lastKnownSector = lastKnownSector;
this.position = position;
this.sectorChanged = sectorChanged;
+2 -2
View File
@@ -14,8 +14,8 @@ public final class Appearance {
/**
* The default appearance.
*/
public static final Appearance DEFAULT_APPEARANCE = new Appearance(Gender.MALE, new int[] { 0, 10, 18, 26, 33, 36, 42 },
new int[5]);
public static final Appearance DEFAULT_APPEARANCE = new Appearance(Gender.MALE, new int[] { 0, 10, 18, 26, 33, 36,
42 }, new int[5]);
/**
* The array of clothing/skin colors.
+1 -5
View File
@@ -1,9 +1,5 @@
package org.apollo.game.model;
/**
* Represents a single movement direction.
*
@@ -90,7 +86,7 @@ public enum Direction {
return Direction.WEST;
}
}
return Direction.NONE;
}
+2 -3
View File
@@ -221,13 +221,12 @@ public final class Position {
public boolean isWithinDistance(Position other, int distance) {
int deltaX = Math.abs(getX() - other.getX());
int deltaY = Math.abs(getY() - other.getY());
return deltaX <= distance && deltaY <= distance;
return deltaX <= distance && deltaY <= distance && getHeight() == other.getHeight();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("x", getX()).add("y", getY()).add("height", getHeight())
.add("sector", getSectorCoordinates()).toString();
return MoreObjects.toStringHelper(this).add("x", getX()).add("y", getY()).add("height", getHeight()).add("sector", getSectorCoordinates()).toString();
}
}
+16 -3
View File
@@ -30,6 +30,7 @@ import org.apollo.game.model.event.EventListener;
import org.apollo.game.model.event.EventListenerChainSet;
import org.apollo.game.scheduling.ScheduledTask;
import org.apollo.game.scheduling.Scheduler;
import org.apollo.game.scheduling.impl.NpcMovementTask;
import org.apollo.io.EquipmentDefinitionParser;
import org.apollo.util.MobRepository;
import org.apollo.util.NameUtil;
@@ -97,6 +98,11 @@ public final class World {
*/
private final EventListenerChainSet events = new EventListenerChainSet();
/**
* The ScheduledTask that moves Npcs.
*/
private NpcMovementTask npcMovement;
/**
* The {@link MobRepository} of {@link Npc}s.
*/
@@ -113,7 +119,7 @@ public final class World {
private final Map<Long, Player> players = new HashMap<>();
/**
* The {@link PluginManager}. TODO: better place than here!!
* The {@link PluginManager}.
*/
private PluginManager pluginManager;
@@ -178,7 +184,7 @@ public final class World {
}
/**
* Gets the plugin manager. TODO should this be here?
* Gets the plugin manager.
*
* @return The plugin manager.
*/
@@ -242,8 +248,11 @@ public final class World {
placeEntities(objects);
logger.fine("Loaded " + objects.length + " static objects.");
npcMovement = new NpcMovementTask(); // Must be exactly here because of ordering issues.
scheduler.schedule(npcMovement);
manager.start();
pluginManager = manager; // TODO move!!
pluginManager = manager;
}
/**
@@ -285,6 +294,10 @@ public final class World {
if (success) {
Sector sector = sectors.fromPosition(npc.getPosition());
sector.addEntity(npc);
if (npc.hasBoundaries()) {
npcMovement.addNpc(npc);
}
} else {
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
}
+15 -7
View File
@@ -14,6 +14,7 @@ import org.apollo.game.model.area.collision.CollisionMatrix;
import org.apollo.game.model.entity.Entity;
import org.apollo.game.model.entity.Entity.EntityType;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
@@ -174,13 +175,14 @@ public final class Sector {
Set<Entity> local = entities.get(old);
Preconditions.checkArgument(local != null && local.remove(entity), "Entity belongs in this sector but does not exist.");
if (local == null || !local.remove(entity)) {
throw new IllegalArgumentException("Entity belongs in this sector (" + this + ") but does not exist.");
}
local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
local.add(entity);
notifyListeners(entity, SectorOperation.MOVE);
}
/**
@@ -205,7 +207,9 @@ public final class Sector {
Set<Entity> local = entities.get(position);
Preconditions.checkArgument(local != null && local.remove(entity), "Entity belongs in this sector but does not exist.");
if (local == null || !local.remove(entity)) {
throw new IllegalArgumentException("Entity belongs in this sector (" + this + ") but does not exist.");
}
notifyListeners(entity, SectorOperation.REMOVE);
}
@@ -221,9 +225,14 @@ public final class Sector {
*/
public boolean traversable(Position position, EntityType entity, Direction direction) {
CollisionMatrix matrix = matrices[position.getHeight()];
int x = position.getLocalX(), y = position.getLocalY();
int x = position.getX(), y = position.getY();
return matrix.traversable(x, y, entity, direction);
return !matrix.untraversable(x % SECTOR_SIZE, y % SECTOR_SIZE, entity, direction);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString();
}
/**
@@ -233,8 +242,7 @@ public final class Sector {
* @throws IllegalArgumentException If the specified position is not included in this sector.
*/
private void checkPosition(Position position) {
Preconditions.checkArgument(coordinates.equals(SectorCoordinates.fromPosition(position)),
"Position is not included in this sector.");
Preconditions.checkArgument(coordinates.equals(SectorCoordinates.fromPosition(position)), "Position is not included in this sector.");
}
}
@@ -9,16 +9,16 @@ import org.apollo.game.model.entity.Entity.EntityType;
*/
public enum CollisionFlag {
/**
* The walk east flag.
*/
MOB_EAST(1),
/**
* The walk north flag.
*/
MOB_NORTH(0),
/**
* The walk east flag.
*/
MOB_EAST(1),
/**
* The walk south flag.
*/
@@ -29,16 +29,16 @@ public enum CollisionFlag {
*/
MOB_WEST(3),
/**
* The projectile east flag.
*/
PROJECTILE_EAST(5),
/**
* The projectile north flag.
*/
PROJECTILE_NORTH(4),
/**
* The projectile east flag.
*/
PROJECTILE_EAST(5),
/**
* The projectile south flag.
*/
@@ -15,6 +15,16 @@ import com.google.common.base.Preconditions;
*/
public final class CollisionMatrix {
/**
* Indicates that all types of traversal are allowed.
*/
private static final byte ALL_ALLOWED = 0b0000_0000;
/**
* Indicates that no types of traversal are allowed.
*/
private static final byte ALL_BLOCKED = (byte) 0b1111_1111;
/**
* Creates an array of CollisionMatrix objects, all of the specified width and length.
*
@@ -29,16 +39,6 @@ public final class CollisionMatrix {
return matrices;
}
/**
* Indicates that all types of traversal are allowed.
*/
private static final byte ALL_ALLOWED = 0b0000_0000;
/**
* Indicates that no types of traversal are allowed.
*/
private static final byte ALL_BLOCKED = (byte) 0b1111_1111;
/**
* The length of the matrix.
*/
@@ -76,13 +76,7 @@ public final class CollisionMatrix {
* @return {@code true} if all of the CollisionFlags are set, otherwise {@code false}.
*/
public boolean all(int x, int y, CollisionFlag... flags) {
for (CollisionFlag flag : flags) {
if ((get(x, y) & flag.asByte()) == 0) {
return false;
}
}
return true;
return Arrays.stream(flags).allMatch(flag -> (get(x, y) & flag.asByte()) != 0);
}
/**
@@ -95,13 +89,7 @@ public final class CollisionMatrix {
* @return {@code true} if any of the CollisionFlags are set, otherwise {@code false}.
*/
public boolean any(int x, int y, CollisionFlag... flags) {
for (CollisionFlag flag : flags) {
if ((get(x, y) & flag.asByte()) != 0) {
return true;
}
}
return false;
return Arrays.stream(flags).anyMatch(flag -> (get(x, y) & flag.asByte()) != 0);
}
/**
@@ -173,21 +161,20 @@ public final class CollisionMatrix {
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("width", width).add("length", length).add("matrix", Arrays.toString(matrix))
.toString();
return MoreObjects.toStringHelper(this).add("width", width).add("length", length).add("matrix", Arrays.toString(matrix)).toString();
}
/**
* Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified
* coordinate pair.
* Returns whether or not an Entity of the specified {@link EntityType type} cannot traverse the tile at the
* specified coordinate pair.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param entity The {@link EntityType}.
* @param direction The {@link Direction} the Entity is approaching from.
* @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not.
* @return {@code true} if the tile at the specified coordinate pair is not traversable, {@code false} if not.
*/
public boolean traversable(int x, int y, EntityType entity, Direction direction) {
public boolean untraversable(int x, int y, EntityType entity, Direction direction) {
CollisionFlag[] flags = CollisionFlag.forType(entity);
int north = 0, east = 1, south = 2, west = 3;
@@ -1,5 +1,5 @@
/**
* Contains classes which represent in-game sectors - blocks of 8x8 tiles used to store ground items, temporary objects, etc.
* efficiently.
* Contains classes which represent in-game sectors - blocks of 8x8 tiles used to store ground items, temporary objects,
* etc. efficiently.
*/
package org.apollo.game.model.area;
@@ -388,7 +388,7 @@ public final class ItemDefinition {
public void toNote() {
if (isNote()) {
if (description != null && description.startsWith("Swap this note at any bank for ")) {
return; // already converted TODO better way of checking?
return; // already converted.
}
ItemDefinition infoDef = lookup(noteInfoId);
@@ -178,8 +178,7 @@ public final class ObjectDefinition {
/**
* Indicates the impenetrability of this object.
*
* @return {@code true} if this object is impenetrable, otherwise
* {@code false}.
* @return {@code true} if this object is impenetrable, otherwise {@code false}.
*/
public boolean isImpenetrable() {
return impenetrable;
@@ -188,8 +187,7 @@ public final class ObjectDefinition {
/**
* Indicates the interactivity of this object.
*
* @return {@code true} if the object is interactive, otherwise
* {@code false}.
* @return {@code true} if the object is interactive, otherwise {@code false}.
*/
public boolean isInteractive() {
return interactive;
@@ -198,8 +196,7 @@ public final class ObjectDefinition {
/**
* Indicates whether or not this object obstructs the ground.
*
* @return {@code true} if the object obstructs the ground otherwise
* {@code false}.
* @return {@code true} if the object obstructs the ground otherwise {@code false}.
*/
public boolean isObstructive() {
return obstructive;
@@ -1,5 +1,4 @@
/**
* Contains definition classes which contain information about types of items,
* NPCs, etc.
* Contains definition classes which contain information about types of items, NPCs, etc.
*/
package org.apollo.game.model.def;
@@ -89,8 +89,7 @@ public final class GameObject extends Entity {
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("id", getId()).add("type", getType()).add("rotation", getRotation())
.toString();
return MoreObjects.toStringHelper(this).add("id", getId()).add("type", getType()).add("rotation", getRotation()).toString();
}
}
+2 -2
View File
@@ -194,8 +194,8 @@ public abstract class Mob extends Entity {
*/
public final Direction[] getDirections() {
if (firstDirection != Direction.NONE) {
return secondDirection == Direction.NONE ? new Direction[] { firstDirection } : new Direction[] { firstDirection,
secondDirection };
return secondDirection == Direction.NONE ? new Direction[] { firstDirection } : new Direction[] {
firstDirection, secondDirection };
}
return Direction.EMPTY_DIRECTION_ARRAY;
+28 -28
View File
@@ -1,6 +1,5 @@
package org.apollo.game.model.entity;
import java.util.Arrays;
import java.util.Optional;
import org.apollo.game.model.Position;
@@ -20,9 +19,9 @@ import com.google.common.base.Preconditions;
public final class Npc extends Mob {
/**
* The positions representing the bounds (i.e. walking limits) of this Npc.
* The Positions representing the boundaries (i.e. walking limits) of this Npc.
*/
private Position[] boundary;
private Optional<Position[]> boundaries;
/**
* Creates a new Npc with the specified id and {@link Position}.
@@ -31,18 +30,20 @@ public final class Npc extends Mob {
* @param position The position.
*/
public Npc(int id, Position position) {
this(position, NpcDefinition.lookup(id));
this(position, NpcDefinition.lookup(id), null);
}
/**
* Creates a new Npc with the specified {@link NpcDefinition} and {@link Position}.
*
* @param position The position.
* @param definition The definition.
* @param position The Position.
* @param definition The NpcDefinition.
* @param boundaries The boundary Positions.
*/
public Npc(Position position, NpcDefinition definition) {
public Npc(Position position, NpcDefinition definition, Position[] boundaries) {
super(position, definition);
this.boundaries = Optional.ofNullable(boundaries);
init();
}
@@ -50,19 +51,19 @@ public final class Npc extends Mob {
public boolean equals(Object obj) {
if (obj instanceof Npc) {
Npc other = (Npc) obj;
return position.equals(other.position) && Arrays.equals(boundary, other.boundary) && getId() == other.getId();
return index == other.index && getId() == other.getId();
}
return false;
}
/**
* Gets the boundary of this Npc.
* Gets the boundaries of this Npc.
*
* @return The boundary.
* @return The boundaries.
*/
public Position[] getBoundary() {
return boundary.clone();
public Optional<Position[]> getBoundaries() {
return boundaries.isPresent() ? Optional.of(boundaries.get().clone()) : Optional.empty();
}
@Override
@@ -79,30 +80,29 @@ public final class Npc extends Mob {
return definition.get().getId();
}
/**
* Returns whether or not this Npc has boundaries.
*
* @return {@code true} if this Npc has boundaries, {@code false} if not.
*/
public boolean hasBoundaries() {
return boundaries.isPresent();
}
@Override
public int hashCode() {
final int prime = 31;
int result = prime * position.hashCode() + Arrays.hashCode(boundary);
return prime * result + getId();
return prime * index + getId();
}
/**
* Indicates whether or not this Npc is bound to a specific set of coordinates.
* Sets the boundaries of this Npc.
*
* @return {@code true} if the Npc is bound, otherwise {@code false}.
* @param boundaries The boundaries.
*/
public boolean isBound() {
return boundary == null;
}
/**
* Sets the boundary of this Npc.
*
* @param boundary The boundary.
*/
public void setBoundary(Position[] boundary) {
Preconditions.checkArgument(boundary.length == 4, "Boundary count must be 4.");
this.boundary = boundary.clone();
public void setBoundaries(Position[] boundaries) {
Preconditions.checkArgument(boundaries.length == 2, "Boundary count must be 2.");
this.boundaries = Optional.of(boundaries.clone());
}
@Override
+4 -7
View File
@@ -686,7 +686,7 @@ public final class Player extends Mob {
*/
public void sendInitialMessages() {
blockSet.add(SynchronizationBlock.createAppearanceBlock(this));
send(new IdAssignmentMessage(index, members)); // TODO should this be sent when we reconnect?
send(new IdAssignmentMessage(index, members));
sendMessage("Welcome to RuneScape.");
int[] tabs = InterfaceConstants.DEFAULT_INVENTORY_TABS;
@@ -943,8 +943,7 @@ public final class Player extends Mob {
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel)
.add("client version", getClientVersion()).toString();
return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel).add("client version", getClientVersion()).toString();
}
/**
@@ -968,11 +967,9 @@ public final class Player extends Mob {
InventoryListener fullBankListener = new FullInventoryListener(this, FullInventoryListener.FULL_BANK_MESSAGE);
InventoryListener appearanceListener = new AppearanceInventoryListener(this);
InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this,
SynchronizationInventoryListener.INVENTORY_ID);
InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.INVENTORY_ID);
InventoryListener syncBankListener = new SynchronizationInventoryListener(this, BankConstants.BANK_INVENTORY_ID);
InventoryListener syncEquipmentListener = new SynchronizationInventoryListener(this,
SynchronizationInventoryListener.EQUIPMENT_ID);
InventoryListener syncEquipmentListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.EQUIPMENT_ID);
inventory.addListener(syncInventoryListener);
inventory.addListener(fullInventoryListener);
+3 -3
View File
@@ -115,9 +115,9 @@ public final class Skill {
/**
* The skill names.
*/
private static final String[] SKILL_NAMES = { "Attack", "Defence", "Strength", "Hitpoints", "Ranged", "Prayer", "Magic",
"Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", "Crafting", "Smithing", "Mining", "Herblore",
"Agility", "Thieving", "Slayer", "Farming", "Runecraft" };
private static final String[] SKILL_NAMES = { "Attack", "Defence", "Strength", "Hitpoints", "Ranged", "Prayer",
"Magic", "Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", "Crafting", "Smithing", "Mining",
"Herblore", "Agility", "Thieving", "Slayer", "Farming", "Runecraft" };
/**
* Gets the name of a skill.
@@ -57,8 +57,7 @@ public final class SkillSet {
* @return The minimum level.
*/
public static int getLevelForExperience(double experience) {
Preconditions.checkArgument(experience >= 0 && experience <= MAXIMUM_EXP, "Experience must be between 0 and "
+ MAXIMUM_EXP + ", inclusive.");
Preconditions.checkArgument(experience >= 0 && experience <= MAXIMUM_EXP, "Experience must be between 0 and " + MAXIMUM_EXP + ", inclusive.");
for (int level = 1; level <= 98; level++) {
if (experience < EXPERIENCE_FOR_LEVEL[level + 1]) {
@@ -80,8 +80,7 @@ public final class AttributeMap {
AttributeDefinition<T> definition = getDefinition(name);
Preconditions.checkNotNull(definition, "Attributes must be defined before their value can be retreived.");
return (Attribute<T>) attributes.computeIfAbsent(name,
key -> createAttribute(definition.getDefault(), definition.getType()));
return (Attribute<T>) attributes.computeIfAbsent(name, key -> createAttribute(definition.getDefault(), definition.getType()));
}
/**
@@ -23,7 +23,7 @@ import org.apollo.game.model.Position;
*
* @author Major
*/
final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
public final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
/**
* The heuristic.
@@ -1,7 +1,7 @@
package org.apollo.game.model.entity.path;
import java.util.Deque;
import java.util.Set;
import java.util.Optional;
import org.apollo.game.model.Direction;
import org.apollo.game.model.Position;
@@ -9,7 +9,8 @@ import org.apollo.game.model.World;
import org.apollo.game.model.area.Sector;
import org.apollo.game.model.area.SectorRepository;
import org.apollo.game.model.entity.Entity.EntityType;
import org.apollo.game.model.entity.GameObject;
import com.google.common.base.Preconditions;
/**
* An algorithm used to find a path between two {@link Position}s.
@@ -19,46 +20,48 @@ import org.apollo.game.model.entity.GameObject;
abstract class PathfindingAlgorithm {
/**
* The repository of sectors.
* The repository of Sectors.
*/
private static final SectorRepository repository = World.getWorld().getSectorRepository();
private static final SectorRepository REPOSITORY = World.getWorld().getSectorRepository();
/**
* Finds a valid path from the origin {@link Position} to the target one.
*
* @param origin The origin position.
* @param target The target position.
* @return The {@link Deque} containing the positions to go through.
* @param origin The origin Position.
* @param target The target Position.
* @return The {@link Deque} containing the Positions to go through.
*/
public abstract Deque<Position> find(Position origin, Position target);
/**
* Returns whether or not the tile at the specified position is walkable. FIXME do this properly w/tile collision
* data!
*
* @param position The {@link Position}.
* @return {@code true} if the tile is walkable, otherwise {@code false}.
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
* to is traversable.
*
* @param current The current Position.
* @param directions The Directions that should be checked.
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
*/
protected boolean traversable(Position position) {
Sector sector = repository.get(position.getSectorCoordinates());
Set<GameObject> objects = sector.getEntities(position, EntityType.GAME_OBJECT);
return objects.stream().anyMatch(object -> object.getDefinition().isSolid());
protected boolean traversable(Position current, Direction... directions) {
return traversable(current, Optional.empty(), directions);
}
/**
* Returns whether or not the {@link Position}s walking one step in a specified {@link Direction} would lead to is
* traversable.
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
* to is traversable.
*
* @param position The starting position.
* @param directions The directions that should be checked.
* @return {@code true} if any of the directions lead to a traversable tile, otherwise {@code false}.
* @param current The current Position.
* @param boundaries The {@link Optional} containing the Position boundaries.
* @param directions The Directions that should be checked.
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
*/
protected boolean traversable(Position position, Direction... directions) {
int height = position.getHeight();
protected boolean traversable(Position current, Optional<Position[]> boundaries, Direction... directions) {
Preconditions.checkArgument(directions != null && directions.length > 0, "Directions array cannot be null.");
int height = current.getHeight();
Position[] positions = boundaries.isPresent() ? boundaries.get() : new Position[0];
for (Direction direction : directions) {
int x = position.getX(), y = position.getY();
int x = current.getX(), y = current.getY();
int value = direction.toInteger();
if (value >= Direction.NORTH_WEST.toInteger() && value <= Direction.NORTH_EAST.toInteger()) {
@@ -73,7 +76,9 @@ abstract class PathfindingAlgorithm {
x--;
}
if (traversable(new Position(x, y, height))) {
Position next = new Position(x, y, height);
Sector sector = REPOSITORY.get(next.getSectorCoordinates());
if (sector.traversable(next, EntityType.NPC, direction) && (positions.length == 0 || inside(next, positions))) {
return true;
}
}
@@ -81,4 +86,18 @@ abstract class PathfindingAlgorithm {
return false;
}
/**
* Returns whether or not the specified {@link Position} is inside the specified {@code boundary}.
*
* @param position The Position.
* @param boundary The boundary Positions.
* @return {@code true} if the specified Position is inside the boundary, {@code false} if not.
*/
private boolean inside(Position position, Position[] boundary) {
int x = position.getX(), y = position.getY();
Position min = boundary[0], max = boundary[1];
return x >= min.getX() && y >= min.getY() && x <= max.getX() && y <= max.getY();
}
}
@@ -2,6 +2,7 @@ package org.apollo.game.model.entity.path;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Optional;
import org.apollo.game.model.Direction;
import org.apollo.game.model.Position;
@@ -12,7 +13,12 @@ import org.apollo.game.model.Position;
*
* @author Major
*/
final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
/**
* The Optional containing the boundary Positions.
*/
private Optional<Position[]> boundaries = Optional.empty();
@Override
public Deque<Position> find(Position origin, Position target) {
@@ -22,6 +28,19 @@ final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
return addHorizontal(origin, target, positions);
}
/**
* Finds a valid path from the origin {@link Position} to the target one.
*
* @param origin The origin Position.
* @param target The target Position.
* @param boundaries The boundary Positions, which are marking as untraversable.
* @return The {@link Deque} containing the Positions to go through.
*/
public Deque<Position> find(Position origin, Position target, Position[] boundaries) {
this.boundaries = Optional.of(boundaries);
return find(origin, target);
}
/**
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
* <p>
@@ -33,33 +52,33 @@ final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
* </ul>
*
* @param current The current position.
* @param start The current position.
* @param target The target position.
* @param positions The deque of positions.
* @return The deque of positions containing the path.
*/
private Deque<Position> addHorizontal(Position current, Position target, Deque<Position> positions) {
int x = current.getX(), y = current.getY(), height = current.getHeight();
int dx = x - target.getX();
private Deque<Position> addHorizontal(Position start, Position target, Deque<Position> positions) {
int x = start.getX(), y = start.getY(), height = start.getHeight();
int dx = x - target.getX(), dy = y - target.getY();
if (dx > 0) {
Position west = new Position(x - 1, y, height);
Position current = start;
while (traversable(west) && dx-- > 0) {
west = new Position(--x, y, height);
positions.addLast(west);
while (traversable(current, boundaries, Direction.WEST) && dx-- > 0) {
current = new Position(--x, y, height);
positions.addLast(current);
}
} else if (dx < 0) {
Position east = new Position(x + 1, y, height);
Position current = start;
while (traversable(east) && dx++ < 0) {
east = new Position(++x, y, height);
positions.addLast(east);
while (traversable(current, boundaries, Direction.EAST) && dx++ < 0) {
current = new Position(++x, y, height);
positions.addLast(current);
}
}
Position last = new Position(x, y, height);
if (!current.equals(last) && traversable(last, Direction.NORTH, Direction.SOUTH)) {
if (!start.equals(last) && dy != 0 && traversable(last, boundaries, (dy > 0) ? Direction.SOUTH : Direction.NORTH)) {
return addVertical(last, target, positions);
}
@@ -77,33 +96,33 @@ final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
* </ul>
*
* @param current The current position.
* @param start The current position.
* @param target The target position.
* @param positions The deque of positions.
* @return The deque of positions containing the path.
*/
private Deque<Position> addVertical(Position current, Position target, Deque<Position> positions) {
int x = current.getX(), y = current.getY(), height = current.getHeight();
int dy = y - target.getY();
private Deque<Position> addVertical(Position start, Position target, Deque<Position> positions) {
int x = start.getX(), y = start.getY(), height = start.getHeight();
int dy = y - target.getY(), dx = x - target.getX();
if (dy > 0) {
Position south = new Position(x, y - 1, height);
Position current = start;
while (traversable(south) && dy-- > 0) {
south = new Position(x, --y, height);
positions.addLast(south);
while (traversable(current, boundaries, Direction.SOUTH) && dy-- > 0) {
current = new Position(x, --y, height);
positions.addLast(current);
}
} else if (dy < 0) {
Position north = new Position(x, y + 1, height);
Position current = start;
while (traversable(north) && dy++ < 0) {
north = new Position(x, ++y, height);
positions.addLast(north);
while (traversable(current, boundaries, Direction.NORTH) && dy++ < 0) {
current = new Position(x, ++y, height);
positions.addLast(current);
}
}
Position last = new Position(x, y, height);
if (!last.equals(target) && traversable(last, Direction.EAST, Direction.WEST)) {
if (!last.equals(target) && dx != 0 && traversable(last, boundaries, (dx > 0) ? Direction.WEST : Direction.EAST)) {
return addHorizontal(last, target, positions);
}
+2 -2
View File
@@ -6,7 +6,7 @@ package org.apollo.game.model.event;
* @author Major
*/
public abstract class Event {
/**
* Indicates whether or not the Event chain has been terminated.
*/
@@ -27,5 +27,5 @@ public abstract class Event {
public final boolean terminated() {
return terminated;
}
}
@@ -10,8 +10,8 @@ public class InterfaceConstants {
/**
* The default inventory tab ids.
*/
public static final int[] DEFAULT_INVENTORY_TABS = { 2423, 3917, 638, 3213, 1644, 5608, 1151, -1, 5065, 5715, 2449, 904, 147,
962, };
public static final int[] DEFAULT_INVENTORY_TABS = { 2423, 3917, 638, 3213, 1644, 5608, 1151, -1, 5065, 5715, 2449,
904, 147, 962, };
// 6299 = music tab, music disabled
// 4445 = settings tab, music disabled
// 12855 = ancients magic
@@ -19,8 +19,8 @@ public class InterfaceConstants {
/**
* The level-up dialogue interface ids.
*/
public static final int[] LEVEL_UP_INTERFACES = { 6247, 6253, 6206, 6216, 4443, 6242, 6211, 6226, 4272, 6231, 6258, 4282,
6263, 6221, 4416, 6237, 4277, 4261, 12122, 4887, 4267 };
public static final int[] LEVEL_UP_INTERFACES = { 6247, 6253, 6206, 6216, 4443, 6242, 6211, 6226, 4272, 6231, 6258,
4282, 6263, 6221, 4416, 6237, 4277, 4261, 12122, 4887, 4267 };
/**
* The quest interface id.
@@ -35,12 +35,13 @@ public class InterfaceConstants {
/**
* The array of widgets that display the text.
*/
public static final int[] QUEST_TEXT = { 8144, 8145, 8147, 8148, 8149, 8150, 8151, 8152, 8153, 8154, 8155, 8156, 8157, 8158,
8159, 8160, 8161, 8162, 8163, 8164, 8165, 8166, 8167, 8168, 8169, 8170, 8171, 8172, 8173, 8174, 8175, 8176, 8177,
8178, 8179, 8180, 8181, 8182, 8183, 8184, 8185, 8186, 8187, 8188, 8189, 8190, 8191, 8192, 8193, 8194, 8195, 12174,
12175, 12176, 12177, 12178, 12179, 12180, 12181, 12182, 12183, 12184, 12185, 12186, 12187, 12188, 12189, 12190,
12191, 12192, 12193, 12194, 12195, 12196, 12197, 12198, 12199, 12200, 12201, 12202, 12203, 12204, 12205, 12206,
12207, 12208, 12209, 12210, 12211, 12212, 12213, 12214, 12215, 12216, 12217, 12218, 12219, 12220, 12221, 12222, 12223 };
public static final int[] QUEST_TEXT = { 8144, 8145, 8147, 8148, 8149, 8150, 8151, 8152, 8153, 8154, 8155, 8156,
8157, 8158, 8159, 8160, 8161, 8162, 8163, 8164, 8165, 8166, 8167, 8168, 8169, 8170, 8171, 8172, 8173, 8174,
8175, 8176, 8177, 8178, 8179, 8180, 8181, 8182, 8183, 8184, 8185, 8186, 8187, 8188, 8189, 8190, 8191, 8192,
8193, 8194, 8195, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12181, 12182, 12183, 12184, 12185,
12186, 12187, 12188, 12189, 12190, 12191, 12192, 12193, 12194, 12195, 12196, 12197, 12198, 12199, 12200,
12201, 12202, 12203, 12204, 12205, 12206, 12207, 12208, 12209, 12210, 12211, 12212, 12213, 12214, 12215,
12216, 12217, 12218, 12219, 12220, 12221, 12222, 12223 };
/**
* The quest title widget id.
+1 -1
View File
@@ -51,7 +51,7 @@ public final class Inventory {
/**
* A flag indicating if events are being fired.
*/
private boolean firingEvents = true; // TODO: make this reentrant
private boolean firingEvents = true;
/**
* The items in this inventory.
+1 -2
View File
@@ -1,5 +1,4 @@
/**
* Contains classes which represent things in the in-game world such as items,
* players and NPCs.
* Contains classes which represent things in the in-game world such as items, players and NPCs.
*/
package org.apollo.game.model;
@@ -31,8 +31,7 @@ public final class LevelUpSkillListener extends SkillAdapter {
// TODO show the interface
String name = Skill.getName(id);
String article = LanguageUtil.getIndefiniteArticle(name);
player.sendMessage("You've just advanced " + article + " " + name + " level! You have reached level "
+ skill.getMaximumLevel() + ".");
player.sendMessage("You've just advanced " + article + " " + name + " level! You have reached level " + skill.getMaximumLevel() + ".");
if (Skill.isCombatSkill(id)) {
player.getSkillSet().calculateCombatLevel();
@@ -0,0 +1,100 @@
package org.apollo.game.scheduling.impl;
import java.util.Comparator;
import java.util.Deque;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.Npc;
import org.apollo.game.model.entity.WalkingQueue;
import org.apollo.game.model.entity.path.SimplePathfindingAlgorithm;
import org.apollo.game.scheduling.ScheduledTask;
import com.google.common.base.Preconditions;
/**
* A {@link ScheduledTask} that causes {@link Npc}s to randomly walk around in their boundary.
*
* @author Major
*/
public final class NpcMovementTask extends ScheduledTask {
/**
* The delay between executions of this task, in pulses.
*/
private static final int DELAY = 5;
/**
* The random number generator used to calculate how many Npcs should be moved per execution.
*/
private static final Random RANDOM = new Random();
/**
* The comparator used to sort the Npcs in the PriorityQueue.
*/
private static final Comparator<Npc> RANDOM_COMPARATOR = (first, second) -> RANDOM.nextInt(2) - 1;
/**
* The PathfindingAlgorithm used by this Task.
*/
private final SimplePathfindingAlgorithm algorithm = new SimplePathfindingAlgorithm();
/**
* The Queue of Npcs.
*/
private final Queue<Npc> npcs = new PriorityQueue<>(RANDOM_COMPARATOR);
/**
* Creates the NpcMovementTask.
*/
public NpcMovementTask() {
super(DELAY, false);
}
/**
* Adds the {@link Npc} to this {@link ScheduledTask}.
*
* @param npc The Npc to add.
*/
public void addNpc(Npc npc) {
Preconditions.checkArgument(npc.hasBoundaries(), "Cannot add an npc with no boundaries to the NpcMovementTask.");
npcs.offer(npc);
}
@Override
public void execute() {
int count = RANDOM.nextInt(npcs.size() / 50 + 5);
for (int iterations = 0; iterations < count; iterations++) {
Npc npc = npcs.poll();
if (npc == null) {
break;
}
Position[] boundary = npc.getBoundaries().get();
Position current = npc.getPosition();
Position min = boundary[0], max = boundary[1];
int currentX = current.getX(), currentY = current.getY();
boolean negativeX = RANDOM.nextBoolean(), negativeY = RANDOM.nextBoolean();
int x = RANDOM.nextInt(negativeX ? (currentX - min.getX()) : (max.getX() - currentX));
int y = RANDOM.nextInt(negativeY ? (currentY - min.getY()) : (max.getY() - currentY));
int dx = negativeX ? -x : x;
int dy = negativeY ? -y : y;
Position next = new Position(currentX + dx, currentY + dy);
Deque<Position> positions = algorithm.find(current, next, boundary);
WalkingQueue queue = npc.getWalkingQueue();
Position first = positions.pollFirst();
if (first != null && queue.addFirstStep(first)) {
positions.forEach(npc.getWalkingQueue()::addStep);
}
npcs.offer(npc);
}
}
}
@@ -1,5 +1,4 @@
/**
* Contains classes related to scheduling which allow tasks to be executed in
* future pulses periodically.
* Contains classes related to scheduling which allow tasks to be executed in future pulses periodically.
*/
package org.apollo.game.scheduling;
@@ -77,8 +77,7 @@ public final class AppearanceBlock extends SynchronizationBlock {
* @param isSkulled Whether or not the player is skulled.
* @param npcId The npc id of the player, if they are appearing as an npc, (otherwise {@code -1}).
*/
AppearanceBlock(long name, Appearance appearance, int combat, int skill, Inventory equipment, int headIcon,
boolean isSkulled, int npcId) {
AppearanceBlock(long name, Appearance appearance, int combat, int skill, Inventory equipment, int headIcon, boolean isSkulled, int npcId) {
this.name = name;
this.appearance = appearance;
this.combat = combat;
@@ -48,8 +48,7 @@ public final class ForceMovementBlock extends SynchronizationBlock {
* @param travelDurationY The length of time (in game pulses) the player's movement along the Y-axis will last.
* @param direction The direction the player should move.
*/
ForceMovementBlock(Position initialPosition, Position finalPosition, int travelDurationX, int travelDurationY,
Direction direction) {
ForceMovementBlock(Position initialPosition, Position finalPosition, int travelDurationX, int travelDurationY, Direction direction) {
this.initialPosition = initialPosition;
this.finalPosition = finalPosition;
this.travelDurationX = travelDurationX;
@@ -37,8 +37,7 @@ public abstract class SynchronizationBlock {
int combat = player.getSkillSet().getCombatLevel();
int id = player.hasNpcDefinition() ? player.getDefinition().getId() : -1;
return new AppearanceBlock(player.getEncodedName(), player.getAppearance(), combat, 0, player.getEquipment(),
player.getPrayerIcon(), player.isSkulled(), id);
return new AppearanceBlock(player.getEncodedName(), player.getAppearance(), combat, 0, player.getEquipment(), player.getPrayerIcon(), player.isSkulled(), id);
}
/**
@@ -72,8 +71,7 @@ public abstract class SynchronizationBlock {
* @param direction The {@link Direction} the player should move.
* @return The force movement block.
*/
public static SynchronizationBlock createForceMovementBlock(Position initialPosition, Position finalPosition,
int travelDurationX, int travelDurationY, Direction direction) {
public static SynchronizationBlock createForceMovementBlock(Position initialPosition, Position finalPosition, int travelDurationX, int travelDurationY, Direction direction) {
return new ForceMovementBlock(initialPosition, finalPosition, travelDurationX, travelDurationY, direction);
}
@@ -98,10 +96,8 @@ public abstract class SynchronizationBlock {
* @param secondary If the block is a secondary hit or not.
* @return The hit update block.
*/
public static SynchronizationBlock createHitUpdateBlock(int damage, int type, int currentHealth, int maximumHealth,
boolean secondary) {
return secondary ? new SecondaryHitUpdateBlock(damage, type, currentHealth, maximumHealth) : new HitUpdateBlock(damage,
type, currentHealth, maximumHealth);
public static SynchronizationBlock createHitUpdateBlock(int damage, int type, int currentHealth, int maximumHealth, boolean secondary) {
return secondary ? new SecondaryHitUpdateBlock(damage, type, currentHealth, maximumHealth) : new HitUpdateBlock(damage, type, currentHealth, maximumHealth);
}
/**
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Contains classes related to client synchronization - the process where the
* client's state is updated by the server so it matches the server's state.
* Contains classes related to client synchronization - the process where the client's state is updated by the server so
* it matches the server's state.
*/
package org.apollo.game.sync;
@@ -26,8 +26,7 @@ public final class MovementSegment extends SynchronizationSegment {
*/
public MovementSegment(SynchronizationBlockSet blockSet, Direction[] directions) {
super(blockSet);
Preconditions.checkArgument(directions.length >= 0 && directions.length < 3,
"Directions length must be between 0 and 2 inclusive.");
Preconditions.checkArgument(directions.length >= 0 && directions.length < 3, "Directions length must be between 0 and 2 inclusive.");
this.directions = directions;
}
@@ -43,14 +42,14 @@ public final class MovementSegment extends SynchronizationSegment {
@Override
public SegmentType getType() {
switch (directions.length) {
case 0:
return SegmentType.NO_MOVEMENT;
case 1:
return SegmentType.WALK;
case 2:
return SegmentType.RUN;
default:
throw new IllegalStateException("Direction type unsupported.");
case 0:
return SegmentType.NO_MOVEMENT;
case 1:
return SegmentType.WALK;
case 2:
return SegmentType.RUN;
default:
throw new IllegalStateException("Direction type unsupported.");
}
}
@@ -1,6 +1,5 @@
/**
* Contains classes related to synchronization segments. Each segment contains
* multiple blocks and can be used to add, remove, teleport or move a
* mob.
* Contains classes related to synchronization segments. Each segment contains multiple blocks and can be used to add,
* remove, teleport or move a mob.
*/
package org.apollo.game.sync.seg;
@@ -50,8 +50,7 @@ public final class NpcSynchronizationTask extends SynchronizationTask {
for (Iterator<Npc> it = localNpcs.iterator(); it.hasNext();) {
Npc npc = it.next();
if (!npc.isActive() || npc.isTeleporting()
|| npc.getPosition().getLongestDelta(playerPosition) > player.getViewingDistance()) {
if (!npc.isActive() || npc.isTeleporting() || npc.getPosition().getLongestDelta(playerPosition) > player.getViewingDistance()) {
it.remove();
segments.add(new RemoveMobSegment());
} else {
@@ -70,8 +69,7 @@ public final class NpcSynchronizationTask extends SynchronizationTask {
}
Position npcPosition = npc.getPosition();
if (npcPosition.isWithinDistance(playerPosition, player.getViewingDistance()) && !localNpcs.contains(npc)
&& npcPosition.getHeight() == playerPosition.getHeight()) {
if (npcPosition.isWithinDistance(playerPosition, player.getViewingDistance()) && !localNpcs.contains(npc) && npcPosition.getHeight() == playerPosition.getHeight()) {
localNpcs.add(npc);
added++;
npc.turnTo(npc.getFacingPosition());
@@ -38,7 +38,7 @@ public final class PhasedSynchronizationTask extends SynchronizationTask {
public void run() {
try {
task.run();
} catch (Exception e) { // TODO better solution...
} catch (Exception e) {
e.printStackTrace();
// The executor suppresses any exceptions thrown as part of the task, so we catch and print here as
// rethrowing them does nothing.
@@ -70,8 +70,7 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext();) {
Player other = it.next();
if (!other.isActive() || other.isTeleporting()
|| other.getPosition().getLongestDelta(player.getPosition()) > player.getViewingDistance()) {
if (!other.isActive() || other.isTeleporting() || other.getPosition().getLongestDelta(player.getPosition()) > player.getViewingDistance()) {
it.remove();
segments.add(new RemoveMobSegment());
} else {
@@ -91,8 +90,7 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
break;
}
if (other != player && other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance())
&& !localPlayers.contains(other)) {
if (other != player && other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) && !localPlayers.contains(other)) {
localPlayers.add(other);
added++;
@@ -107,8 +105,7 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
}
}
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownSector, player.getPosition(),
sectorChanged, segment, oldLocalPlayers, segments);
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownSector, player.getPosition(), sectorChanged, segment, oldLocalPlayers, segments);
player.send(message);
}
@@ -1,6 +1,5 @@
/**
* Contains classes related to
* {@link org.apollo.game.sync.task.SynchronizationTask}s, small chunks of work
* executed during the client synchronization process.
* Contains classes related to {@link org.apollo.game.sync.task.SynchronizationTask}s, small chunks of work executed
* during the client synchronization process.
*/
package org.apollo.game.sync.task;
@@ -53,8 +53,7 @@ public final class MessageHandlerChainParser {
* @return A {@link MessageHandlerChainGroup}.
*/
@SuppressWarnings("unchecked")
public MessageHandlerChainGroup parse() throws IOException, SAXException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
public MessageHandlerChainGroup parse() throws IOException, SAXException, ClassNotFoundException, InstantiationException, IllegalAccessException {
XmlNode messages = parser.parse(is);
if (!messages.getName().equals("messages")) {
throw new IOException("Root node name is not 'messages'.");
@@ -94,8 +93,7 @@ public final class MessageHandlerChainParser {
throw new IOException("Handler node must have a value.");
}
Class<? extends MessageHandler<?>> handlerClass = (Class<? extends MessageHandler<?>>) Class
.forName(handlerClassName);
Class<? extends MessageHandler<?>> handlerClass = (Class<? extends MessageHandler<?>>) Class.forName(handlerClassName);
MessageHandler<?> handler = handlerClass.newInstance();
handlers.add(handler);
}
@@ -0,0 +1,56 @@
package org.apollo.io.player;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apollo.util.NameUtil;
/**
* A utility class with common functionality used by the binary player loader/ savers.
*
* @author Graham
* @author Major
*/
public final class BinaryFileUtils {
/**
* The Path to the saved games directory.
*/
private static final Path SAVED_GAMES_DIRECTORY = Paths.get("data/savedGames");
/**
* Creates the saved games directory if it does not exist.
*/
static {
try {
if (!Files.exists(SAVED_GAMES_DIRECTORY)) {
Files.createDirectory(SAVED_GAMES_DIRECTORY);
}
} catch (IOException e) {
throw new UncheckedIOException("Error creating saved games directory.", e);
}
}
/**
* Gets the save {@link File} for the specified player.
*
* @param username The username of the player.
* @return The file.
*/
public static Path getFile(String username) {
String filtered = NameUtil.decodeBase37(NameUtil.encodeBase37(username));
return SAVED_GAMES_DIRECTORY.resolve(filtered + ".dat");
}
/**
* Sole private constructor to prevent instantiation.
*/
private BinaryFileUtils() {
}
}
@@ -1,13 +1,20 @@
package org.apollo.io.player.impl;
package org.apollo.io.player;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apollo.game.model.Appearance;
import org.apollo.game.model.Item;
@@ -16,6 +23,8 @@ import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.Skill;
import org.apollo.game.model.entity.SkillSet;
import org.apollo.game.model.entity.attr.Attribute;
import org.apollo.game.model.entity.attr.AttributeMap;
import org.apollo.game.model.entity.attr.AttributePersistence;
import org.apollo.game.model.entity.attr.AttributeType;
import org.apollo.game.model.entity.attr.BooleanAttribute;
import org.apollo.game.model.entity.attr.NumericalAttribute;
@@ -26,8 +35,6 @@ import org.apollo.game.model.entity.setting.PrivacyState;
import org.apollo.game.model.entity.setting.PrivilegeLevel;
import org.apollo.game.model.entity.setting.ScreenBrightness;
import org.apollo.game.model.inv.Inventory;
import org.apollo.io.player.PlayerLoader;
import org.apollo.io.player.PlayerLoaderResponse;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.security.PlayerCredentials;
import org.apollo.util.NameUtil;
@@ -36,28 +43,39 @@ import org.apollo.util.StreamUtil;
import com.lambdaworks.crypto.SCryptUtil;
/**
* A {@link PlayerLoader} implementation that loads data from a binary file.
* A {@link PlayerSerializer} implementation that uses a binary file to store player data.
*
* @author Graham
* @author Major
*/
public final class BinaryPlayerLoader implements PlayerLoader {
public final class BinaryPlayerSerializer implements PlayerSerializer {
/**
* The default spawn position.
* The Path to the saved games directory.
*/
private static final Position SPAWN_POSITION = new Position(3093, 3104);
private static final Path SAVED_GAMES_DIRECTORY = Paths.get("data/savedGames");
static {
try {
if (!Files.exists(SAVED_GAMES_DIRECTORY)) {
Files.createDirectory(SAVED_GAMES_DIRECTORY);
}
} catch (IOException e) {
throw new UncheckedIOException("Error creating saved games directory.", e);
}
}
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws IOException {
File file = BinaryPlayerUtil.getFile(credentials.getUsername());
if (!file.exists()) {
Player player = new Player(credentials, SPAWN_POSITION);
player.getBank().add(995, 25); // 25 coins
Path path = getFile(credentials.getUsername());
if (!Files.exists(path)) {
Player player = new Player(credentials, TUTORIAL_ISLAND_SPAWN);
credentials.setPassword(SCryptUtil.scrypt(credentials.getPassword(), 16384, 8, 1));
return new PlayerLoaderResponse(LoginConstants.STATUS_OK, player);
}
try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(path)))) {
String name = StreamUtil.readString(in);
String password = StreamUtil.readString(in);
@@ -67,37 +85,35 @@ public final class BinaryPlayerLoader implements PlayerLoader {
credentials.setPassword(password); // Update password to the hashed one.
PrivilegeLevel privilegeLevel = PrivilegeLevel.valueOf(in.readByte());
PrivilegeLevel privilege = PrivilegeLevel.valueOf(in.readByte());
MembershipStatus members = MembershipStatus.valueOf(in.readByte());
PrivacyState chatPrivacy = PrivacyState.valueOf(in.readByte(), true);
PrivacyState friendPrivacy = PrivacyState.valueOf(in.readByte(), false);
PrivacyState tradePrivacy = PrivacyState.valueOf(in.readByte(), false);
int runEnergy = in.readByte();
ScreenBrightness brightness = ScreenBrightness.valueOf(in.readByte());
int x = in.readUnsignedShort();
int y = in.readUnsignedShort();
int height = in.readUnsignedByte();
int genderIntValue = in.readUnsignedByte();
Gender gender = genderIntValue == Gender.MALE.toInteger() ? Gender.MALE : Gender.FEMALE;
Gender gender = (in.readUnsignedByte() == Gender.MALE.toInteger()) ? Gender.MALE : Gender.FEMALE;
int[] style = new int[7];
for (int i = 0; i < style.length; i++) {
style[i] = in.readUnsignedByte();
for (int slot = 0; slot < style.length; slot++) {
style[slot] = in.readUnsignedByte();
}
int[] colors = new int[5];
for (int i = 0; i < colors.length; i++) {
colors[i] = in.readUnsignedByte();
for (int slot = 0; slot < colors.length; slot++) {
colors[slot] = in.readUnsignedByte();
}
Player player = new Player(credentials, new Position(x, y, height));
player.setPrivilegeLevel(privilegeLevel);
player.setPrivilegeLevel(privilege);
player.setMembers(members);
player.setChatPrivacy(chatPrivacy);
player.setFriendPrivacy(friendPrivacy);
player.setTradePrivacy(tradePrivacy);
player.setRunEnergy(runEnergy);
player.setScreenBrightness(brightness);
player.setAppearance(new Appearance(gender, style, colors));
@@ -141,6 +157,87 @@ public final class BinaryPlayerLoader implements PlayerLoader {
}
}
@Override
public void savePlayer(Player player) throws IOException {
Path file = getFile(player.getUsername());
try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(file))) {
StreamUtil.writeString(out, player.getUsername());
StreamUtil.writeString(out, player.getCredentials().getPassword());
out.writeByte(player.getPrivilegeLevel().toInteger());
out.writeByte(player.getMembershipStatus().getValue());
out.writeByte(player.getChatPrivacy().toInteger(true));
out.writeByte(player.getFriendPrivacy().toInteger(false));
out.writeByte(player.getTradePrivacy().toInteger(false));
out.writeByte(player.getScreenBrightness().toInteger());
Position position = player.getPosition();
out.writeShort(position.getX());
out.writeShort(position.getY());
out.writeByte(position.getHeight());
Appearance appearance = player.getAppearance();
out.writeByte(appearance.getGender().toInteger());
int[] style = appearance.getStyle();
for (int element : style) {
out.writeByte(element);
}
int[] colors = appearance.getColors();
for (int color : colors) {
out.writeByte(color);
}
writeInventory(out, player.getInventory());
writeInventory(out, player.getEquipment());
writeInventory(out, player.getBank());
SkillSet skills = player.getSkillSet();
out.writeByte(skills.size());
for (int id = 0; id < skills.size(); id++) {
Skill skill = skills.getSkill(id);
out.writeByte(skill.getCurrentLevel());
out.writeDouble(skill.getExperience());
}
List<String> usernames = player.getFriendUsernames();
out.writeByte(usernames.size());
for (String username : usernames) {
out.writeLong(NameUtil.encodeBase37(username));
}
usernames = player.getIgnoredUsernames();
out.writeByte(usernames.size());
for (String username : usernames) {
out.writeLong(NameUtil.encodeBase37(username));
}
Set<Entry<String, Attribute<?>>> attributes = player.getAttributes().entrySet();
attributes.removeIf(e -> AttributeMap.getDefinition(e.getKey()).getPersistence() != AttributePersistence.PERSISTENT);
out.writeInt(attributes.size());
for (Entry<String, Attribute<?>> entry : attributes) {
String name = entry.getKey();
StreamUtil.writeString(out, name);
Attribute<?> attribute = entry.getValue();
out.writeByte(attribute.getType().getValue());
out.write(attribute.encode());
}
}
}
/**
* Gets the save {@link File} for the specified player.
*
* @param username The username of the player.
* @return The file.
*/
private Path getFile(String username) {
String filtered = NameUtil.decodeBase37(NameUtil.encodeBase37(username));
return SAVED_GAMES_DIRECTORY.resolve(filtered + ".dat");
}
/**
* Reads the player's {@link Attribute}s.
*
@@ -148,7 +245,7 @@ public final class BinaryPlayerLoader implements PlayerLoader {
* @return The {@link Map} of attribute names to attributes.
* @throws IOException If there is an error reading from the stream.
*/
private static Map<String, Attribute<?>> readAttributes(DataInputStream in) throws IOException {
private Map<String, Attribute<?>> readAttributes(DataInputStream in) throws IOException {
int count = in.readInt();
Map<String, Attribute<?>> attributes = new HashMap<>(count);
@@ -187,7 +284,7 @@ public final class BinaryPlayerLoader implements PlayerLoader {
* @param inventory The inventory.
* @throws IOException If an I/O error occurs.
*/
private static void readInventory(DataInputStream in, Inventory inventory) throws IOException {
private void readInventory(DataInputStream in, Inventory inventory) throws IOException {
int capacity = in.readUnsignedShort();
inventory.stopFiringEvents();
@@ -206,4 +303,27 @@ public final class BinaryPlayerLoader implements PlayerLoader {
}
}
/**
* Writes an inventory to the specified output stream.
*
* @param out The output stream.
* @param inventory The inventory.
* @throws IOException If an I/O error occurs.
*/
private void writeInventory(DataOutputStream out, Inventory inventory) throws IOException {
int capacity = inventory.capacity();
out.writeShort(capacity);
for (int slot = 0; slot < capacity; slot++) {
Item item = inventory.get(slot);
if (item != null) {
out.writeShort(item.getId() + 1);
out.writeInt(item.getAmount());
} else {
out.writeShort(0);
out.writeInt(0);
}
}
}
}
@@ -1,35 +1,33 @@
package org.apollo.io.player.impl;
package org.apollo.io.player;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.setting.MembershipStatus;
import org.apollo.game.model.entity.setting.PrivilegeLevel;
import org.apollo.io.player.PlayerLoader;
import org.apollo.io.player.PlayerLoaderResponse;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.security.PlayerCredentials;
/**
* A dummy {@link PlayerLoader} implementation used for testing purposes.
* A {@link PlayerSerializer} that saves no data and returns an administrator member account, ideal for debugging.
*
* @author Graham
* @author Major
*/
public final class DummyPlayerLoader implements PlayerLoader {
/**
* The default spawn position for players loaded by this loader.
*/
private static final Position DEFAULT_POSITION = new Position(3093, 3104);
public final class DummyPlayerSerializer implements PlayerSerializer {
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) {
int status = LoginConstants.STATUS_OK;
Player player = new Player(credentials, DEFAULT_POSITION);
Player player = new Player(credentials, TUTORIAL_ISLAND_SPAWN);
player.setPrivilegeLevel(PrivilegeLevel.ADMINISTRATOR);
player.setMembers(MembershipStatus.PAID);
return new PlayerLoaderResponse(status, player);
}
@Override
public void savePlayer(Player player) {
/* discard player */
}
}
@@ -0,0 +1,23 @@
package org.apollo.io.player;
import org.apollo.game.model.entity.Player;
import org.apollo.security.PlayerCredentials;
/**
* A {@link PlayerSerializer} that utilises {@code JDBC} to communicate with an SQL database containing player data.
*
* @author Major
*/
public final class JdbcPlayerSerializer implements PlayerSerializer {
@Override
public void savePlayer(Player player) throws Exception {
throw new UnsupportedOperationException("JDBC saving is not supported at this time.");
}
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception {
throw new UnsupportedOperationException("JDBC loading is not supported at this time.");
}
}
@@ -1,22 +0,0 @@
package org.apollo.io.player;
import org.apollo.security.PlayerCredentials;
/**
* An interface which may be extended by others which are capable of loading players. For example, implementations might
* include text-based, binary and SQL loaders.
*
* @author Graham
*/
public interface PlayerLoader {
/**
* Loads a player.
*
* @param credentials The player's credentials.
* @return The {@link PlayerLoaderResponse}.
* @throws Exception If an error occurs.
*/
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception;
}
@@ -8,9 +8,10 @@ import org.apollo.net.codec.login.LoginConstants;
import com.google.common.base.Preconditions;
/**
* A response for the {@link PlayerLoader#loadPlayer(org.apollo.security.PlayerCredentials)} call.
* A response for the {@link PlayerSerializer#loadPlayer} call.
*
* @author Graham
* @author Major
*/
public final class PlayerLoaderResponse {
-21
View File
@@ -1,21 +0,0 @@
package org.apollo.io.player;
import org.apollo.game.model.entity.Player;
/**
* An interface which may be implemented by others which are capable of saving players. For example, implementations
* might include text-based, binary and SQL savers.
*
* @author Graham
*/
public interface PlayerSaver {
/**
* Saves a player.
*
* @param player The player to save.
* @throws Exception If an error occurs.
*/
public void savePlayer(Player player) throws Exception;
}
@@ -0,0 +1,38 @@
package org.apollo.io.player;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.Player;
import org.apollo.security.PlayerCredentials;
/**
* An interface which may be implemented by others which are capable of serializing and deserializing players. For
* example, implementations might include text-based, binary and SQL serializers.
*
* @author Graham
* @author Major
*/
public interface PlayerSerializer {
/**
* The spawn point for Players, on Tutorial Island.
*/
Position TUTORIAL_ISLAND_SPAWN = new Position(3093, 3104);
/**
* Loads a {@link Player}.
*
* @param credentials The {@link PlayerCredentials}.
* @return The {@link PlayerLoaderResponse}.
* @throws Exception If an error occurs.
*/
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception;
/**
* Saves a {@link Player}.
*
* @param player The Player to save.
* @throws Exception If an error occurs.
*/
public void savePlayer(Player player) throws Exception;
}
@@ -1,126 +0,0 @@
package org.apollo.io.player.impl;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.apollo.game.model.Appearance;
import org.apollo.game.model.Item;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.Skill;
import org.apollo.game.model.entity.SkillSet;
import org.apollo.game.model.entity.attr.Attribute;
import org.apollo.game.model.entity.attr.AttributeMap;
import org.apollo.game.model.entity.attr.AttributePersistence;
import org.apollo.game.model.inv.Inventory;
import org.apollo.io.player.PlayerSaver;
import org.apollo.util.NameUtil;
import org.apollo.util.StreamUtil;
/**
* A {@link PlayerSaver} implementation that saves player data to a binary file.
*
* @author Graham
*/
public final class BinaryPlayerSaver implements PlayerSaver {
@Override
public void savePlayer(Player player) throws IOException {
File file = BinaryPlayerUtil.getFile(player.getUsername());
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
StreamUtil.writeString(out, player.getUsername());
StreamUtil.writeString(out, player.getCredentials().getPassword());
out.writeByte(player.getPrivilegeLevel().toInteger());
out.writeByte(player.getMembershipStatus().getValue());
out.writeByte(player.getChatPrivacy().toInteger(true));
out.writeByte(player.getFriendPrivacy().toInteger(false));
out.writeByte(player.getTradePrivacy().toInteger(false));
out.writeByte(player.getRunEnergy());
out.writeByte(player.getScreenBrightness().toInteger());
Position position = player.getPosition();
out.writeShort(position.getX());
out.writeShort(position.getY());
out.writeByte(position.getHeight());
Appearance appearance = player.getAppearance();
out.writeByte(appearance.getGender().toInteger());
int[] style = appearance.getStyle();
for (int element : style) {
out.writeByte(element);
}
int[] colors = appearance.getColors();
for (int color : colors) {
out.writeByte(color);
}
writeInventory(out, player.getInventory());
writeInventory(out, player.getEquipment());
writeInventory(out, player.getBank());
SkillSet skills = player.getSkillSet();
out.writeByte(skills.size());
for (int id = 0; id < skills.size(); id++) {
Skill skill = skills.getSkill(id);
out.writeByte(skill.getCurrentLevel());
out.writeDouble(skill.getExperience());
}
List<String> usernames = player.getFriendUsernames();
out.writeByte(usernames.size());
for (String username : usernames) {
out.writeLong(NameUtil.encodeBase37(username));
}
usernames = player.getIgnoredUsernames();
out.writeByte(usernames.size());
for (String username : usernames) {
out.writeLong(NameUtil.encodeBase37(username));
}
Set<Entry<String, Attribute<?>>> attributes = player.getAttributes().entrySet();
attributes.removeIf(e -> AttributeMap.getDefinition(e.getKey()).getPersistence() != AttributePersistence.PERSISTENT);
out.writeInt(attributes.size());
for (Entry<String, Attribute<?>> entry : attributes) {
String name = entry.getKey();
StreamUtil.writeString(out, name);
Attribute<?> attribute = entry.getValue();
out.writeByte(attribute.getType().getValue());
out.write(attribute.encode());
}
}
}
/**
* Writes an inventory to the specified output stream.
*
* @param out The output stream.
* @param inventory The inventory.
* @throws IOException If an I/O error occurs.
*/
private static void writeInventory(DataOutputStream out, Inventory inventory) throws IOException {
int capacity = inventory.capacity();
out.writeShort(capacity);
for (int slot = 0; slot < capacity; slot++) {
Item item = inventory.get(slot);
if (item != null) {
out.writeShort(item.getId() + 1);
out.writeInt(item.getAmount());
} else {
out.writeShort(0);
out.writeInt(0);
}
}
}
}
@@ -1,46 +0,0 @@
package org.apollo.io.player.impl;
import java.io.File;
import org.apollo.util.NameUtil;
/**
* A utility class with common functionality used by the binary player loader/ savers.
*
* @author Graham
*/
public final class BinaryPlayerUtil {
/**
* The saved games directory.
*/
private static final File SAVED_GAMES_DIRECTORY = new File("data/savedGames");
/**
* Creates the saved games directory if it does not exist.
*/
static {
if (!SAVED_GAMES_DIRECTORY.exists()) {
SAVED_GAMES_DIRECTORY.mkdir();
}
}
/**
* Gets the save {@link File} for the specified player.
*
* @param username The username of the player.
* @return The file.
*/
public static File getFile(String username) {
String filtered = NameUtil.decodeBase37(NameUtil.encodeBase37(username));
return new File(SAVED_GAMES_DIRECTORY, filtered + ".dat");
}
/**
* Default private constructor to prevent instantiation.
*/
private BinaryPlayerUtil() {
}
}
@@ -1,18 +0,0 @@
package org.apollo.io.player.impl;
import org.apollo.game.model.entity.Player;
import org.apollo.io.player.PlayerSaver;
/**
* A {@link PlayerSaver} implementation that discards player data.
*
* @author Graham
*/
public final class DiscardPlayerSaver implements PlayerSaver {
@Override
public void savePlayer(Player player) {
/* discard player */
}
}
@@ -1,19 +0,0 @@
package org.apollo.io.player.impl;
import org.apollo.io.player.PlayerLoader;
import org.apollo.io.player.PlayerLoaderResponse;
import org.apollo.security.PlayerCredentials;
/**
* A {@link PlayerLoader} that utilises {@code JDBC} to load player files.
*
* @author Major
*/
public final class JdbcPlayerLoader implements PlayerLoader {
@Override
public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception {
throw new UnsupportedOperationException("JDBC loading is not supported at this time.");
}
}
@@ -1,18 +0,0 @@
package org.apollo.io.player.impl;
import org.apollo.game.model.entity.Player;
import org.apollo.io.player.PlayerSaver;
/**
* A {@link PlayerSaver} that utilises {@code JDBC} to save the player.
*
* @author Major
*/
public final class JdbcPlayerSaver implements PlayerSaver {
@Override
public void savePlayer(Player player) throws Exception {
throw new UnsupportedOperationException("JDBC saving is not supported at this time.");
}
}
@@ -1,4 +0,0 @@
/**
* Contains various player loader/saver implementations.
*/
package org.apollo.io.player.impl;
+13 -26
View File
@@ -8,9 +8,8 @@ import java.util.concurrent.Executors;
import org.apollo.Service;
import org.apollo.game.model.entity.Player;
import org.apollo.io.player.PlayerLoader;
import org.apollo.io.player.PlayerLoaderResponse;
import org.apollo.io.player.PlayerSaver;
import org.apollo.io.player.PlayerSerializer;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.net.codec.login.LoginRequest;
import org.apollo.net.release.Release;
@@ -25,6 +24,7 @@ import org.xml.sax.SAXException;
* The {@link LoginService} manages {@link LoginRequest}s.
*
* @author Graham
* @author Major
*/
public final class LoginService extends Service {
@@ -34,14 +34,9 @@ public final class LoginService extends Service {
private final ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory("LoginService"));
/**
* The current {@link PlayerLoader}.
* The current {@link PlayerSerializer}.
*/
private PlayerLoader loader;
/**
* The current {@link PlayerSaver}.
*/
private PlayerSaver saver;
private PlayerSerializer serializer;
/**
* Creates the login service.
@@ -70,24 +65,16 @@ public final class LoginService extends Service {
}
if (!rootNode.getName().equals("login")) {
throw new IOException("Unexpected root node name.");
throw new IOException("Unexpected root node name, expected 'login'.");
}
XmlNode loaderNode = rootNode.getChild("loader");
if (loaderNode == null || !loaderNode.hasValue()) {
throw new IOException("No loader child node or value.");
XmlNode serializer = rootNode.getChild("serializer");
if (serializer == null || !serializer.hasValue()) {
throw new IOException("No serializer child node or value.");
}
XmlNode saverNode = rootNode.getChild("saver");
if (saverNode == null || !saverNode.hasValue()) {
throw new IOException("No saver child node or value.");
}
Class<?> loaderClazz = Class.forName(loaderNode.getValue());
Class<?> saverClazz = Class.forName(saverNode.getValue());
loader = (PlayerLoader) loaderClazz.newInstance();
saver = (PlayerSaver) saverClazz.newInstance();
Class<?> clazz = Class.forName(serializer.getValue());
this.serializer = (PlayerSerializer) clazz.newInstance();
}
/**
@@ -95,7 +82,7 @@ public final class LoginService extends Service {
*/
@Override
public void start() {
/* empty - here for consistency with other services */
}
/**
@@ -110,7 +97,7 @@ public final class LoginService extends Service {
// TODO check archive 0 CRCs
session.handlePlayerLoaderResponse(request, new PlayerLoaderResponse(LoginConstants.STATUS_GAME_UPDATED));
} else {
executor.submit(new PlayerLoaderWorker(loader, session, request));
executor.submit(new PlayerLoaderWorker(serializer, session, request));
}
}
@@ -121,7 +108,7 @@ public final class LoginService extends Service {
* @param player The player to save.
*/
public void submitSaveRequest(GameSession session, Player player) {
executor.submit(new PlayerSaverWorker(saver, session, player));
executor.submit(new PlayerSaverWorker(serializer, session, player));
}
}
+5 -5
View File
@@ -3,8 +3,8 @@ package org.apollo.login;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apollo.io.player.PlayerLoader;
import org.apollo.io.player.PlayerLoaderResponse;
import org.apollo.io.player.PlayerSerializer;
import org.apollo.net.codec.login.LoginConstants;
import org.apollo.net.codec.login.LoginRequest;
import org.apollo.net.session.LoginSession;
@@ -22,9 +22,9 @@ public final class PlayerLoaderWorker implements Runnable {
private static final Logger logger = Logger.getLogger(PlayerLoaderWorker.class.getName());
/**
* The player loader.
* The PlayerSerializer.
*/
private final PlayerLoader loader;
private final PlayerSerializer loader;
/**
* The request.
@@ -39,11 +39,11 @@ public final class PlayerLoaderWorker implements Runnable {
/**
* Creates a {@link PlayerLoaderWorker} which will do the work for a single player load request.
*
* @param loader The current player loader.
* @param loader The {@link PlayerSerializer}.
* @param session The {@link LoginSession} which initiated the request.
* @param request The {@link LoginRequest} object.
*/
public PlayerLoaderWorker(PlayerLoader loader, LoginSession session, LoginRequest request) {
public PlayerLoaderWorker(PlayerSerializer loader, LoginSession session, LoginRequest request) {
this.loader = loader;
this.session = session;
this.request = request;
+3 -3
View File
@@ -4,7 +4,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import org.apollo.game.model.entity.Player;
import org.apollo.io.player.PlayerSaver;
import org.apollo.io.player.PlayerSerializer;
import org.apollo.net.session.GameSession;
/**
@@ -27,7 +27,7 @@ public final class PlayerSaverWorker implements Runnable {
/**
* The player saver.
*/
private final PlayerSaver saver;
private final PlayerSerializer saver;
/**
* The game session.
@@ -41,7 +41,7 @@ public final class PlayerSaverWorker implements Runnable {
* @param session The game session.
* @param player The player to save.
*/
public PlayerSaverWorker(PlayerSaver saver, GameSession session, Player player) {
public PlayerSaverWorker(PlayerSerializer saver, GameSession session, Player player) {
this.saver = saver;
this.session = session;
this.player = player;
+6 -6
View File
@@ -85,13 +85,13 @@ public final class ApolloHandler extends ChannelInboundHandlerAdapter {
HandshakeMessage handshakeMessage = (HandshakeMessage) message;
switch (handshakeMessage.getServiceId()) {
case HandshakeConstants.SERVICE_GAME:
attribute.set(new LoginSession(ctx, serverContext));
break;
case HandshakeConstants.SERVICE_GAME:
attribute.set(new LoginSession(ctx, serverContext));
break;
case HandshakeConstants.SERVICE_UPDATE:
attribute.set(new UpdateSession(ctx.channel(), serverContext));
break;
case HandshakeConstants.SERVICE_UPDATE:
attribute.set(new UpdateSession(ctx.channel(), serverContext));
break;
}
}
@@ -62,17 +62,17 @@ public final class GamePacketDecoder extends StatefulFrameDecoder<GameDecoderSta
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, GameDecoderState state) {
switch (state) {
case GAME_OPCODE:
decodeOpcode(in, out);
break;
case GAME_LENGTH:
decodeLength(in);
break;
case GAME_PAYLOAD:
decodePayload(in, out);
break;
default:
throw new IllegalStateException("Invalid game decoder state.");
case GAME_OPCODE:
decodeOpcode(in, out);
break;
case GAME_LENGTH:
decodeLength(in);
break;
case GAME_PAYLOAD:
decodePayload(in, out);
break;
default:
throw new IllegalStateException("Invalid game decoder state.");
}
}
@@ -106,20 +106,20 @@ public final class GamePacketDecoder extends StatefulFrameDecoder<GameDecoderSta
type = metaData.getType();
switch (type) {
case FIXED:
length = metaData.getLength();
if (length == 0) {
setState(GameDecoderState.GAME_OPCODE);
out.add(new GamePacket(opcode, type, Unpooled.EMPTY_BUFFER));
} else {
setState(GameDecoderState.GAME_PAYLOAD);
}
break;
case VARIABLE_BYTE:
setState(GameDecoderState.GAME_LENGTH);
break;
default:
throw new IllegalStateException("Illegal packet type: " + type + ".");
case FIXED:
length = metaData.getLength();
if (length == 0) {
setState(GameDecoderState.GAME_OPCODE);
out.add(new GamePacket(opcode, type, Unpooled.EMPTY_BUFFER));
} else {
setState(GameDecoderState.GAME_PAYLOAD);
}
break;
case VARIABLE_BYTE:
setState(GameDecoderState.GAME_LENGTH);
break;
default:
throw new IllegalStateException("Illegal packet type: " + type + ".");
}
}
}
@@ -34,23 +34,23 @@ public final class HandshakeDecoder extends ByteToMessageDecoder {
int id = buffer.readUnsignedByte();
switch (id) {
case HandshakeConstants.SERVICE_GAME:
ctx.pipeline().addFirst("loginEncoder", new LoginEncoder());
ctx.pipeline().addAfter("handshakeDecoder", "loginDecoder", new LoginDecoder());
break;
case HandshakeConstants.SERVICE_GAME:
ctx.pipeline().addFirst("loginEncoder", new LoginEncoder());
ctx.pipeline().addAfter("handshakeDecoder", "loginDecoder", new LoginDecoder());
break;
case HandshakeConstants.SERVICE_UPDATE:
ctx.pipeline().addFirst("updateEncoder", new UpdateEncoder());
ctx.pipeline().addBefore("handler", "updateDecoder", new UpdateDecoder());
case HandshakeConstants.SERVICE_UPDATE:
ctx.pipeline().addFirst("updateEncoder", new UpdateEncoder());
ctx.pipeline().addBefore("handler", "updateDecoder", new UpdateDecoder());
ByteBuf buf = ctx.alloc().buffer(8).writeLong(0);
ctx.channel().writeAndFlush(buf);
break;
ByteBuf buf = ctx.alloc().buffer(8).writeLong(0);
ctx.channel().writeAndFlush(buf);
break;
default:
ByteBuf data = buffer.readBytes(buffer.readableBytes());
logger.info(String.format("Unexpected handshake request received: %d data: %s", id, data.toString()));
return;
default:
ByteBuf data = buffer.readBytes(buffer.readableBytes());
logger.info(String.format("Unexpected handshake request received: %d data: %s", id, data.toString()));
return;
}
ctx.pipeline().remove(this);
@@ -60,17 +60,17 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, LoginDecoderState state) {
switch (state) {
case LOGIN_HANDSHAKE:
decodeHandshake(ctx, in, out);
break;
case LOGIN_HEADER:
decodeHeader(ctx, in, out);
break;
case LOGIN_PAYLOAD:
decodePayload(ctx, in, out);
break;
default:
throw new IllegalStateException("Invalid login decoder state: " + state);
case LOGIN_HANDSHAKE:
decodeHandshake(ctx, in, out);
break;
case LOGIN_HEADER:
decodeHeader(ctx, in, out);
break;
case LOGIN_PAYLOAD:
decodePayload(ctx, in, out);
break;
default:
throw new IllegalStateException("Invalid login decoder state: " + state);
}
}
@@ -105,14 +105,14 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
*/
private void decodeHeader(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.readableBytes() >= 2) {
int loginType = buffer.readUnsignedByte();
int type = buffer.readUnsignedByte();
if (loginType != LoginConstants.TYPE_STANDARD && loginType != LoginConstants.TYPE_RECONNECTION) {
if (type != LoginConstants.TYPE_STANDARD && type != LoginConstants.TYPE_RECONNECTION) {
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
reconnecting = loginType == LoginConstants.TYPE_RECONNECTION;
reconnecting = type == LoginConstants.TYPE_RECONNECTION;
loginLength = buffer.readUnsignedByte();
setState(LoginDecoderState.LOGIN_PAYLOAD);
@@ -129,53 +129,51 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
private void decodePayload(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.readableBytes() >= loginLength) {
ByteBuf payload = buffer.readBytes(loginLength);
int clientVersion = 255 - payload.readUnsignedByte();
int version = 255 - payload.readUnsignedByte();
int releaseNumber = payload.readUnsignedShort();
int release = payload.readUnsignedShort();
int lowMemoryFlag = payload.readUnsignedByte();
if (lowMemoryFlag != 0 && lowMemoryFlag != 1) {
int memoryStatus = payload.readUnsignedByte();
if (memoryStatus != 0 && memoryStatus != 1) {
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
boolean lowMemory = lowMemoryFlag == 1;
boolean lowMemory = memoryStatus == 1;
int[] archiveCrcs = new int[FileSystemConstants.ARCHIVE_COUNT];
for (int i = 0; i < 9; i++) {
archiveCrcs[i] = payload.readInt();
int[] crcs = new int[FileSystemConstants.ARCHIVE_COUNT];
for (int index = 0; index < 9; index++) {
crcs[index] = payload.readInt();
}
int securePayloadLength = payload.readUnsignedByte();
if (securePayloadLength != loginLength - 41) {
int length = payload.readUnsignedByte();
if (length != loginLength - 41) {
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
ByteBuf securePayload = payload.readBytes(securePayloadLength);
ByteBuf secure = payload.readBytes(length);
BigInteger bigInteger = new BigInteger(securePayload.array());
bigInteger = bigInteger.modPow(NetworkConstants.RSA_EXPONENT, NetworkConstants.RSA_MODULUS);
BigInteger value = new BigInteger(secure.array());
value = value.modPow(NetworkConstants.RSA_EXPONENT, NetworkConstants.RSA_MODULUS);
secure = Unpooled.wrappedBuffer(value.toByteArray());
securePayload = Unpooled.wrappedBuffer(bigInteger.toByteArray());
int secureId = securePayload.readUnsignedByte();
if (secureId != 10) {
int id = secure.readUnsignedByte();
if (id != 10) {
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
long clientSeed = securePayload.readLong();
long reportedServerSeed = securePayload.readLong();
if (reportedServerSeed != serverSeed) {
long clientSeed = secure.readLong();
long reportedSeed = secure.readLong();
if (reportedSeed != serverSeed) {
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
int uid = securePayload.readInt();
String username = BufferUtil.readString(securePayload);
String password = BufferUtil.readString(securePayload);
int uid = secure.readInt();
String username = BufferUtil.readString(secure);
String password = BufferUtil.readString(secure);
if (password.length() < 6 || password.length() > 20 || username.isEmpty() || username.length() > 12) {
writeResponseCode(ctx, LoginConstants.STATUS_INVALID_CREDENTIALS);
@@ -189,8 +187,8 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
seed[3] = (int) serverSeed;
IsaacRandom decodingRandom = new IsaacRandom(seed);
for (int i = 0; i < seed.length; i++) {
seed[i] += 50;
for (int index = 0; index < seed.length; index++) {
seed[index] += 50;
}
IsaacRandom encodingRandom = new IsaacRandom(seed);
@@ -198,9 +196,7 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
PlayerCredentials credentials = new PlayerCredentials(username, password, usernameHash, uid);
IsaacRandomPair randomPair = new IsaacRandomPair(encodingRandom, decodingRandom);
LoginRequest request = new LoginRequest(credentials, randomPair, reconnecting, lowMemory, releaseNumber, archiveCrcs, clientVersion);
out.add(request);
out.add(new LoginRequest(credentials, randomPair, reconnecting, lowMemory, release, crcs, version));
}
}
@@ -208,11 +204,11 @@ public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState>
* Writes a response code to the client and closes the current channel.
*
* @param ctx The context of the channel handler.
* @param responseCode The response code to write.
* @param response The response code to write.
*/
private void writeResponseCode(ChannelHandlerContext ctx, int responseCode) {
private void writeResponseCode(ChannelHandlerContext ctx, int response) {
ByteBuf buffer = ctx.alloc().buffer(1);
buffer.writeByte(responseCode);
buffer.writeByte(response);
ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE);
}
@@ -56,8 +56,7 @@ public final class LoginRequest {
* @param archiveCrcs The archive CRCs.
* @param clientVersion The client version.
*/
public LoginRequest(PlayerCredentials credentials, IsaacRandomPair randomPair, boolean lowMemory, boolean reconnecting,
int releaseNumber, int[] archiveCrcs, int clientVersion) {
public LoginRequest(PlayerCredentials credentials, IsaacRandomPair randomPair, boolean lowMemory, boolean reconnecting, int releaseNumber, int[] archiveCrcs, int clientVersion) {
this.credentials = credentials;
this.randomPair = randomPair;
this.lowMemory = lowMemory;
@@ -41,14 +41,14 @@ public final class OnDemandRequest implements Comparable<OnDemandRequest> {
*/
public static Priority valueOf(int value) {
switch (value) {
case 0:
return HIGH;
case 1:
return MEDIUM;
case 2:
return LOW;
default:
throw new IllegalArgumentException("Priority out of range - received " + value + ".");
case 0:
return HIGH;
case 1:
return MEDIUM;
case 2:
return LOW;
default:
throw new IllegalArgumentException("Priority out of range - received " + value + ".");
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Contains classes related to networking. Many of these extend Netty's set of
* classes - such as the pipeline factory, handler and codecs.
* Contains classes related to networking. Many of these extend Netty's set of classes - such as the pipeline factory,
* handler and codecs.
*/
package org.apollo.net;
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* Contains abstract classes which should be extended by a particular release,
* allowing for portability between various protocol and client releases.
* Contains abstract classes which should be extended by a particular release, allowing for portability between various
* protocol and client releases.
*/
package org.apollo.net.release;
@@ -227,8 +227,7 @@ public final class NpcSynchronizationMessageEncoder extends MessageEncoder<NpcSy
* @param message The message.
* @param builder The builder.
*/
private static void putMovementUpdate(SynchronizationSegment segment, NpcSynchronizationMessage message,
GamePacketBuilder builder) {
private static void putMovementUpdate(SynchronizationSegment segment, NpcSynchronizationMessage message, GamePacketBuilder builder) {
boolean updateRequired = segment.getBlockSet().size() > 0;
if (segment.getType() == SegmentType.RUN) {
Direction[] directions = ((MovementSegment) segment).getDirections();
@@ -388,8 +388,7 @@ public final class PlayerSynchronizationMessageEncoder extends MessageEncoder<Pl
* @param message The message.
* @param builder The builder.
*/
private static void putMovementUpdate(SynchronizationSegment seg, PlayerSynchronizationMessage message,
GamePacketBuilder builder) {
private static void putMovementUpdate(SynchronizationSegment seg, PlayerSynchronizationMessage message, GamePacketBuilder builder) {
boolean updateRequired = seg.getBlockSet().size() > 0;
if (seg.getType() == SegmentType.TELEPORT) {
Position position = ((TeleportSegment) seg).getDestination();
@@ -18,7 +18,6 @@ public final class ThirdObjectActionMessageDecoder extends MessageDecoder<ThirdO
@Override
public ThirdObjectActionMessage decode(GamePacket packet) {
// TODO ripped out of some Winterlove-based server, so probably wrong
GamePacketReader reader = new GamePacketReader(packet);
int x = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE);
int y = (int) reader.getUnsigned(DataType.SHORT);
@@ -11,11 +11,11 @@ import org.apollo.net.release.MessageDecoder;
*/
public final class FirstNpcActionMessageDecoder extends MessageDecoder<FirstNpcActionMessage> {
@Override
public FirstNpcActionMessage decode(GamePacket packet) {
GamePacketReader reader = new GamePacketReader(packet);
int index = (int) reader.getSigned(DataType.SHORT, DataOrder.LITTLE);
return new FirstNpcActionMessage(index);
}
@Override
public FirstNpcActionMessage decode(GamePacket packet) {
GamePacketReader reader = new GamePacketReader(packet);
int index = (int) reader.getSigned(DataType.SHORT, DataOrder.LITTLE);
return new FirstNpcActionMessage(index);
}
}
@@ -29,7 +29,7 @@ public final class FlaggedMouseEventMessageDecoder extends MessageDecoder<Flagge
} else {
read = (int) reader.getUnsigned(DataType.INT) & ~0xc0000000;
}
clicks = (read >> 19);
x = (read & 0x7f) % 765;
y = (read & 0x7f) / 765;

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