diff --git a/.gitignore b/.gitignore index 07a10d66..e3e103fa 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /data/fs /data/savedGames /bin/ +/lib/ \ No newline at end of file diff --git a/data/note-317.dat b/data/note-317.dat deleted file mode 100644 index 8438dbb2..00000000 Binary files a/data/note-317.dat and /dev/null differ diff --git a/data/note-377.dat b/data/note-377.dat deleted file mode 100644 index 488fec5b..00000000 Binary files a/data/note-377.dat and /dev/null differ diff --git a/data/plugins/areas/actions.rb b/data/plugins/areas/actions.rb index e72f42f4..58eab91e 100644 --- a/data/plugins/areas/actions.rb +++ b/data/plugins/areas/actions.rb @@ -3,8 +3,20 @@ require 'java' java_import 'org.apollo.game.message.impl.DisplayCrossbonesMessage' java_import 'org.apollo.game.model.entity.Player' + + +# Registers an area action. +def area_action(name, &block) + AREA_ACTIONS[name] = action = AreaAction.new + action.instance_eval(&block) +end + + AREA_ACTIONS = {} + +private + # An action that is called when a player enters or exits an area. class AreaAction @@ -38,31 +50,4 @@ class AreaAction @on_exit.call(player) unless @on_exit.nil? end -end - -# Registers an area action. -def area_action(name, &block) - AREA_ACTIONS[name] = action = AreaAction.new - action.instance_eval(&block) -end - -# Defines the pvp area action. -area_action :pvp do - on_entry { |player| player.in_pvp = true } - on_exit { |player| player.in_pvp = false } -end - -# Defines the wilderness area action. -area_action :wilderness do - - on_entry do |player| - player.send(DisplayCrossbonesMessage.new(true)) - player.in_wilderness = true - end - - on_exit do |player| - player.send(DisplayCrossbonesMessage.new(false)) - player.in_wilderness = false - end - end \ No newline at end of file diff --git a/data/plugins/areas/areas.rb b/data/plugins/areas/areas.rb index cb540224..369dd2b7 100644 --- a/data/plugins/areas/areas.rb +++ b/data/plugins/areas/areas.rb @@ -1,11 +1,26 @@ require 'java' +java_import 'org.apollo.game.model.Position' +java_import 'org.apollo.game.model.entity.Entity$EntityType' java_import 'org.apollo.game.model.entity.Player' -# Todo make 0 the default height + + +# Creates a new area and registers it with the supplied coordinates. +def area(hash) + raise 'Hash must contain a name, coordinates, and actions pair.' unless hash.has_keys?(:name, :coordinates, :actions) + name = hash[:name]; coordinates = hash[:coordinates]; actions = hash[:actions] + + actions = [ actions ] if actions.is_a?(Symbol) + actions.map! { |action| AREA_ACTIONS[action]} + @areas << Area.new(name, coordinates, actions) +end + + +private # A map of coordinates (as an array) to areas. -AREAS = [] +@areas = [] # An area of the game world. class Area @@ -16,31 +31,70 @@ class Area @actions = actions end - # Called when the player has entered the area. - def entered(player) - actions.each { |action| action.entered(player) } + def min_x() # TODO better data structure and methods than this + @coordinates[0] end - # Called whilst the player is inside the area. + def min_y() + @coordinates[1] + end + + def max_x() + @coordinates[2] + end + + def max_y() + @coordinates[3] + end + + def height() + @coordinates[4] + end + + # Called when the player has entered the area. + def entered(player) + @actions.each { |action| action.entered(player) } + end + + # Called when the player has moved, but is still inside the area (and was in the area before). def inside(player) - acttions.each { |action| action.inside(player) } + @actions.each { |action| action.inside(player) } end # Called when the player has exited the area. def exited(player) - actions.each { |action| action.exited(player) } + @actions.each { |action| action.exited(player) } end end -# Creates a new area and registers it with the supplied coordinates. -def area(hash) - raise 'Hash must contain a name, coordinates, and actions pair.' unless hash.has_keys?(:name, :coordinates, :actions) - name = hash[:name]; coordinates = hash[:coordinates]; actions = hash[:actions] +# Listen for the MobPositionUpdateEvent and update the area listeners if appropriate. +on :mob_position_update do |event| + mob = event.mob + next unless mob.entity_type == EntityType::PLAYER - AREAS << Area.new(name, coordinates, actions.is_a?(Symbol) ? [actions] : actions) + old = mob.position + @areas.each do |area| + was_inside = old.inside(area) + next_inside = event.next.inside(area) + + if was_inside + if next_inside then area.inside(mob) else area.exited(mob) end + else + area.entered(mob) if next_inside + end + end end -# Coordinates refer to the bottom-left position (min_x, min_y) and the top-right position (max_x, max_y), followed by the height (optional). -area :name => :wilderness, :coordinates => [ 2944, 3520, 3392, 6400, 0 ], :actions => [ :pvp, :multicombat, :wilderness ] -area :name => :duel_arena, :coordinates => [ 3327, 3200, 3392, 3286 ], :actions => :pvp \ No newline at end of file +# The existing Position class. +class Position + + # Returns whether or not this Position is inside the specified Area. + def inside(area) + return false if (x < area.min_x() || x > area.max_x() || y < area.min_y() || y > area.max_y()) + z = area.height() + + return true if (z.nil? || z == height) + end + +end \ No newline at end of file diff --git a/data/plugins/bootstrap.rb b/data/plugins/bootstrap.rb index 41224dbc..134e006b 100644 --- a/data/plugins/bootstrap.rb +++ b/data/plugins/bootstrap.rb @@ -16,11 +16,11 @@ require 'java' java_import 'org.apollo.game.command.CommandListener' java_import 'org.apollo.game.message.handler.MessageHandler' -java_import 'org.apollo.game.login.LoginListener' -java_import 'org.apollo.game.login.LogoutListener' java_import 'org.apollo.game.model.World' java_import 'org.apollo.game.model.entity.Player' -java_import 'org.apollo.game.model.setting.PrivilegeLevel' +java_import 'org.apollo.game.model.event.EventListener' +java_import 'org.apollo.game.model.event.PlayerEvent' +java_import 'org.apollo.game.model.entity.setting.PrivilegeLevel' java_import 'org.apollo.game.scheduling.ScheduledTask' java_import 'org.apollo.util.plugin.PluginContext' @@ -32,64 +32,61 @@ RIGHTS_STANDARD = PrivilegeLevel::STANDARD # Extends the (Ruby) String class with a method to convert a lower case, # underscore delimited string to camel-case. class String - def camelize + + # Converts a ruby snake_case string to camel-case. + def camelize() gsub(/(?:^|_)(.)/) { $1.upcase } end + end # A CommandListener that executes a Proc object with two arguments: the player and the command. class ProcCommandListener < CommandListener + # Creates the ProcCommandListener. def initialize(rights, block) super(rights) @block = block end + # Executes the block listening for the command. def execute(player, command) @block.call(player, command) end end -# A LoginListener that executes a Proc object with the player argument. -class ProcLoginListener - java_implements LoginListener - - def initialize(block) - super() - @block = block - end - - def execute(player) - @block.call(player) - end -end - # A LogoutListener that executes a Proc object with the player argument. -class ProcLogoutListener - java_implements LogoutListener +class ProcEventListener + java_implements EventListener + # Creates the ProcEventListener. def initialize(block) super() @block = block end - def execute(player) - @block.call(player) + # Executes the block handling the Event. + def handle(event) + args = [ event ] + args << event.player if event.kind_of?(PlayerEvent) + @block.call(*args) end end -# An MessageHandler which executes a Proc object with three arguments: the chain +# A MessageHandler which executes a Proc object with three arguments: the chain # context, the player and the message. class ProcMessageHandler < MessageHandler + # Creates the ProcMessageListener. def initialize(block, option) - super() # required (with brackets!), see http://jira.codehaus.org/browse/JRUBY-679 + super() @block = block @option = option end + # Handles the message. def handle(ctx, player, message) @block.call(ctx, player, message) if (@option == 0 || @option == message.option) end @@ -99,11 +96,13 @@ end # A ScheduledTask which executes a Proc object with one argument (itself). class ProcScheduledTask < ScheduledTask + # Creates the ProcScheduledTask. def initialize(delay, immediate, block) super(delay, immediate) @block = block end + # Executes the block. def execute @block.call(self) end @@ -138,8 +137,7 @@ end # * :command # * :message # * :button -# * :login -# * :logout +# * Any valid Event, as a symbol in ruby snake_case form. # # A command takes one or two arguments (the command name and optionally the # minimum rights level to use it). The minimum rights level defaults to @@ -155,9 +153,10 @@ def on(type, *args, &block) when :command then on_command(args, block) when :message then on_message(args, block) when :button then on_button(args, block) - when :login then on_login(block) - when :logout then on_logout(block) - else raise 'Unknown message type.' + else + class_name = type.to_s.camelize.concat('Event') + type = Java::JavaClass.for_name("org.apollo.game.model.event.impl.#{class_name}") + $world.listen_for(type, ProcEventListener.new(block)) end end @@ -189,10 +188,9 @@ def on_message(args, proc) end end - if message.is_a?(Symbol) - class_name = message.to_s.camelize.concat('Message') - message = Java::JavaClass.for_name("org.apollo.game.message.impl.#{class_name}") - end + + class_name = message.to_s.camelize.concat('Message') + message = Java::JavaClass.for_name("org.apollo.game.message.impl.#{class_name}") $ctx.add_last_message_handler(message, ProcMessageHandler.new(proc, option)) end diff --git a/data/plugins/chat/privacy/privacy.rb b/data/plugins/chat/privacy/privacy.rb index 4de03b8b..8200fce7 100644 --- a/data/plugins/chat/privacy/privacy.rb +++ b/data/plugins/chat/privacy/privacy.rb @@ -1,6 +1,6 @@ require 'java' -java_import 'org.apollo.game.model.setting.PrivacyState' +java_import 'org.apollo.game.model.entity.setting.PrivacyState' java_import 'org.apollo.game.message.impl.SendFriendMessage' on :message, :privacy_option do |ctx, player, message| diff --git a/data/plugins/chat/private-messaging/friend.rb b/data/plugins/chat/private-messaging/friend.rb index dea9d769..9433659a 100644 --- a/data/plugins/chat/private-messaging/friend.rb +++ b/data/plugins/chat/private-messaging/friend.rb @@ -4,8 +4,8 @@ java_import 'org.apollo.game.message.impl.FriendServerStatusMessage' java_import 'org.apollo.game.message.impl.IgnoreListMessage' java_import 'org.apollo.game.message.impl.SendFriendMessage' java_import 'org.apollo.game.model.World' -java_import 'org.apollo.game.model.setting.ServerStatus' -java_import 'org.apollo.game.model.setting.PrivacyState' +java_import 'org.apollo.game.model.entity.setting.ServerStatus' +java_import 'org.apollo.game.model.entity.setting.PrivacyState' java_import 'org.apollo.game.model.entity.Player' @@ -42,7 +42,7 @@ on :message, :remove_friend do |ctx, player, message| end # Update the friend server status and send the friend/ignore lists of the player logging in. -on :login do |player| +on :login do |event, player| player.send(FriendServerStatusMessage.new(ServerStatus::CONNECTING)) player.send(IgnoreListMessage.new(player.ignored_usernames)) if player.ignored_usernames.size > 0 @@ -62,7 +62,7 @@ on :login do |player| end # Notifies the player's friends that the player has logged out. -on :logout do |player| +on :logout do |event, player| update_friends(player, 0) end diff --git a/data/plugins/chat/private-messaging/messaging.rb b/data/plugins/chat/private-messaging/messaging.rb index 1d91b94d..17aa7d77 100644 --- a/data/plugins/chat/private-messaging/messaging.rb +++ b/data/plugins/chat/private-messaging/messaging.rb @@ -2,7 +2,7 @@ require 'java' java_import 'org.apollo.game.message.impl.ForwardPrivateChatMessage' java_import 'org.apollo.game.model.World' -java_import 'org.apollo.game.model.setting.PrivacyState' +java_import 'org.apollo.game.model.entity.setting.PrivacyState' on :message, :private_chat do |ctx, player, message| friend = $world.get_player(message.username) @@ -11,9 +11,9 @@ end # Checks if the sender is permitted to interact with the friend they have added: def interaction_permitted(sender, friend) - if friend == nil || friend.has_ignored(sender.username) + if friend.nil? || friend.has_ignored(sender.username) return false end - return friend.friends_with(sender.username) ? friend.friend_privacy != PrivacyState::OFF : friend.friend_privacy == PrivacyState::ON + return friend.friends_with(sender.username) ? (friend.friend_privacy != PrivacyState::OFF) : (friend.friend_privacy == PrivacyState::ON) end \ No newline at end of file diff --git a/data/plugins/cmd/bank/bank.rb b/data/plugins/cmd/bank/bank.rb index 6c28fa91..3b210064 100644 --- a/data/plugins/cmd/bank/bank.rb +++ b/data/plugins/cmd/bank/bank.rb @@ -1,8 +1,6 @@ require 'java' -java_import 'org.apollo.game.model.inter.bank.BankUtils' - # Opens the player's bank. on :command, :bank, RIGHTS_ADMIN do |player, command| - BankUtils.open_bank(player) + player.open_bank end \ No newline at end of file diff --git a/data/plugins/consumables/food.rb b/data/plugins/consumables/food.rb index 025b0ea2..cfbe600b 100644 --- a/data/plugins/consumables/food.rb +++ b/data/plugins/consumables/food.rb @@ -4,57 +4,91 @@ java_import 'org.apollo.game.model.Animation' java_import 'org.apollo.game.model.entity.Skill' java_import 'org.apollo.game.model.entity.Player' -EAT_FOOD_SOUND = 317 +private -# Todo support other foods (e.g. pizza) +EAT_FOOD_SOUND = 317 # Represents an edible piece of food, such as bread or fish. class Food < Consumable - def initialize(name, id, restoration) + def initialize(name, id, restoration, replace) super(name, id, EAT_FOOD_SOUND) @restoration = restoration + @replace = replace end # Restore the appropriate amount of hitpoints when consumed. def consume(player) - hitpoints = player.skill_set.skill(HITPOINTS_SKILL_ID) + hitpoints = player.skill_set.skill(Skill::HITPOINTS) new_curr = [ hitpoints.current_level + @restoration, hitpoints.maximum_level ].min - player.skill_set.set_skill(HITPOINTS_SKILL_ID, Skill.new(hitpoints.experience, new_curr, hitpoints.maximum_level)) + player.inventory.add(@replace) unless (@replace == -1) + player.send_message("You eat the #{name}.", true) + player.send_message("It heals some health.", true) if new_curr == hitpoints + + player.skill_set.set_skill(Skill::HITPOINTS, Skill.new(hitpoints.experience, new_curr, hitpoints.maximum_level)) end end # Appends a food item to the list of consumables. -def append_food(hash) +def food(hash) raise 'Hash must contain a name, id, and a restoration value.' unless (hash.has_keys?(:name, :id, :restoration)) - name = hash[:name]; id = hash[:id]; restoration = hash[:restoration] + name = hash[:name]; id = hash[:id]; restoration = hash[:restoration]; replace = hash[:replace] || -1; @delay = hash[:delay] || 3 - append_consumable(Food.new(name, id, restoration)) + append_consumable(Food.new(name, id, restoration, replace)) end -append_food :name => :bread, :id => 2309, :restoration => 4 -append_food :name => :cooked_meat, :id => 2142, :restoration => 3 -append_food :name => :cooked_chicken, :id => 2140, :restoration => 3 -append_food :name => :ugthanki_meat, :id => 1861, :restoration => 3 +food :name => :bread, :id => 2309, :restoration => 4 +food :name => :cooked_meat, :id => 2142, :restoration => 3 +food :name => :cooked_chicken, :id => 2140, :restoration => 3 +food :name => :ugthanki_meat, :id => 1861, :restoration => 3 -append_food :name => :anchovies, :id => 319, :restoration => 1 -append_food :name => :shrimps, :id => 315, :restoration => 3 -append_food :name => :sardine, :id => 325, :restoration => 3 -append_food :name => :herring, :id => 347, :restoration => 5 -append_food :name => :mackeral, :id => 355, :restoration => 5 -append_food :name => :trout, :id => 333, :restoration => 7 -append_food :name => :cod, :id => 339, :restoration => 7 -append_food :name => :pike, :id => 351, :restoration => 8 -append_food :name => :salmon, :id => 329, :restoration => 9 -append_food :name => :tuna, :id => 361, :restoration => 10 -append_food :name => :lobster, :id => 379, :restoration => 12 -append_food :name => :bass, :id => 365, :restoration => 13 -append_food :name => :swordfish, :id => 373, :restoration => 14 -append_food :name => :monkfish, :id => 7946, :restoration => 16 -append_food :name => :shark, :id => 385, :restoration => 20 -append_food :name => :sea_turtle, :id => 397, :restoration => 21 -append_food :name => :manta_ray, :id => 391, :restoration => 22 \ No newline at end of file +food :name => :anchovies, :id => 319, :restoration => 1 +food :name => :shrimps, :id => 315, :restoration => 3 +food :name => :sardine, :id => 325, :restoration => 3 +food :name => :herring, :id => 347, :restoration => 5 +food :name => :mackeral, :id => 355, :restoration => 5 +food :name => :trout, :id => 333, :restoration => 7 +food :name => :cod, :id => 339, :restoration => 7 +food :name => :pike, :id => 351, :restoration => 8 +food :name => :salmon, :id => 329, :restoration => 9 +food :name => :tuna, :id => 361, :restoration => 10 +food :name => :lobster, :id => 379, :restoration => 12 +food :name => :bass, :id => 365, :restoration => 13 +food :name => :swordfish, :id => 373, :restoration => 14 +food :name => :monkfish, :id => 7946, :restoration => 16 +food :name => :shark, :id => 385, :restoration => 20 +food :name => :sea_turtle, :id => 397, :restoration => 21 +food :name => :manta_ray, :id => 391, :restoration => 22 + +food :name => :cake, :id => 1891, :restoration => 4, :replace => 1893 +food :name => :cake, :id => 1893, :restoration => 4, :replace => 1895 +food :name => :cake, :id => 1895, :restoration => 4 + +food :name => :chocolate_cake, :id => 1897, :restoration => 5, :replace => 1899 +food :name => :chocolate_cake, :id => 1899, :restoration => 5, :replace => 1901 +food :name => :chocolate_cake, :id => 1901, :restoration => 5 + +food :name => :plain_pizza, :id => 2289, :restoration => 7, :replace => 2291 +food :name => :plain_pizza, :id => 2291, :restoration => 7 + +food :name => :meat_pizza, :id => 2293, :restoration => 8, :replace => 2295 +food :name => :meat_pizza, :id => 2295, :restoration => 8 + +food :name => :anchovy_pizza, :id => 2297, :restoration => 9, :replace => 2299 +food :name => :anchovy_pizza, :id => 2299, :restoration => 9 + +food :name => :pineapple_pizza, :id => 2301, :restoration => 11, :replace => 2303 +food :name => :pineapple_pizza, :id => 2303, :restoration => 11 + +food :name => :redberry_pie, :id => 2325, :restoration => 5, :replace => 2333, :delay => 1 +food :name => :redberry_pie, :id => 2333, :restoration => 5, :delay => 1 + +food :name => :meat_pie, :id => 2327, :restoration => 6, :replace => 2331, :delay => 1 +food :name => :meat_pie, :id => 2331, :restoration => 6, :delay => 1 + +food :name => :apple_pie, :id => 2323, :restoration => 7, :replace => 2335, :delay => 1 +food :name => :apple_pie, :id => 2335, :restoration => 7, :delay => 1 diff --git a/data/plugins/consumables/plugin.xml b/data/plugins/consumables/plugin.xml index 1017d547..50f2ea90 100644 --- a/data/plugins/consumables/plugin.xml +++ b/data/plugins/consumables/plugin.xml @@ -6,13 +6,12 @@ Adds support for consumables (e.g. potions, food). Major + Ryley - - skill-herblore - + \ No newline at end of file diff --git a/data/plugins/consumables/potions.rb b/data/plugins/consumables/potions.rb index ed27a5ce..71505929 100644 --- a/data/plugins/consumables/potions.rb +++ b/data/plugins/consumables/potions.rb @@ -2,13 +2,23 @@ require 'java' java_import 'org.apollo.game.model.entity.Skill' -DRINK_POTION_SOUND = 334 +private + +module Constants + + # The sound made when drinking a potion. + DRINK_POTION_SOUND = 334 + + # The id of an empty vial. + EMPTY_VIAL_ID = 229 + +end # A drinkable potion. class Potion < Consumable def initialize(id, name, doses) - super(name, id, DRINK_POTION_SOUND) + super(name, id, Constants::DRINK_POTION_SOUND) @doses = doses end @@ -21,7 +31,7 @@ class Potion < Consumable player.send_message("You have #{ @doses.length - index } dose#{ "s" unless index == 3 } of potion left.", true) else player.send_message('You drink the last of your potion.', true) - player.inventory.add(EMPTY_VIAL_ID) + player.inventory.add(Constants::EMPTY_VIAL_ID) end drink(player) diff --git a/data/plugins/dialogue/dialogue.rb b/data/plugins/dialogue/dialogue.rb index 301cf771..ffe51c9a 100644 --- a/data/plugins/dialogue/dialogue.rb +++ b/data/plugins/dialogue/dialogue.rb @@ -7,26 +7,65 @@ java_import 'org.apollo.game.message.impl.SetWidgetNpcModelMessage' java_import 'org.apollo.game.message.impl.SetWidgetPlayerModelMessage' java_import 'org.apollo.game.message.impl.SetWidgetTextMessage' -# Defines a dialogue, with the specified name and block. -def dialogue(name, &block) - raise 'Dialogues must have a name and block.' if (name.nil? || block.nil?) +# The map of conversation names to Conversations. +CONVERSATIONS = {} - dialogue = Dialogue.new(name) - dialogue.instance_eval(&block) - dialogue.wrap - DIALOGUES[name] = dialogue + +# Declares a conversation. +def conversation(name, &block) + conversation = Conversation.new(name) + conversation.instance_eval(&block) + + raise "Conversation named #{name} already exists." if CONVERSATIONS.has_key?(name) + CONVERSATIONS[name] = conversation end -# Defines an opening (i.e. conversation starter) dialogue, which hooks into the chain. -# Allows for a lambda prerequisite to be passed, which takes one argument the player; if the prerequisite evaluates to false, the dialogue will not be opened. -def opening_dialogue(name, prerequisite=nil, &block) - dialogue = dialogue(name, &block) - npc = dialogue.npc - raise 'Npc cannot be null when opening a dialogue.' if npc.nil? +# A conversation held between two entities. +class Conversation - on :message, :first_npc_action, npc do |ctx, player, event| - player.open_dialogue(name) if (prerequisite.nil? || prerequisite.call(player)) + # Creates the Conversation. + def initialize(name) + @dialogues = {} + @starters = [] + @name = name end + + # Defines a dialogue, with the specified name and block. + def dialogue(name, &block) + raise 'Dialogues must have a name and block.' if (name.nil? || block.nil?) + + dialogue = Dialogue.new(name, self) + dialogue.instance_eval(&block) + dialogue.wrap + + raise "Conversations #{@name} already has a dialogue named #{name}." if @dialogues.has_key?(name) + @dialogues[name] = dialogue + + if ((@dialogues.empty? || dialogue.has_precondition?) && dialogue.type == :npc_speech) + npc = dialogue.npc + raise 'Npc cannot be null when opening a dialogue.' if npc.nil? + @starters << dialogue + + on :message, :first_npc_action do |ctx, player, event| + if npc == $world.npc_repository.get(event.index).id + @starters.each do |start| + if dialogue.precondition(player) + send_dialogue(player, dialogue) + ctx.break_handler_chain() + break + end + end + end + end + end + end + + # Gets part of a conversation (i.e. a dialogue). + def part(name) + raise "Conversation #{@name} does not contain a dialogue called #{name}." unless @dialogues.has_key?(name) + @dialogues[name] + end + end # Declares an emote, with the specified name and id. @@ -35,12 +74,26 @@ def declare_emote(name, id) end +# Sends the specified dialogue. +def send_dialogue(player, dialogue) + type = dialogue.type + action = dialogue.action + action.call(player) unless action.nil? + + case type + when :message_with_item then send_item_dialogue(player, dialogue) + when :message_with_model then send_model_dialogue(player, dialogue) + when :npc_speech then send_npc_dialogue(player, dialogue) + when :options then send_options_dialogue(player, dialogue) + when :player_speech then send_player_dialogue(player, dialogue) + when :text + if dialogue.has_continue? then send_text_dialogue(player, dialogue) else send_statement_dialogue(player, dialogue) end + else raise "Unrecognised dialogue type #{type}." + end +end private -# The hash of dialogue names to dialogues. -DIALOGUES = {} - # The hash of emote names to ids. EMOTES = {} @@ -50,8 +103,11 @@ MAXIMUM_LINE_COUNT = 4 # The maximum amount of options that can be displayed on a dialogue. MAXIMUM_OPTION_COUNT = 5 -# The maximum width of a line, in characters. -MAXIMUM_LINE_WIDTH = 55 +# The maximum width of a line, in pixels, for a dialogue with media. +MAXIMUM_MEDIA_LINE_WIDTH = 350 + +# The maximum width of a line, in pixels, for a dialogue with no media. +MAXIMUM_LINE_WIDTH = 430 # The possible types of a dialogue. DIALOGUE_TYPES = [ :message_with_item, :message_with_model, :npc_speech, :options, :player_speech, :text ] @@ -61,12 +117,19 @@ class Dialogue attr_reader :emote, :name, :media, :options, :text, :title, :type # Initializes the Dialogue. - def initialize(name) + def initialize(name, conversation) @name = name.to_s + @conversation = conversation @text = [] @options = [] end + # An action that is executed when the dialogue is displayed. + def action(&block) + @action = block unless block.nil? + @action + end + # Closes the dialogue interface when the player clicks the 'Click here to continue...' text. def close continue(:close => true) @@ -77,16 +140,41 @@ class Dialogue raise 'Cannot add a continue event on a dialogue with options.' unless @options.size.zero? raise 'Must declare either a type or a block for a continue event.' if (type.nil? && block.nil?) - @options << (block.nil? ? get_next_dialogue(type) : block) + action = get_next_dialogue(type) unless type.nil? + @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) - raise 'Can only perform an emote on :player_speech or :npc_speech dialogues.' unless [ :npc_speech, :player_speech ].include?(@type) - @emote = EMOTES[emote] if emote.kind_of?(Symbol) + unless emote.nil? + raise 'Can only perform an emote on :player_speech or :npc_speech dialogues.' unless [ :npc_speech, :player_speech ].include?(@type) + @emote = emote.kind_of?(Symbol) ? EMOTES[emote] : emote + end + @emote end + # Returns whether or not this Dialogue has a continue option. + def has_continue? + !@options.empty? + end + + # Returns whether or not this dialogue has a precondition. + def has_precondition? + !@precondition.nil? + end + # Gets the media of this dialogue. def media() case @type @@ -120,8 +208,10 @@ class Dialogue # Sets the id of the npc displayed. def npc(npc=nil) - raise 'Can only display an npc on :npc_speech dialogues.' unless @type == :npc_speech - @npc = lookup_npc(npc) unless npc.nil? + unless npc.nil? + raise 'Can only display an npc on :npc_speech dialogues.' unless @type == :npc_speech + @npc = lookup_npc(npc) + end @npc end @@ -136,15 +226,18 @@ class Dialogue # Gets the array of options. def options - @options.dup + @options.dup + end + + # Sets the precondition of this dialogue. + def precondition(player=nil, &block) + @precondition = block unless block.nil? + @precondition.call(player) unless player.nil? end # Appends a message to the text list. def text(*message) - unless message.nil? - @text.concat(message) - end - + @text.concat(message) unless message.nil? @text end @@ -165,47 +258,44 @@ class Dialogue end # Wraps text in this Dialogue, inserting extra Dialogues in the chain if necessary. - def wrap - lines = [] + def wrap # TODO redo this next if @type == :options + lines = [] + maximum_width = (@type == :text) ? MAXIMUM_LINE_WIDTH : MAXIMUM_MEDIA_LINE_WIDTH + maximum_lines = MAXIMUM_LINE_COUNT - text = @text[0] - segments = []# text.chars.each_slice(MAXIMUM_LINE_WIDTH).map(&:join) # Split text into array of strings with length <= 60. - previous = 0; index = MAXIMUM_LINE_WIDTH + text = @text.first + segments = [] + index = 0; width = 0; space = 0 while index < text.length - index -= 1 until text[index] == ' ' - segments << text[previous..index] - previous = index - index += MAXIMUM_LINE_WIDTH - end - segments << text[previous..text.length] + char = text[index] + space = index if char == ' ' + width += get_width(char) + index += 1 - if (segments.size <= MAXIMUM_LINE_COUNT) + if (width >= maximum_width) + segments << text[0..space] + text = text[(space + 1)..-1] + width = index = space = 0 + end + end + + segments << text + + if (segments.size <= maximum_lines) lines.concat(segments) - @text = @text.drop(1) + @text = @text[1..-1] insert_copy(@text) if @text.size > 0 else - remaining = MAXIMUM_LINE_COUNT - segments.size - lines.concat(segments.first(remaining)) - insert_copy(segments.drop(remaining).join().concat(@text.drop(1))) + lines.concat(segments.first(maximum_lines)) + segments = [ segments.drop(maximum_lines).join() ] + insert_copy(segments << @text[1..-1].join()) end @text = lines 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 - private # Inserts a copy of this Dialogue into the chain, but with different text. @@ -217,22 +307,20 @@ class Dialogue index ||= -1 name = "#{name[0..index]}-auto-inserted-#{id}" - dialogue = Dialogue.new(name) + dialogue = Dialogue.new(name, @conversation) dialogue.copy_from(self, text.dup) dialogue.wrap() - DIALOGUES[name] = dialogue @options[0] = ->(player) { send_dialogue(player, dialogue) } end # Decodes the next dialogue interface from the hash, returning a proc. - def get_next_dialogue(hash) - hash.keys.each do |key| + def get_next_dialogue(hash) # TODO rename + hash.each_pair do |key, value| case key - when :close - return ->(player) { player.send(CloseInterfaceMessage.new) } - when :dialogue - return ->(player) { send_dialogue(player, lookup_dialogue(hash[key])) } + when :disabled then return ->(player) { } + when :close then return ->(player) { player.send(CloseInterfaceMessage.new) } + when :dialogue then return ->(player) { send_dialogue(player, @conversation.part(value)) } else raise "Unrecognised dialogue continue type #{key}." end end @@ -240,41 +328,11 @@ class Dialogue end -# The existing Player class. -class Player +# The dialogue interface ids for dialogues that only display text, but with no 'Click here to continue...' message. +STATEMENT_DIALOGUE_IDS = [ 12788, 12790, 12793, 12797, 6179 ] # TODO - # Opens the dialogue with the specified name. - def open_dialogue(name) - dialogue = lookup_dialogue(name) - send_dialogue(self, dialogue) - end - -end - - - -# Gets a Dialogue using the name it was registered with. -def lookup_dialogue(name) - dialogue = DIALOGUES[name] - raise "No dialogue named #{name.to_s}." if dialogue.nil? - - dialogue -end - -# Sends the specified dialogue. -def send_dialogue(player, dialogue) - type = dialogue.type - - case type - when :message_with_item then send_item_dialogue(player, dialogue) - when :message_with_model then send_model_dialogue(player, dialogue) - when :npc_speech then send_npc_dialogue(player, dialogue) - when :options then send_options_dialogue(player, dialogue) - when :player_speech then send_player_dialogue(player, dialogue) - when :text then send_text_dialogue(player, dialogue) - else raise "Unrecognised dialogue type #{type}." - end -end +# The dialogue interface ids for dialogues that display an item and text, ordered by line count. +ITEM_DIALOGUE_IDS = [ 306, 310, 315, 321 ] # The dialogue interface ids for dialogues that only display text, ordered by line count. TEXT_DIALOGUE_IDS = [ 356, 359, 363, 368, 374 ] @@ -288,6 +346,21 @@ NPC_DIALOGUE_IDS = [ 4882, 4887, 4893, 4900 ] # The dialogue interface ids for option dialogues, ordered by (option_count - 1) OPTIONS_DIALOGUE_IDS = [ 2459, 2469, 2480, 2492 ] + + +## TODO separate this into different Dialogue types ## + + +# Sends a dialogue displaying only text, with no 'Click here to continue...' button. +def send_statement_dialogue(player, dialogue) + text = dialogue.text + dialogue_id = STATEMENT_DIALOGUE_IDS[text.size] + + set_text(player, dialogue_id + 1, dialogue.title) + text.each_with_index { |line, index| set_text(player, dialogue_id + 2 + index, line) } + player.interface_set.open_dialogue_overlay(dialogue_id) +end + # Sends a dialogue displaying only text. def send_text_dialogue(player, dialogue) title = dialogue.title @@ -296,7 +369,7 @@ end # Sends a dialogue displaying the player's head. def send_player_dialogue(player, dialogue) - send_generic_dialogue(player, dialogue, PLAYERS_DIALOGUE_IDS, ->(id) { SetWidgetPlayerModelMessage.new(id + 1) }) + send_generic_dialogue(player, dialogue, player.username, PLAYER_DIALOGUE_IDS, ->(id) { SetWidgetPlayerModelMessage.new(id + 1) }) end # Sends a dialogue displaying the head of an npc. @@ -308,7 +381,6 @@ def send_npc_dialogue(player, dialogue) send_generic_dialogue(player, dialogue, name, NPC_DIALOGUE_IDS, ->(id) { SetWidgetNpcModelMessage.new(id + 1, npc)}) end - # Sends a dialogue displaying an event. def send_generic_dialogue(player, dialogue, title, ids, event=nil) text = dialogue.text @@ -317,8 +389,8 @@ def send_generic_dialogue(player, dialogue, title, ids, event=nil) set_text(player, dialogue_title_id(dialogue_id), title) - text.each_index { |index| set_text(player, dialogue_text_id(dialogue_id, index), text[index]) } - player.interface_set.open_dialogue(ContinueDialogueAdapter.new(player, dialogue.options[0]), dialogue_id) # TODO listener!!! + text.each_with_index { |line, index| set_text(player, dialogue_text_id(dialogue_id, index), line) } + player.interface_set.open_dialogue(ContinueDialogueAdapter.new(player, dialogue.options[0]), dialogue_id) end @@ -334,8 +406,8 @@ def send_options_dialogue(player, dialogue) question = dialogue.title set_text(player, dialogue_question_id(dialogue_id), question) - text.each_index { |index| set_text(player, dialogue_option_id(dialogue_id, index), text[index]) } - player.interface_set.open_dialogue(OptionDialogueAdapter.new(player, options), dialogue_id) # TODO listener!!! + text.each_with_index { |line, index| set_text(player, dialogue_option_id(dialogue_id, index), line) } + player.interface_set.open_dialogue(OptionDialogueAdapter.new(player, options), dialogue_id) end @@ -344,7 +416,7 @@ class ContinueDialogueAdapter < DialogueAdapter # Creates the ContinueDialogueAdadpter. def initialize(player, continue) - super() + super() @player = player @continue = continue end @@ -362,7 +434,7 @@ class OptionDialogueAdapter < DialogueAdapter # Creates the OptionDialogueAdadpter. def initialize(player, options) - super() + super() @player = player @options = options.dup end @@ -421,6 +493,7 @@ GLYPH_SPACING = [ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 13, 6, 8, 8, 8, 8, 4, 4, 5, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ] +# Gets the width of a single character. def get_width(char) - + return GLYPH_SPACING[char.ord] end \ No newline at end of file diff --git a/data/plugins/entity/attributes/attributes.rb b/data/plugins/entity/attributes/attributes.rb index 161ac917..370593e2 100644 --- a/data/plugins/entity/attributes/attributes.rb +++ b/data/plugins/entity/attributes/attributes.rb @@ -1,6 +1,8 @@ require 'java' -java_import 'org.apollo.game.model.entity.Entity' +java_import 'org.apollo.game.model.entity.Mob' +java_import 'org.apollo.game.model.entity.Npc' +java_import 'org.apollo.game.model.entity.Player' java_import 'org.apollo.game.model.entity.attr.Attribute' java_import 'org.apollo.game.model.entity.attr.AttributeDefinition' java_import 'org.apollo.game.model.entity.attr.AttributeMap' @@ -10,48 +12,51 @@ java_import 'org.apollo.game.model.entity.attr.BooleanAttribute' java_import 'org.apollo.game.model.entity.attr.NumericalAttribute' java_import 'org.apollo.game.model.entity.attr.StringAttribute' -class Entity + +# Declares an attribute and adds its definition. +def declare_attribute(name, default, persistence=:transient) + raise "Attribute #{name} clashes with an existing variable." if (Player.method_defined?(name) || Mob.method_defined?(name) || Npc.method_defined?(name)) + AttributeMap::define(name.to_s, AttributeDefinition.new(default, get_persistence(persistence), get_type(default))) +end + + +private + +# The existing Mob class. +class Mob - # Overrides method_missing + # Overrides method_missing to implement the functionality. def method_missing(symbol, *args) name = symbol.to_s.strip if name[-1] == "=" raise "Expected argument count of 1, received #{args.length}" unless args.length == 1 - puts name - name = name[0...-1].strip # Drop the equals + name = name[0...-1].strip # Drop the equals and trim whitespace. set_attribute(name, to_attribute(args[0])) - elsif AttributeMap::get_definition(name) == nil + elsif AttributeMap::get_definition(name).nil? super(symbol, *args) else - attribute = get_attribute(name); definition = AttributeMap::get_definition(name) - value = attribute == nil ? definition.default : attribute.value - - return definition.type == AttributeType::SYMBOL ? value.to_sym : value - end - end - - # Gets the appropriate attribute for the specified value. - private - def to_attribute(value) - case value - when String, Symbol then return StringAttribute.new(value.to_s, value.is_a?(Symbol)) - when Integer, Float then return NumericalAttribute.new(value) - when TrueClass, FalseClass then return BooleanAttribute.new(value) - else raise "Undefined attribute type #{value.class}." + attribute = get_attribute(name) + value = attribute.value + return (attribute.type == AttributeType::SYMBOL) ? value.to_sym : value end end end -# Declares an attribute and adds its definition. -def declare_attribute(name, default, persistence=:transient) - AttributeMap::add_definition(name.to_s, AttributeDefinition.new(default, get_persistence(persistence), get_type(default))) + + # Gets the appropriate attribute for the specified value. +def to_attribute(value) + case value + when String, Symbol then return StringAttribute.new(value.to_s, value.is_a?(Symbol)) + when Integer, Float then return NumericalAttribute.new(value) + when TrueClass, FalseClass then return BooleanAttribute.new(value) + else raise "Undefined attribute type #{value.class}." + end end # Gets the attribute type of the specified value. -private def get_type(value) case value when String then return AttributeType::STRING @@ -65,7 +70,7 @@ end # Gets the Persistence type of the specified value. def get_persistence(persistence) - raise "Undefined persistence type #{persistence}." unless persistence == :serialized || persistence == :transient + raise "Undefined persistence type #{persistence}." unless [ :persistent, :transient ].include?(persistence) - return persistence == :serialized ? AttributePersistence::SERIALIZED : AttributePersistence::TRANSIENT + return (persistence == :persistent) ? AttributePersistence::PERSISTENT : AttributePersistence::TRANSIENT end \ No newline at end of file diff --git a/data/plugins/player-action/login.rb b/data/plugins/player-action/login.rb new file mode 100644 index 00000000..ee2c78d3 --- /dev/null +++ b/data/plugins/player-action/login.rb @@ -0,0 +1,6 @@ +java_import 'org.apollo.game.model.entity.Player' + +on :login do |event, player| + show_action(player, TRADE_ACTION) + show_action(player, FOLLOW_ACTION) +end \ No newline at end of file diff --git a/data/plugins/player-action/player-action.rb b/data/plugins/player-action/player-action.rb new file mode 100644 index 00000000..46144a49 --- /dev/null +++ b/data/plugins/player-action/player-action.rb @@ -0,0 +1,59 @@ +require 'java' + +java_import 'org.apollo.game.message.impl.SetPlayerActionMessage' +java_import 'org.apollo.game.model.entity.Player' + +class PlayerAction + attr_reader :slot, :primary, :name + + def initialize(slot, primary, name) + index = [ :first, :second, :third, :fourth, :fifth ].find_index(slot) + raise "Unsupport action slot #{slot}." if index.nil? + + @slot = index + @primary = primary + @name = name + end + +end + +ATTACK_ACTION = PlayerAction.new(:third, true, 'Attack') +CHALLENGE_ACTION = PlayerAction.new(:third, true, 'Challenge') +TRADE_ACTION = PlayerAction.new(:fourth, true, 'Trade with') +FOLLOW_ACTION = PlayerAction.new(:fifth, true, 'Follow') + +# Shows multiple context menu action for the specified player +def show_actions(player, *actions) + raise 'Must specify at least one action to show' if actions.nil? + + actions.each do |action| + player.add_action(action) + player.send(SetPlayerActionMessage.new(action.name, action.slot, action.primary)) + end +end + +# Shows a single context menu action for the specified player +def show_action(player, action) + show_actions(player, action) +end + +# Hides a context menu action for the specified player +def hide_action(player, action) + show_action(player, PlayerAction.new('null', action.slot, action.primary)) +end + +class Player + + def actions + @actions ||= {} + end + + def add_action(action) + actions[action.slot] = action.name + end + + def has_action(action) + return actions[action.slot] == action.name + end + +end \ No newline at end of file diff --git a/data/plugins/player-action/plugin.xml b/data/plugins/player-action/plugin.xml new file mode 100644 index 00000000..82a7d15f --- /dev/null +++ b/data/plugins/player-action/plugin.xml @@ -0,0 +1,15 @@ + + + player-action + 1 + Player actions + Manages player right click actions + + Ryley + + + + + + + \ No newline at end of file diff --git a/data/plugins/skill/herblore/potion.rb b/data/plugins/skill/herblore/potion.rb index 1603f0a1..b99415be 100644 --- a/data/plugins/skill/herblore/potion.rb +++ b/data/plugins/skill/herblore/potion.rb @@ -7,6 +7,8 @@ java_import 'org.apollo.game.model.def.ItemDefinition' java_import 'org.apollo.game.model.inter.EnterAmountListener' java_import 'org.apollo.game.model.inter.dialogue.DialogueAdapter' +private + WATER_VIAL_ID = 227 EMPTY_VIAL_ID = 229 diff --git a/data/plugins/skill/prayer/plugin.xml b/data/plugins/skill/prayer/plugin.xml index 3d6a402c..edbeb441 100644 --- a/data/plugins/skill/prayer/plugin.xml +++ b/data/plugins/skill/prayer/plugin.xml @@ -1,7 +1,7 @@ skill-prayer - 1 + 0.9 Prayer Adds the Prayer skill. @@ -10,6 +10,9 @@ + - + + attributes + \ No newline at end of file diff --git a/data/plugins/skill/prayer/prayers.rb b/data/plugins/skill/prayer/prayers.rb new file mode 100644 index 00000000..788b843e --- /dev/null +++ b/data/plugins/skill/prayer/prayers.rb @@ -0,0 +1,89 @@ + +java_import 'org.apollo.game.message.impl.ConfigMessage' + +# Declares the active prayer attribute. +declare_attribute(:active_prayer, -1, :persistent) + + +# The hash of button ids to prayers. +PRAYERS = {} + +# Intercept the ButtonMessage to toggle a prayer. +on :message, :button do |ctx, player, message| + button = message.widget_id + prayer = PRAYERS[button] + + unless prayer.nil? + if (prayer.level > player.skill_set.get_maximum_level(Skill::PRAYER)) + update_setting(player, prayer, :off) + next + end + player.send_message("after level check") + + previous = player.active_prayer + + unless previous == -1 + update_setting(player, PRAYERS[previous], :off) + end + + if previous != button + player.send_message("Previous: #{previous}, new: #{button}.") + update_setting(player, prayer, :on) + player.active_prayer = button + end + end +end + +private + +# A Prayer that can be activated by a player. +class Prayer + attr_reader :name, :level, :button, :setting, :drain + + def initialize(name, level, button, setting, drain) + @name = name + @level = level + @button = button + @setting = setting + @drain = drain + end + +end + +def update_setting(player, prayer, state) + value = (state == :on) ? 1 : 0 + player.send_message("Toggling prayer #{prayer.name}, state: #{state}.") + player.send(ConfigMessage.new(prayer.setting, value)) +end + +# Appends a Prayer to the hash. +def append_prayer(properties) + raise 'Error: prayer properties hash must contain a name, level, button, setting, and drain.' unless properties.has_keys?(:name, :level, :button, :setting, :drain) + + button = properties[:button] + PRAYERS[button] = Prayer.new(properties[:name], properties[:level], button, properties[:setting], properties[:drain]) +end + +# Don't deal with the actual effect here to avoid mess (TODO do it, but with attributes?). +append_prayer name: :thick_skin, level: 1, button: 5609, setting: 83, drain: 0.01 +append_prayer name: :burst_of_strength, level: 4, button: 5610, setting: 84, drain: 0.01 +append_prayer name: :clarity_of_thought, level: 7, button: 5611, setting: 85, drain: 0.01 +append_prayer name: :rock_skin, level: 10, button: 5612, setting: 86, drain: 0.04 +append_prayer name: :superhuman_strength, level: 13, button: 5613, setting: 87, drain: 0.04 +append_prayer name: :improved_reflexes, level: 16, button: 5614, setting: 88, drain: 0.04 + +append_prayer name: :rapid_restore, level: 19, button: 5615, setting: 89, drain: 0.01 +append_prayer name: :rapid_heal, level: 22, button: 5615, setting: 90, drain: 0.01 +append_prayer name: :protect_item, level: 25, button: 5617, setting: 91, drain: 0.01 + +append_prayer name: :steel_skin, level: 28, button: 5618, setting: 92, drain: 0.1 +append_prayer name: :ultimate_strength, level: 31, button: 5619, setting: 93, drain: 0.1 +append_prayer name: :incredible_reflexes, level: 34, button: 5620, setting: 94, drain: 0.1 + +append_prayer name: :protect_from_magic, level: 37, button: 5621, setting: 95, drain: 0.15 +append_prayer name: :protect_from_missiles, level: 40, button: 5622, setting: 96, drain: 0.15 +append_prayer name: :protect_from_melee, level: 43, button: 5633, setting: 97, drain: 0.15 + +append_prayer name: :retribution, level: 46, button: 683, setting: 98, drain: 0.15 +append_prayer name: :redemption, level: 49, button: 684, setting: 99, drain: 0.15 +append_prayer name: :smite, level: 52, button: 685, setting: 100, drain: 0.2 \ No newline at end of file diff --git a/data/plugins/skill/runecraft/tiara.rb b/data/plugins/skill/runecraft/tiara.rb index 8a77490d..9e1559f9 100644 --- a/data/plugins/skill/runecraft/tiara.rb +++ b/data/plugins/skill/runecraft/tiara.rb @@ -44,22 +44,20 @@ end # Appends a tiara to the list. def append_tiara(hash) - raise 'Hash must contain a tiara id, altar id, talisman id, a bitshift number, and experience.' unless hash.has_key?(:tiara_id) && hash.has_key?(:altar) && hash.has_key?(:talisman) && hash.has_key?(:bitshift) && hash.has_key?(:experience) + raise 'Hash must contain a tiara id, altar id, talisman id, a bitshift number, and experience.' unless hash.has_keys?(:altar, :bitshift, :experience, :talisman, :tiara_id) tiara_id = hash[:tiara_id]; altar = hash[:altar]; talisman = hash[:talisman]; bitshift = hash[:bitshift]; experience = hash[:experience] TIARAS_BY_TALISMAN[talisman] = TIARAS_BY_ID[tiara_id] = TIARAS_BY_ALTAR[altar] = Tiara.new(tiara_id, altar, talisman, bitshift, experience) end # Sets the correct config upon login, if the player is wearing a tiara. -on :login do |player| +on :login do |event, player| + player = event.player hat = player.equipment.get(EquipmentConstants::HAT) - next if hat.nil? - - tiara = TIARAS_BY_ID[hat] - unless tiara.nil? - tiara.send_config - else - send_empty_config(player) + + unless hat.nil? + tiara = TIARAS_BY_ID[hat] + if tiara.nil? then send_empty_config(player) else tiara.send_config end end end @@ -67,7 +65,7 @@ end on :message, :second_object_action do |ctx, player, message| object_id = message.id tiara = TIARAS_BY_ALTAR[object_id] - return if tiara.nil? + next if tiara.nil? hat = player.equipment.get(EquipmentConstants::HAT) diff --git a/data/plugins/util/command.rb b/data/plugins/util/command.rb index 7607c062..0e86d618 100644 --- a/data/plugins/util/command.rb +++ b/data/plugins/util/command.rb @@ -1,3 +1,9 @@ +require 'java' + +java_import 'org.apollo.game.model.def.ItemDefinition' +java_import 'org.apollo.game.model.def.NpcDefinition' +java_import 'org.apollo.game.model.def.ObjectDefinition' + # Checks whether the amount of arguments provided is correct, sending the player the specified message if not. def valid_arg_length(args, length, player, message) diff --git a/src/org/apollo/Server.java b/src/org/apollo/Server.java index dc97737c..4812f475 100644 --- a/src/org/apollo/Server.java +++ b/src/org/apollo/Server.java @@ -107,18 +107,19 @@ public final class Server { * @param jagGrabAddress The JAGGRAB address to bind to. */ public void bind(SocketAddress serviceAddress, SocketAddress httpAddress, SocketAddress jagGrabAddress) { - logger.fine("Binding service listener to address: " + serviceAddress + "..."); - serviceBootstrap.bind(serviceAddress); - - logger.fine("Binding HTTP listener to address: " + httpAddress + "..."); try { - httpBootstrap.bind(httpAddress); - } catch (Throwable t) { - logger.log(Level.WARNING, "Binding to HTTP failed: client will use JAGGRAB as a fallback (not recommended)!", t); - } + logger.fine("Binding service listener to address: " + serviceAddress + "..."); + serviceBootstrap.bind(serviceAddress).sync(); - logger.fine("Binding JAGGRAB listener to address: " + jagGrabAddress + "..."); - jagGrabBootstrap.bind(jagGrabAddress); + logger.fine("Binding HTTP listener to address: " + httpAddress + "..."); + httpBootstrap.bind(httpAddress).sync(); + + logger.fine("Binding JAGGRAB listener to address: " + jagGrabAddress + "..."); + jagGrabBootstrap.bind(jagGrabAddress).sync(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Binding to a port failed: ensure apollo isn't already running.", e); + System.exit(1); + } logger.info("Ready for connections."); } diff --git a/src/org/apollo/game/GameService.java b/src/org/apollo/game/GameService.java index 31cc3730..efdbab2a 100644 --- a/src/org/apollo/game/GameService.java +++ b/src/org/apollo/game/GameService.java @@ -148,14 +148,20 @@ public final class GameService extends Service { } /** - * Registers a player (may block!). + * Registers a {@link Player} (may block!). * - * @param player The player. + * @param player The Player. + * @param session The {@link GameSession} of the Player. * @return A {@link RegistrationStatus}. */ - public RegistrationStatus registerPlayer(Player player) { + public RegistrationStatus registerPlayer(Player player, GameSession session) { synchronized (this) { - return World.getWorld().register(player); + RegistrationStatus status = World.getWorld().register(player); + if (status == RegistrationStatus.OK) { + player.setSession(session); + } + + return status; } } diff --git a/src/org/apollo/game/command/CommandListener.java b/src/org/apollo/game/command/CommandListener.java index 57eeffe6..97202fa1 100644 --- a/src/org/apollo/game/command/CommandListener.java +++ b/src/org/apollo/game/command/CommandListener.java @@ -1,7 +1,7 @@ package org.apollo.game.command; import org.apollo.game.model.entity.Player; -import org.apollo.game.model.setting.PrivilegeLevel; +import org.apollo.game.model.entity.setting.PrivilegeLevel; /** * An interface which should be implemented to listen to {@link Command}s. diff --git a/src/org/apollo/game/login/LoginDispatcher.java b/src/org/apollo/game/login/LoginDispatcher.java deleted file mode 100644 index 878fd9c8..00000000 --- a/src/org/apollo/game/login/LoginDispatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.apollo.game.login; - -import java.util.ArrayList; -import java.util.List; - -import org.apollo.game.model.entity.Player; - -/** - * A class that dispatches {@link Player}s to {@link LoginListener}s. - * - * @author Major - */ -public final class LoginDispatcher { - - /** - * A {@link List} of login listeners. - */ - private List listeners = new ArrayList<>(); - - /** - * Dispatches a player to the appropriate login listener. - * - * @param player The player. - */ - public void dispatch(Player player) { - for (LoginListener listener : listeners) { - listener.execute(player); - } - } - - /** - * Registers a listener with this dispatcher. - * - * @param listener The listener. - */ - public void register(LoginListener listener) { - listeners.add(listener); - } - -} \ No newline at end of file diff --git a/src/org/apollo/game/login/LoginListener.java b/src/org/apollo/game/login/LoginListener.java deleted file mode 100644 index 5fcc2ea4..00000000 --- a/src/org/apollo/game/login/LoginListener.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.apollo.game.login; - -import org.apollo.game.model.entity.Player; - -/** - * A class that should be extended for actions that should be executed when the player logs in. - * - * @author Major - */ -@FunctionalInterface -public interface LoginListener { - - /** - * Executes the action for this listener. - * - * @param player The player. - */ - public abstract void execute(Player player); - -} \ No newline at end of file diff --git a/src/org/apollo/game/login/LogoutDispatcher.java b/src/org/apollo/game/login/LogoutDispatcher.java deleted file mode 100644 index c7a5e3f8..00000000 --- a/src/org/apollo/game/login/LogoutDispatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.apollo.game.login; - -import java.util.ArrayList; -import java.util.List; - -import org.apollo.game.model.entity.Player; - -/** - * A class that dispatches {@link Player}s to {@link LogoutListener}s. - * - * @author Major - */ -public final class LogoutDispatcher { - - /** - * A {@link List} of logout listeners. - */ - private List listeners = new ArrayList<>(); - - /** - * Dispatches a player to the appropriate logout listener. - * - * @param player The player. - */ - public void dispatch(Player player) { - for (LogoutListener listen : listeners) { - listen.execute(player); - } - } - - /** - * Registers a listener with this dispatcher. - * - * @param listener The listener. - */ - public void register(LogoutListener listener) { - listeners.add(listener); - } - -} diff --git a/src/org/apollo/game/login/LogoutListener.java b/src/org/apollo/game/login/LogoutListener.java deleted file mode 100644 index 8a67f5ee..00000000 --- a/src/org/apollo/game/login/LogoutListener.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.apollo.game.login; - -import org.apollo.game.model.entity.Player; - -/** - * An interface that should be implemented for actions that should be executed when the player logs out. - * - * @author Major - */ -@FunctionalInterface -public interface LogoutListener { - - /** - * Executes the action for this listener. - * - * @param player The player. - */ - public abstract void execute(Player player); - -} \ No newline at end of file diff --git a/src/org/apollo/game/login/package-info.java b/src/org/apollo/game/login/package-info.java deleted file mode 100644 index b157b152..00000000 --- a/src/org/apollo/game/login/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Contains login and logout listeners. - */ -package org.apollo.game.login; \ No newline at end of file diff --git a/src/org/apollo/game/message/handler/MessageHandlerChain.java b/src/org/apollo/game/message/handler/MessageHandlerChain.java index c3c9942c..96e20db5 100644 --- a/src/org/apollo/game/message/handler/MessageHandlerChain.java +++ b/src/org/apollo/game/message/handler/MessageHandlerChain.java @@ -1,5 +1,9 @@ package org.apollo.game.message.handler; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; + import org.apollo.game.message.Message; import org.apollo.game.model.entity.Player; @@ -7,6 +11,7 @@ import org.apollo.game.model.entity.Player; * A chain of message handlers. * * @author Graham + * @author Ryley * @param The type of message handled by this chain. */ public final class MessageHandlerChain { @@ -14,7 +19,7 @@ public final class MessageHandlerChain { /** * The handlers. */ - private MessageHandler[] handlers; + private final Deque> handlers; /** * Creates the message handler chain. @@ -23,7 +28,7 @@ public final class MessageHandlerChain { */ @SafeVarargs public MessageHandlerChain(MessageHandler... handlers) { - this.handlers = handlers; + this.handlers = new ArrayDeque<>(Arrays.asList(handlers)); } /** @@ -31,36 +36,24 @@ public final class MessageHandlerChain { * * @param handler The handler. */ - @SuppressWarnings("unchecked") public void addLast(MessageHandler handler) { - MessageHandler[] old = handlers; - handlers = new MessageHandler[old.length + 1]; - System.arraycopy(old, 0, handlers, 0, old.length); - handlers[old.length] = handler; + handlers.addLast(handler); } /** - * 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. */ public void handle(Player player, M message) { - final boolean[] running = new boolean[1]; - running[0] = true; - - MessageHandlerContext ctx = new MessageHandlerContext() { - - @Override - public void breakHandlerChain() { - running[0] = false; - } - }; + MessageHandlerContext context = new MessageHandlerContext(); for (MessageHandler handler : handlers) { - handler.handle(ctx, player, message); - if (!running[0]) { + handler.handle(context, player, message); + + if (context.isBroken()) { break; } } diff --git a/src/org/apollo/game/message/handler/MessageHandlerContext.java b/src/org/apollo/game/message/handler/MessageHandlerContext.java index 8658e1fa..6660d1e2 100644 --- a/src/org/apollo/game/message/handler/MessageHandlerContext.java +++ b/src/org/apollo/game/message/handler/MessageHandlerContext.java @@ -1,15 +1,34 @@ 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 */ -public abstract class MessageHandlerContext { +public final class MessageHandlerContext { + + /** + * Denotes whether or not this handler chain is broken. + */ + private boolean broken; /** * Breaks the handler chain. */ - public abstract void breakHandlerChain(); + public void breakHandlerChain() { + broken = true; + } + + /** + * Returns whether or not this handler chain is broken. + * + * @return {@code true} if this handler chain is broken, otherwise + * {@code false}. + */ + public boolean isBroken() { + return broken; + } } \ No newline at end of file diff --git a/src/org/apollo/game/message/handler/impl/EquipItemHandler.java b/src/org/apollo/game/message/handler/impl/EquipItemHandler.java index 8142f89d..a3628e6b 100644 --- a/src/org/apollo/game/message/handler/impl/EquipItemHandler.java +++ b/src/org/apollo/game/message/handler/impl/EquipItemHandler.java @@ -17,7 +17,7 @@ import org.apollo.util.LanguageUtil; * * @author Major * @author Graham - * @author Ryley Kimmel + * @author Ryley */ public final class EquipItemHandler extends MessageHandler { diff --git a/src/org/apollo/game/message/handler/impl/PlayerDesignMessageHandler.java b/src/org/apollo/game/message/handler/impl/PlayerDesignMessageHandler.java index 96dfa71a..7e7ad3af 100644 --- a/src/org/apollo/game/message/handler/impl/PlayerDesignMessageHandler.java +++ b/src/org/apollo/game/message/handler/impl/PlayerDesignMessageHandler.java @@ -16,7 +16,6 @@ public final class PlayerDesignMessageHandler extends MessageHandler sectors.fromPosition(entity.getPosition()).addEntity(entity)); + public void listenFor(Class type, EventListener listener) { + events.putListener(type, listener); } /** @@ -348,6 +327,16 @@ public final class World { return scheduler.schedule(task); } + /** + * Submits the specified {@link Event}, passing it to the listeners.. + * + * @param event The Event. + * @return {@code true} if the Event should proceed, {@code false} if not. + */ + public boolean submit(Event event) { + return events.notify(event); + } + /** * Unregisters the specified {@link Npc}. * @@ -375,11 +364,18 @@ public final class World { Sector sector = sectors.fromPosition(player.getPosition()); sector.removeEntity(player); - - logoutDispatcher.dispatch(player); } else { logger.warning("Could not find player " + player + " to unregister!"); } } + /** + * Adds entities to sectors in the {@link SectorRepository}. + * + * @param entities The entities. + */ + private void placeEntities(Entity... entities) { + Arrays.stream(entities).forEach(entity -> sectors.fromPosition(entity.getPosition()).addEntity(entity)); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/Sector.java b/src/org/apollo/game/model/area/Sector.java index e1c35d46..3c0b3e89 100644 --- a/src/org/apollo/game/model/area/Sector.java +++ b/src/org/apollo/game/model/area/Sector.java @@ -8,7 +8,9 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.apollo.game.model.Direction; import org.apollo.game.model.Position; +import org.apollo.game.model.area.collision.CollisionMatrix; import org.apollo.game.model.entity.Entity; import org.apollo.game.model.entity.Entity.EntityType; @@ -23,7 +25,7 @@ import com.google.common.collect.ImmutableSet; public final class Sector { /** - * The width and length of a sector, in tiles. + * The width and length of a Sector, in tiles. */ public static final int SECTOR_SIZE = 8; @@ -33,20 +35,25 @@ public final class Sector { private static final int DEFAULT_SET_SIZE = 2; /** - * The sector coordinates of this sector. + * The SectorCoordinates of this Sector. */ private final SectorCoordinates coordinates; /** - * A map of positions to entities in that position. + * The Map of Positions to Entities in that Position. */ private final Map> entities = new HashMap<>(); /** - * A list of listeners registered to this sector. + * A List of SectorListeners registered to this Sector. */ private final List listeners = new ArrayList<>(); + /** + * The CollisionMatrix. + */ + private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SECTOR_SIZE, SECTOR_SIZE); + /** * Creates a new sector. * @@ -67,28 +74,29 @@ public final class Sector { } /** - * Adds a {@link Entity} from to sector. Note that this does not spawn the entity, or do any other action other than + * Adds a {@link Entity} from to sector. Note that this does not spawn the Entity, or do any other action other than * register it to this sector. * - * @param entity The entity. - * @throws IllegalArgumentException If the entity does not belong in this sector. + * @param entity The Entity. + * @throws IllegalArgumentException If the Entity does not belong in this sector. */ public void addEntity(Entity entity) { Position position = entity.getPosition(); checkPosition(position); - Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); + Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); local.add(entity); + notifyListeners(entity, SectorOperation.ADD); } /** - * Checks if this sector contains the specified entity. + * Checks if this sector contains the specified Entity. *

* This method operates in constant time. * - * @param entity The entity. - * @return {@code true} if this sector contains the entity, otherwise {@code false}. + * @param entity The Entity. + * @return {@code true} if this sector contains the Entity, otherwise {@code false}. */ public boolean contains(Entity entity) { Position position = entity.getPosition(); @@ -114,12 +122,13 @@ public final class Sector { * @return The list. */ public Set getEntities(Position position) { - return ImmutableSet.copyOf(entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE))); + Set set = entities.get(position); + return (set == null) ? ImmutableSet.of() : ImmutableSet.copyOf(set); } /** * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}. The returned - * type will be immutable. Type will be inferred from the call, so ensure that the entity type and the reference + * type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference * correspond, or this method will fail at runtime. * * @param position The {@link Position} containing the entities. @@ -127,21 +136,35 @@ public final class Sector { * @return The set of entities. */ public Set getEntities(Position position, EntityType type) { - Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); + Set local = entities.get(position); + if (local == null) { + return ImmutableSet.of(); + } @SuppressWarnings("unchecked") - Set filtered = (Set) local.stream().filter(entity -> entity.getEntityType() == type).collect(Collectors.toSet()); + Set filtered = (Set) local.stream().filter(Entity -> Entity.getEntityType() == type).collect(Collectors.toSet()); return ImmutableSet.copyOf(filtered); } /** - * Moves the {@link Entity} that was in the specified {@code old} {@link Position}, to the current position of the - * entity. - *

- * Both the {@code old} and current positions of the entity must belong to this sector. + * Gets the {@link CollisionMatrix} at the specified height level. * - * @param old The old position of the entity. - * @param entity The entity to move. + * @param height The height level. + * @return The CollisionMatrix. + */ + public CollisionMatrix getMatrix(int height) { + Preconditions.checkElementIndex(height, matrices.length, "Matrix height level must be [0, " + matrices.length + ")."); + return matrices[height]; + } + + /** + * Moves the {@link Entity} that was in the specified {@code old} {@link Position}, to the current position of the + * Entity. + *

+ * Both the {@code old} and current positions of the Entity must belong to this sector. + * + * @param old The old position of the Entity. + * @param entity The Entity to move. * @throws IllegalArgumentException If either of the positions do not belong to this sector. */ public void moveEntity(Position old, Entity entity) { @@ -175,8 +198,8 @@ public final class Sector { /** * Removes a {@link Entity} from this sector. * - * @param entity The entity. - * @throws IllegalArgumentException If the entity does not belong in this sector, or if it was never added. + * @param entity The Entity. + * @throws IllegalArgumentException If the Entity does not belong in this sector, or if it was never added. */ public void removeEntity(Entity entity) { Position position = entity.getPosition(); @@ -191,6 +214,22 @@ public final class Sector { notifyListeners(entity, SectorOperation.REMOVE); } + /** + * Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified + * coordinate pair. + * + * @param position The {@link Position} of the tile. + * @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. + */ + public boolean traversable(Position position, EntityType entity, Direction direction) { + CollisionMatrix matrix = matrices[position.getHeight()]; + int x = position.getLocalX(), y = position.getLocalY(); + + return matrix.traversable(x, y, entity, direction); + } + /** * Checks that the specified {@link Position} is included in this sector. * diff --git a/src/org/apollo/game/model/area/collision/CollisionFlag.java b/src/org/apollo/game/model/area/collision/CollisionFlag.java new file mode 100644 index 00000000..b129511b --- /dev/null +++ b/src/org/apollo/game/model/area/collision/CollisionFlag.java @@ -0,0 +1,100 @@ +package org.apollo.game.model.area.collision; + +/** + * A type of flag in a {@link CollisionMatrix}. + * + * @author Major + */ +public enum CollisionFlag { + + /** + * The walk north flag. + */ + MOB_NORTH(0), + + /** + * The walk east flag. + */ + MOB_EAST(1), + + /** + * The walk south flag. + */ + MOB_SOUTH(2), + + /** + * The walk west flag. + */ + MOB_WEST(3), + + /** + * The projectile north flag. + */ + PROJECTILE_NORTH(4), + + /** + * The projectile east flag. + */ + PROJECTILE_EAST(5), + + /** + * The projectile south flag. + */ + PROJECTILE_SOUTH(6), + + /** + * The projectile west flag. + */ + PROJECTILE_WEST(7); + + /** + * Returns an array of CollisionFlags that indicate if a Mob can traverse over a tile. + * + * @return The array of CollisionFlags. + */ + public static CollisionFlag[] mobs() { + return new CollisionFlag[] { MOB_NORTH, MOB_EAST, MOB_SOUTH, MOB_WEST }; + } + + /** + * Returns an array of CollisionFlags that indicate if a Projectile can traverse over a tile. + * + * @return The array of CollisionFlags. + */ + public static CollisionFlag[] projectiles() { + return new CollisionFlag[] { PROJECTILE_NORTH, PROJECTILE_EAST, PROJECTILE_SOUTH, PROJECTILE_WEST }; + } + + /** + * The index of the bit this flag is stored in. + */ + private final int bit; + + /** + * Creates the CollisionFlag. + * + * @param bit The index of the bit this flag is stored in. + */ + private CollisionFlag(int bit) { + this.bit = bit; + } + + /** + * Gets this CollisionFlag, as a {@code byte}. + * + * @return The value, as a {@code byte}. + */ + public byte asByte() { + return (byte) (1 << bit); + } + + /** + * Gets the index of the bit this flag is stored in. + * + * @return The index of the bit. + */ + public int getBit() { + return bit; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/collision/CollisionMatrix.java b/src/org/apollo/game/model/area/collision/CollisionMatrix.java new file mode 100644 index 00000000..075c49a8 --- /dev/null +++ b/src/org/apollo/game/model/area/collision/CollisionMatrix.java @@ -0,0 +1,241 @@ +package org.apollo.game.model.area.collision; + +import java.util.Arrays; + +import org.apollo.game.model.Direction; +import org.apollo.game.model.entity.Entity.EntityType; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; + +/** + * A 2-dimensional adjacency matrix containing tile collision data. + * + * @author Major + */ +public final class CollisionMatrix { + + /** + * Creates an array of CollisionMatrix objects, all of the specified width and length. + * + * @param count The length of the array to create. + * @param width The width of each CollisionMatrix. + * @param length The length of each CollisionMatrix. + * @return The array of CollisionMatrix objects. + */ + public static CollisionMatrix[] createMatrices(int count, int width, int length) { + CollisionMatrix[] matrices = new CollisionMatrix[count]; + Arrays.setAll(matrices, index -> new CollisionMatrix(width, length)); + 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. + */ + private final int length; + + /** + * The collision matrix, as a {@code byte} array. + */ + private final byte[] matrix; + + /** + * The width of the matrix. + */ + private final int width; + + /** + * Creates the CollisionMatrix. + * + * @param width The width of the matrix. + * @param length The length of the matrix. + */ + public CollisionMatrix(int width, int length) { + this.width = width; + this.length = length; + matrix = new byte[width * length]; + } + + /** + * Returns whether or not all of the specified {@link CollisionFlag}s are set for the specified + * coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flags The CollisionFlags. + * @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; + } + + /** + * Returns whether or not any of the specified {@link CollisionFlag}s are set for the specified + * coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flags The CollisionFlags. + * @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; + } + + /** + * Completely blocks the tile at the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + */ + public void block(int x, int y) { + set(x, y, ALL_BLOCKED); + } + + /** + * Clears (i.e. sets to {@code false}) the value of the specified {@link CollisionFlag} for the specified coordinate + * pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flag The CollisionFlag. + */ + public void clear(int x, int y, CollisionFlag flag) { + set(x, y, (byte) ~flag.asByte()); + } + + /** + * Returns whether or not the specified {@link CollisionFlag} is set for the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flag The CollisionFlag. + * @return {@code true} if the CollisionFlag is set, {@code false} if not. + */ + public boolean flagged(int x, int y, CollisionFlag flag) { + return (get(x, y) & flag.asByte()) != 0; + } + + /** + * Gets the value of the specified tile. + * + * @param x The x coordinate of the tile. + * @param y The y coordinate of the tile. + * @return The value. + */ + public int get(int x, int y) { + return matrix[indexOf(x, y)] & 0xFF; + } + + /** + * Resets the cell of the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + */ + public void reset(int x, int y) { + set(x, y, ALL_ALLOWED); + } + + /** + * Sets (i.e. sets to {@code true}) the value of the specified {@link CollisionFlag} for the specified coordinate + * pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flag The CollisionFlag. + */ + public void set(int x, int y, CollisionFlag flag) { + set(x, y, flag.asByte()); + } + + @Override + public String 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. + * + * @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. + */ + public boolean traversable(int x, int y, EntityType entity, Direction direction) { + CollisionFlag[] flags = CollisionFlag.forType(entity); + int north = 0, east = 1, south = 2, west = 3; + + switch (direction) { + case NORTH_WEST: + return any(x, y, flags[south], flags[east]); + case NORTH: + return flagged(x, y, flags[south]); + case NORTH_EAST: + return any(x, y, flags[south], flags[west]); + case EAST: + return flagged(x, y, flags[west]); + case SOUTH_EAST: + return any(x, y, flags[north], flags[west]); + case SOUTH: + return flagged(x, y, flags[north]); + case SOUTH_WEST: + return any(x, y, flags[north], flags[east]); + case WEST: + return flagged(x, y, flags[east]); + } + + throw new IllegalArgumentException("Unrecognised direction " + direction + "."); + } + + /** + * Gets the index in the matrix for the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @return The index. + * @throws ArrayIndexOutOfBoundsException If the specified coordinate pair does not fit in this matrix. + */ + private int indexOf(int x, int y) { + int index = y * width + x; + Preconditions.checkElementIndex(index, matrix.length, "Index out of bounds."); + return index; + } + + /** + * Sets the appropriate index for the specified coordinate pair to the specified value. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param value The value. + */ + private void set(int x, int y, byte value) { + matrix[indexOf(x, y)] = value; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/collision/package-info.java b/src/org/apollo/game/model/area/collision/package-info.java new file mode 100644 index 00000000..aeb5e04c --- /dev/null +++ b/src/org/apollo/game/model/area/collision/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to tile collision data. + */ +package org.apollo.game.model.area.collision; \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/Mob.java b/src/org/apollo/game/model/entity/Mob.java index 9240dc8e..022e0692 100644 --- a/src/org/apollo/game/model/entity/Mob.java +++ b/src/org/apollo/game/model/entity/Mob.java @@ -16,6 +16,7 @@ import org.apollo.game.model.area.SectorRepository; import org.apollo.game.model.def.NpcDefinition; import org.apollo.game.model.entity.attr.Attribute; import org.apollo.game.model.entity.attr.AttributeMap; +import org.apollo.game.model.event.impl.MobPositionUpdateEvent; import org.apollo.game.model.inv.Inventory; import org.apollo.game.model.inv.Inventory.StackMode; import org.apollo.game.model.inv.InventoryConstants; @@ -156,7 +157,7 @@ public abstract class Mob extends Entity { * @return The value of the attribute. */ public final Attribute getAttribute(String name) { - return attributes.getAttribute(name); + return attributes.get(name); } /** @@ -368,7 +369,7 @@ public abstract class Mob extends Entity { * @param value The attribute. */ public final void setAttribute(String name, Attribute value) { - attributes.setAttribute(name, value); + attributes.set(name, value); } /** @@ -403,6 +404,15 @@ public abstract class Mob extends Entity { } } + /** + * Returns this mobs interacting index. + * + * @return The interaction index of this mob. + */ + public int getInteractionIndex() { + return index; + } + /** * Updates this mob's interacting mob. * @@ -410,7 +420,7 @@ public abstract class Mob extends Entity { */ public final void setInteractingMob(Mob mob) { interactingMob = mob; - blockSet.add(SynchronizationBlock.createInteractingMobBlock(mob.index)); + blockSet.add(SynchronizationBlock.createInteractingMobBlock(mob.getInteractionIndex())); } /** @@ -419,6 +429,9 @@ public abstract class Mob extends Entity { * @param position The position. */ public final void setPosition(Position position) { + World.getWorld().submit(new MobPositionUpdateEvent(this, position)); + // Intentionally ignore the Event result - accidentally terminating this method would break the entire server. + Position old = this.position; SectorRepository repository = World.getWorld().getSectorRepository(); Sector current = repository.fromPosition(old); diff --git a/src/org/apollo/game/model/entity/Player.java b/src/org/apollo/game/model/entity/Player.java index 2367991e..79f9efc1 100644 --- a/src/org/apollo/game/model/entity/Player.java +++ b/src/org/apollo/game/model/entity/Player.java @@ -19,6 +19,17 @@ import org.apollo.game.model.Appearance; import org.apollo.game.model.Position; import org.apollo.game.model.World; import org.apollo.game.model.area.Sector; +import org.apollo.game.model.entity.attr.Attribute; +import org.apollo.game.model.entity.attr.AttributeDefinition; +import org.apollo.game.model.entity.attr.AttributeMap; +import org.apollo.game.model.entity.attr.AttributePersistence; +import org.apollo.game.model.entity.attr.NumericalAttribute; +import org.apollo.game.model.entity.setting.MembershipStatus; +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.event.impl.LoginEvent; +import org.apollo.game.model.event.impl.LogoutEvent; import org.apollo.game.model.inter.InterfaceConstants; import org.apollo.game.model.inter.InterfaceListener; import org.apollo.game.model.inter.InterfaceSet; @@ -31,9 +42,6 @@ import org.apollo.game.model.inv.Inventory.StackMode; import org.apollo.game.model.inv.InventoryConstants; import org.apollo.game.model.inv.InventoryListener; import org.apollo.game.model.inv.SynchronizationInventoryListener; -import org.apollo.game.model.setting.PrivacyState; -import org.apollo.game.model.setting.PrivilegeLevel; -import org.apollo.game.model.setting.ScreenBrightness; import org.apollo.game.model.skill.LevelUpSkillListener; import org.apollo.game.model.skill.SynchronizationSkillListener; import org.apollo.game.sync.block.SynchronizationBlock; @@ -52,6 +60,11 @@ import com.google.common.base.Preconditions; */ public final class Player extends Mob { + static { + AttributeMap.define("run_energy", AttributeDefinition.forInt(100, AttributePersistence.PERSISTENT)); + AttributeMap.define("client_version", AttributeDefinition.forInt(0, AttributePersistence.TRANSIENT)); + } + /** * The player's appearance. */ @@ -72,32 +85,11 @@ public final class Player extends Mob { */ private transient Deque clicks = new ArrayDeque<>(); - /** - * The version of the client this player is using. This is not the same as the release number, instead denoting the - * custom version. - */ - private transient int clientVersion; - /** * This player's credentials. */ private PlayerCredentials credentials; - @Override - public int hashCode() { - return credentials.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Player) { - Player other = (Player) obj; - return credentials.equals(other.credentials); - } - - return false; - } - /** * A flag which indicates there are npcs that couldn't be added. */ @@ -144,14 +136,9 @@ public final class Player extends Mob { private transient Position lastKnownSector; /** - * The membership flag. + * The MembershipStatus of this Player. */ - private transient boolean members = false; - - /** - * A flag indicating if the player is new. - */ - private boolean newPlayer = false; + private transient MembershipStatus members = MembershipStatus.FREE; /** * This player's prayer icon. @@ -168,16 +155,6 @@ public final class Player extends Mob { */ private final transient Deque queuedMessages = new ArrayDeque<>(); - /** - * A flag indicating if the sector changed in the last cycle. - */ - private transient boolean sectorChanged = false; - - /** - * The player's run energy. - */ - private int runEnergy = 100; - /** * A flag indicating if this player is running. */ @@ -188,6 +165,11 @@ public final class Player extends Mob { */ private ScreenBrightness screenBrightness = ScreenBrightness.NORMAL; + /** + * A flag indicating if the sector changed in the last cycle. + */ + private transient boolean sectorChanged = false; + /** * The {@link GameSession} currently attached to this {@link Player}. */ @@ -263,6 +245,16 @@ public final class Player extends Mob { } } + @Override + public boolean equals(Object obj) { + if (obj instanceof Player) { + Player other = (Player) obj; + return credentials.equals(other.credentials); + } + + return false; + } + /** * Sets the excessive npcs flag. */ @@ -329,7 +321,8 @@ public final class Player extends Mob { * @return The version. */ public int getClientVersion() { - return clientVersion; + Attribute version = attributes.get("client_version"); + return version.getValue(); } /** @@ -382,6 +375,11 @@ public final class Player extends Mob { return ignores; } + @Override + public int getInteractionIndex() { + return getIndex() | 0x8000; + } + /** * Gets this player's interface set. * @@ -433,7 +431,8 @@ public final class Player extends Mob { * @return The run energy. */ public int getRunEnergy() { - return runEnergy; + Attribute energy = attributes.get("run_energy"); + return energy.getValue(); } /** @@ -490,6 +489,11 @@ public final class Player extends Mob { return worldId; } + @Override + public int hashCode() { + return credentials.hashCode(); + } + /** * Indicates whether or not the player with the specified username is on this player's ignore list. * @@ -551,16 +555,16 @@ public final class Player extends Mob { * @return {@code true} if so, {@code false} if not. */ public boolean isMembers() { - return members; + return members == MembershipStatus.PAID; } /** - * Checks if this player has logged in before. + * Gets the {@link MembershipStatus} of this Player. * - * @return A flag indicating if the player is new. + * @return The MembershipStatus. */ - public boolean isNew() { - return newPlayer; + public MembershipStatus getMembershipStatus() { + return members; } /** @@ -594,7 +598,9 @@ public final class Player extends Mob { * Logs the player out, if possible. */ public void logout() { - send(new LogoutMessage()); + if (World.getWorld().submit(new LogoutEvent(this))) { + send(new LogoutMessage()); + } } /** @@ -675,6 +681,27 @@ public final class Player extends Mob { } } + /** + * Sends the initial messages. + */ + public void sendInitialMessages() { + blockSet.add(SynchronizationBlock.createAppearanceBlock(this)); + send(new IdAssignmentMessage(index, members)); // TODO should this be sent when we reconnect? + sendMessage("Welcome to RuneScape."); + + int[] tabs = InterfaceConstants.DEFAULT_INVENTORY_TABS; + for (int tab = 0; tab < tabs.length; tab++) { + send(new SwitchTabInterfaceMessage(tab, tabs[tab])); + } + + inventory.forceRefresh(); + equipment.forceRefresh(); + bank.forceRefresh(); + skillSet.forceRefresh(); + + World.getWorld().submit(new LoginEvent(this)); + } + /** * Sends a message to the player. * @@ -691,7 +718,7 @@ public final class Player extends Mob { * @param filterable Whether or not the message can be filtered. */ public void sendMessage(String message, boolean filterable) { - if (clientVersion > 0) { + if (getClientVersion() > 0) { send(new ServerChatMessage(message, filterable)); } else if (!filterable || !filteringMessages) { send(new ServerChatMessage(message)); @@ -748,12 +775,12 @@ public final class Player extends Mob { } /** - * Sets the value denoting the client's modified version. TODO make this an attribute? + * Sets the value denoting the client's modified version. * - * @param clientVersion The client version. + * @param version The client version. */ - public void setClientVersion(int clientVersion) { - this.clientVersion = clientVersion; + public void setClientVersion(int version) { + attributes.set("client_version", new NumericalAttribute(version)); } /** @@ -797,19 +824,10 @@ public final class Player extends Mob { * * @param members The new membership flag. */ - public void setMembers(boolean members) { + public void setMembers(MembershipStatus members) { this.members = members; } - /** - * Sets the new player flag. TODO make this an attribute? - * - * @param newPlayer A flag indicating if the player has played before. - */ - public void setNew(boolean newPlayer) { - this.newPlayer = newPlayer; - } - /** * Sets the player's prayer icon. TODO make this an attribute? * @@ -829,22 +847,13 @@ public final class Player extends Mob { } /** - * Sets the sector changed flag. + * Sets the player's run energy. * - * @param sectorChanged A flag indicating if the sector has changed. + * @param energy The energy. */ - public void setSectorChanged(boolean sectorChanged) { - this.sectorChanged = sectorChanged; - } - - /** - * Sets the player's run energy. TODO make this an attribute? - * - * @param runEnergy The energy. - */ - public void setRunEnergy(int runEnergy) { - this.runEnergy = runEnergy; - send(new UpdateRunEnergyMessage(runEnergy)); + public void setRunEnergy(int energy) { + attributes.set("run_energy", new NumericalAttribute(energy)); + send(new UpdateRunEnergyMessage(energy)); } /** @@ -856,18 +865,22 @@ public final class Player extends Mob { this.screenBrightness = brightness; } + /** + * Sets the sector changed flag. + * + * @param sectorChanged A flag indicating if the sector has changed. + */ + public void setSectorChanged(boolean sectorChanged) { + this.sectorChanged = sectorChanged; + } + /** * Sets the player's {@link GameSession}. * * @param session The player's {@link GameSession}. - * @param reconnecting The reconnecting flag. */ - public void setSession(GameSession session, boolean reconnecting) { + public void setSession(GameSession session) { this.session = session; - if (!reconnecting) { - sendInitialMessages(); - } - blockSet.add(SynchronizationBlock.createAppearanceBlock(this)); } /** @@ -931,7 +944,7 @@ public final class Player extends Mob { @Override public String toString() { return MoreObjects.toStringHelper(this).add("username", getUsername()).add("privilege", privilegeLevel) - .add("client version", clientVersion).toString(); + .add("client version", getClientVersion()).toString(); } /** @@ -977,27 +990,4 @@ public final class Player extends Mob { skillSet.addListener(new LevelUpSkillListener(this)); } - /** - * Sends the initial messages. - */ - private void sendInitialMessages() { - send(new IdAssignmentMessage(index, members)); // TODO should this be sent when we reconnect? - sendMessage("Welcome to RuneScape."); - if (!newPlayer) { - interfaceSet.openWindow(InterfaceConstants.AVATAR_DESIGN); - } - - int[] tabs = InterfaceConstants.DEFAULT_INVENTORY_TABS; - for (int tab = 0; tab < tabs.length; tab++) { - send(new SwitchTabInterfaceMessage(tab, tabs[tab])); - } - - inventory.forceRefresh(); - equipment.forceRefresh(); - bank.forceRefresh(); - skillSet.forceRefresh(); - - World.getWorld().getLoginDispatcher().dispatch(this); - } - } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/attr/Attribute.java b/src/org/apollo/game/model/entity/attr/Attribute.java index 9fff080f..6627b8a0 100644 --- a/src/org/apollo/game/model/entity/attr/Attribute.java +++ b/src/org/apollo/game/model/entity/attr/Attribute.java @@ -13,12 +13,12 @@ public abstract class Attribute { /** * The type of this attribute. */ - private final AttributeType type; + protected final AttributeType type; /** * The value of this attribute. */ - protected T value; + protected final T value; /** * Creates the attribute with the specified {@link AttributeType} and value. @@ -31,6 +31,13 @@ public abstract class Attribute { this.value = value; } + /** + * Encodes this Attribute into a byte array. + * + * @return The byte array. + */ + public abstract byte[] encode(); + /** * Gets the type of this attribute. * @@ -49,4 +56,11 @@ public abstract class Attribute { return value; } + /** + * Returns the Sting representation of this Attribute. Will be used to write this Attribute as a String, if + * required. + */ + @Override + public abstract String toString(); + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/attr/AttributeDefinition.java b/src/org/apollo/game/model/entity/attr/AttributeDefinition.java index 9a48b15d..0f4092b1 100644 --- a/src/org/apollo/game/model/entity/attr/AttributeDefinition.java +++ b/src/org/apollo/game/model/entity/attr/AttributeDefinition.java @@ -10,25 +10,69 @@ package org.apollo.game.model.entity.attr; public final class AttributeDefinition { /** - * The default value of this definition. + * Creates an AttributeDefinition for a {@code boolean}. + * + * @param defaultValue The default value of the definition. + * @param persistence The {@link AttributePersistence} of the definition. + * @return The AttributeDefinition. + */ + public static AttributeDefinition forBoolean(boolean defaultValue, AttributePersistence persistence) { + return new AttributeDefinition<>(defaultValue, persistence, AttributeType.BOOLEAN); + } + + /** + * Creates an AttributeDefinition for a {@code double}. + * + * @param defaultValue The default value of the definition. + * @param persistence The {@link AttributePersistence} of the definition. + * @return The AttributeDefinition. + */ + public static AttributeDefinition forDouble(double defaultValue, AttributePersistence persistence) { + return new AttributeDefinition<>(defaultValue, persistence, AttributeType.DOUBLE); + } + + /** + * Creates an AttributeDefinition for an {@code int}. + * + * @param defaultValue The default value of the definition. + * @param persistence The {@link AttributePersistence} of the definition. + * @return The AttributeDefinition. + */ + public static AttributeDefinition forInt(int defaultValue, AttributePersistence persistence) { + return new AttributeDefinition<>(defaultValue, persistence, AttributeType.LONG); + } + + /** + * Creates an AttributeDefinition for a String. + * + * @param defaultValue The default value of the definition. + * @param persistence The {@link AttributePersistence} of the definition. + * @return The AttributeDefinition. + */ + public static AttributeDefinition forString(String defaultValue, AttributePersistence persistence) { + return new AttributeDefinition<>(defaultValue, persistence, AttributeType.STRING); + } + + /** + * The default value of the Attribute. */ private final T defaultValue; /** - * The persistence state of this definition. + * The persistence state of the Attribute. */ private final AttributePersistence persistence; /** - * The type of this definition. + * The type of the Attribute. */ private final AttributeType type; /** - * Creates the attribute definition. + * Creates the AttributeDefinition. * * @param defaultValue The default value. - * @param persistence The {@link AttributePersistence} state. + * @param persistence The {@link AttributePersistence}. * @param type The {@link AttributeType}. */ public AttributeDefinition(T defaultValue, AttributePersistence persistence, AttributeType type) { @@ -38,7 +82,7 @@ public final class AttributeDefinition { } /** - * Gets the default value of this attribute definition. + * Gets the default value of this AttributeDefinition. * * @return The default value. */ @@ -47,18 +91,18 @@ public final class AttributeDefinition { } /** - * Gets the persistence state of this attribute definition. + * Gets the {@link AttributePersistence} of this AttributeDefinition. * - * @return The persistence. + * @return The AttributePersistence. */ public AttributePersistence getPersistence() { return persistence; } /** - * Gets the {@link AttributeType} of this definition. + * Gets the {@link AttributeType} of this AttributeDefinition * - * @return The attribute type. + * @return The AttributeType. */ public AttributeType getType() { return type; diff --git a/src/org/apollo/game/model/entity/attr/AttributeMap.java b/src/org/apollo/game/model/entity/attr/AttributeMap.java index 0d79d68e..e1f0ce9b 100644 --- a/src/org/apollo/game/model/entity/attr/AttributeMap.java +++ b/src/org/apollo/game/model/entity/attr/AttributeMap.java @@ -3,6 +3,8 @@ package org.apollo.game.model.entity.attr; import java.util.HashMap; import java.util.Map; +import org.jruby.RubySymbol; + import com.google.common.base.Preconditions; /** @@ -12,10 +14,15 @@ import com.google.common.base.Preconditions; */ public final class AttributeMap { + /** + * The default size of the map. + */ + private static final int DEFAULT_MAP_SIZE = 2; + /** * The map of attribute names to definitions. */ - private static Map> definitions = new HashMap<>(1); + private static Map> definitions = new HashMap<>(); /** * Registers an {@link AttributeDefinition}. @@ -23,7 +30,7 @@ public final class AttributeMap { * @param name The name of the attribute. * @param definition The definition. */ - public static void addDefinition(String name, AttributeDefinition definition) { + public static void define(String name, AttributeDefinition definition) { definitions.put(name, definition); } @@ -33,8 +40,9 @@ public final class AttributeMap { * @param name The name of the attribute. * @return The attribute definition. */ - public static AttributeDefinition getDefinition(String name) { - return definitions.get(name); + @SuppressWarnings("unchecked") + public static AttributeDefinition getDefinition(String name) { + return (AttributeDefinition) definitions.get(name); } /** @@ -46,10 +54,20 @@ public final class AttributeMap { return new HashMap<>(definitions); } + /** + * Returns whether or not an {@link AttributeDefinition} with the specified name exists. + * + * @param name The name of the AttributeDefinition. + * @return {@code true} if the AttributeDefinition exists, {@code false} if not. + */ + public static boolean hasDefinition(String name) { + return definitions.containsKey(name); + } + /** * The map of attribute names to attributes. */ - private Map> attributes = new HashMap<>(); + private Map> attributes = new HashMap<>(DEFAULT_MAP_SIZE); /** * Gets the {@link Attribute} with the specified name. @@ -57,8 +75,13 @@ public final class AttributeMap { * @param name The name of the attribute. * @return The attribute. */ - public Attribute getAttribute(String name) { - return attributes.get(name); + @SuppressWarnings("unchecked") + public Attribute get(String name) { + AttributeDefinition definition = getDefinition(name); + Preconditions.checkNotNull(definition, "Attributes must be defined before their value can be retreived."); + + return (Attribute) attributes.computeIfAbsent(name, + key -> createAttribute(definition.getDefault(), definition.getType())); } /** @@ -76,9 +99,32 @@ public final class AttributeMap { * @param name The name of the attribute. * @param attribute The attribute. */ - public void setAttribute(String name, Attribute attribute) { + public void set(String name, Attribute attribute) { Preconditions.checkNotNull(getDefinition(name), "Attributes must be defined before their value can be set."); attributes.put(name, attribute); } + /** + * Creates an {@link Attribute} with the specified value and {@link AttributeType}. + * + * @param value The value of the Attribute. + * @param type The AttributeType. + * @return The Attribute. + */ + private Attribute createAttribute(T value, AttributeType type) { + switch (type) { + case LONG: + case DOUBLE: + return new NumericalAttribute((Integer) value); + case STRING: + return new StringAttribute((String) value); + case SYMBOL: + return new StringAttribute(((RubySymbol) value).asJavaString(), true); + case BOOLEAN: + return new BooleanAttribute((Boolean) value); + } + + throw new IllegalArgumentException("Unrecognised type " + type + "."); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/attr/AttributePersistence.java b/src/org/apollo/game/model/entity/attr/AttributePersistence.java index 29cb365f..5c86de00 100644 --- a/src/org/apollo/game/model/entity/attr/AttributePersistence.java +++ b/src/org/apollo/game/model/entity/attr/AttributePersistence.java @@ -8,7 +8,7 @@ public enum AttributePersistence { /** * The serialized persistence type, indicating that the attribute will be saved. */ - SERIALIZED, + PERSISTENT, /** * The transient persistence type, indicating that the attribute will not be saved. diff --git a/src/org/apollo/game/model/entity/attr/BooleanAttribute.java b/src/org/apollo/game/model/entity/attr/BooleanAttribute.java index e5409c66..e9df53d1 100644 --- a/src/org/apollo/game/model/entity/attr/BooleanAttribute.java +++ b/src/org/apollo/game/model/entity/attr/BooleanAttribute.java @@ -16,4 +16,14 @@ public final class BooleanAttribute extends Attribute { super(AttributeType.BOOLEAN, value); } + @Override + public byte[] encode() { + return new byte[] { (byte) (value ? 1 : 0) }; + } + + @Override + public String toString() { + return Boolean.toString(value); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/attr/NumericalAttribute.java b/src/org/apollo/game/model/entity/attr/NumericalAttribute.java index a7f21982..ffc3915b 100644 --- a/src/org/apollo/game/model/entity/attr/NumericalAttribute.java +++ b/src/org/apollo/game/model/entity/attr/NumericalAttribute.java @@ -1,5 +1,7 @@ package org.apollo.game.model.entity.attr; +import com.google.common.primitives.Longs; + /** * An {@link Attribute} with a numerical value. * @@ -26,4 +28,15 @@ public final class NumericalAttribute extends Attribute { super(typeOf(value), value); } + @Override + public byte[] encode() { + long encoded = (type == AttributeType.DOUBLE) ? Double.doubleToLongBits((double) value) : (long) value; + return Longs.toByteArray(encoded); + } + + @Override + public String toString() { + return (type == AttributeType.DOUBLE) ? Double.toString((double) value) : Long.toString((long) value); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/attr/StringAttribute.java b/src/org/apollo/game/model/entity/attr/StringAttribute.java index be7c763c..6468ee11 100644 --- a/src/org/apollo/game/model/entity/attr/StringAttribute.java +++ b/src/org/apollo/game/model/entity/attr/StringAttribute.java @@ -1,5 +1,7 @@ package org.apollo.game.model.entity.attr; +import java.nio.charset.Charset; + /** * An {@link Attribute} with a string value. * @@ -26,4 +28,14 @@ public final class StringAttribute extends Attribute { super(symbol ? AttributeType.SYMBOL : AttributeType.STRING, value); } + @Override + public byte[] encode() { + return value.getBytes(Charset.forName("UTF-8")); + } + + @Override + public String toString() { + return value; + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/setting/Gender.java b/src/org/apollo/game/model/entity/setting/Gender.java similarity index 91% rename from src/org/apollo/game/model/setting/Gender.java rename to src/org/apollo/game/model/entity/setting/Gender.java index 1e95bae1..9cd43668 100644 --- a/src/org/apollo/game/model/setting/Gender.java +++ b/src/org/apollo/game/model/entity/setting/Gender.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.setting; +package org.apollo.game.model.entity.setting; /** * An enumeration containing the two genders (male and female). This enumeration relies on the ordering of the elements diff --git a/src/org/apollo/game/model/entity/setting/MembershipStatus.java b/src/org/apollo/game/model/entity/setting/MembershipStatus.java new file mode 100644 index 00000000..c02f86f3 --- /dev/null +++ b/src/org/apollo/game/model/entity/setting/MembershipStatus.java @@ -0,0 +1,58 @@ +package org.apollo.game.model.entity.setting; + +import java.util.Arrays; +import java.util.Optional; + +/** + * The membership status of a Player. + * + * @author Major + */ +public enum MembershipStatus { + + /** + * The free membership status. + */ + FREE(0), + + /** + * The paid membership status. + */ + PAID(1); + + /** + * Gets the MembershipStatus with the specified value. + * + * @param value The integer value of the MembershipStatus. + * @return The MembershipStatus. + * @throws IllegalArgumentException If no MembershipStatus with the specified the value exists. + */ + public static MembershipStatus valueOf(int value) { + Optional optional = Arrays.stream(values()).filter(status -> status.value == value).findAny(); + return optional.orElseThrow(() -> new IllegalArgumentException("Illegal membership status value.")); + } + + /** + * The integer value of this MembershipStatus. + */ + private final int value; + + /** + * Creates the MembershipStatus. + * + * @param value The integer value. + */ + private MembershipStatus(int value) { + this.value = value; + } + + /** + * Gets the value of this MembershipStatus. + * + * @return The value. + */ + public int getValue() { + return value; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/setting/PrivacyState.java b/src/org/apollo/game/model/entity/setting/PrivacyState.java similarity index 98% rename from src/org/apollo/game/model/setting/PrivacyState.java rename to src/org/apollo/game/model/entity/setting/PrivacyState.java index 0a62b1fc..76fbec8d 100644 --- a/src/org/apollo/game/model/setting/PrivacyState.java +++ b/src/org/apollo/game/model/entity/setting/PrivacyState.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.setting; +package org.apollo.game.model.entity.setting; import com.google.common.base.Preconditions; diff --git a/src/org/apollo/game/model/setting/PrivilegeLevel.java b/src/org/apollo/game/model/entity/setting/PrivilegeLevel.java similarity index 96% rename from src/org/apollo/game/model/setting/PrivilegeLevel.java rename to src/org/apollo/game/model/entity/setting/PrivilegeLevel.java index e5738f7c..fbdaf7c6 100644 --- a/src/org/apollo/game/model/setting/PrivilegeLevel.java +++ b/src/org/apollo/game/model/entity/setting/PrivilegeLevel.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.setting; +package org.apollo.game.model.entity.setting; import com.google.common.base.Preconditions; diff --git a/src/org/apollo/game/model/setting/ScreenBrightness.java b/src/org/apollo/game/model/entity/setting/ScreenBrightness.java similarity index 96% rename from src/org/apollo/game/model/setting/ScreenBrightness.java rename to src/org/apollo/game/model/entity/setting/ScreenBrightness.java index 9ae6cd9c..4a190a44 100644 --- a/src/org/apollo/game/model/setting/ScreenBrightness.java +++ b/src/org/apollo/game/model/entity/setting/ScreenBrightness.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.setting; +package org.apollo.game.model.entity.setting; import com.google.common.base.Preconditions; diff --git a/src/org/apollo/game/model/setting/ServerStatus.java b/src/org/apollo/game/model/entity/setting/ServerStatus.java similarity index 95% rename from src/org/apollo/game/model/setting/ServerStatus.java rename to src/org/apollo/game/model/entity/setting/ServerStatus.java index 05bc3d8e..651d4b88 100644 --- a/src/org/apollo/game/model/setting/ServerStatus.java +++ b/src/org/apollo/game/model/entity/setting/ServerStatus.java @@ -1,4 +1,4 @@ -package org.apollo.game.model.setting; +package org.apollo.game.model.entity.setting; import com.google.common.base.Preconditions; diff --git a/src/org/apollo/game/model/setting/package-info.java b/src/org/apollo/game/model/entity/setting/package-info.java similarity index 60% rename from src/org/apollo/game/model/setting/package-info.java rename to src/org/apollo/game/model/entity/setting/package-info.java index 5c8ed99e..db35d20a 100644 --- a/src/org/apollo/game/model/setting/package-info.java +++ b/src/org/apollo/game/model/entity/setting/package-info.java @@ -1,4 +1,4 @@ /** * Contains player setting or customisation-related classes. */ -package org.apollo.game.model.setting; \ No newline at end of file +package org.apollo.game.model.entity.setting; \ No newline at end of file diff --git a/src/org/apollo/game/model/event/Event.java b/src/org/apollo/game/model/event/Event.java new file mode 100644 index 00000000..8d71d84e --- /dev/null +++ b/src/org/apollo/game/model/event/Event.java @@ -0,0 +1,31 @@ +package org.apollo.game.model.event; + +/** + * A type of event that may occur in the game world. + * + * @author Major + */ +public abstract class Event { + + /** + * Indicates whether or not the Event chain has been terminated. + */ + private boolean terminated; + + /** + * Terminates the Event chain. + */ + public final void terminate() { + terminated = true; + } + + /** + * Returns whether or not the Event chain has been terminated. + * + * @return {@code true} if the Event chain has been terminated, otherwise {@code false}. + */ + public final boolean terminated() { + return terminated; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/EventListener.java b/src/org/apollo/game/model/event/EventListener.java new file mode 100644 index 00000000..7a5306b8 --- /dev/null +++ b/src/org/apollo/game/model/event/EventListener.java @@ -0,0 +1,20 @@ +package org.apollo.game.model.event; + +/** + * A listener for an {@link Event} that may occur in the game world. + * + * @author Major + * + * @param The type of Event. + */ +@FunctionalInterface +public interface EventListener { + + /** + * Handles the {@link Event} that occurred. + * + * @param event The Event. + */ + public void handle(E event); + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/EventListenerChain.java b/src/org/apollo/game/model/event/EventListenerChain.java new file mode 100644 index 00000000..a2fbca2d --- /dev/null +++ b/src/org/apollo/game/model/event/EventListenerChain.java @@ -0,0 +1,66 @@ +package org.apollo.game.model.event; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.MoreObjects; + +/** + * A chain of {@link EventListener}s. + * + * @author Major + * @param The type of {@link Event} the listeners in this chain listen for. + */ +final class EventListenerChain { + + /** + * The List of EventListeners. + */ + private final List> listeners = new ArrayList<>(); + + /** + * The Class type of this chain. + */ + private final Class type; + + /** + * Creates the EventListenerChain. + * + * @param type The {@link Class} type of this chain. + */ + public EventListenerChain(Class type) { + this.type = type; + } + + /** + * Adds an {@link EventListener} to this chain. + * + * @param listener The EventListener to add. + */ + public void addListener(EventListener listener) { + listeners.add(listener); + } + + /** + * Notifies each {@link EventListener} in this chain that an {@link Event} has occurred. + * + * @param event The event. + * @return {@code true} if the Event should continue on with its outcome, {@code false} if not. + */ + public boolean notify(E event) { + for (EventListener listener : listeners) { + listener.handle(event); + + if (event.terminated()) { + return false; + } + } + + return true; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("type", type).add("listeners", listeners).toString(); + } +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/EventListenerChainSet.java b/src/org/apollo/game/model/event/EventListenerChainSet.java new file mode 100644 index 00000000..8dbe8bfa --- /dev/null +++ b/src/org/apollo/game/model/event/EventListenerChainSet.java @@ -0,0 +1,42 @@ +package org.apollo.game.model.event; + +import java.util.HashMap; +import java.util.Map; + +/** + * A set of {@link EventListenerChain}s. + * + * @author Major + */ +public final class EventListenerChainSet { + + /** + * The Map of Event Classes to EventListenerChains. + */ + private final Map, EventListenerChain> chains = new HashMap<>(); + + /** + * Notifies the appropriate {@link EventListenerChain} that an {@link Event} has occurred. + * + * @param event The Event. + * @return {@code true} if the Event should continue on with its outcome, {@code false} if not. + */ + public boolean notify(E event) { + @SuppressWarnings("unchecked") + EventListenerChain chain = (EventListenerChain) chains.get(event.getClass()); + return (chain == null) ? true : chain.notify(event); + } + + /** + * Places the {@link EventListenerChain} into this set. + * + * @param clazz The {@link Class} to associate the EventListenerChain with. + * @param listener The EventListenerChain. + */ + public void putListener(Class clazz, EventListener listener) { + @SuppressWarnings("unchecked") + EventListenerChain chain = (EventListenerChain) chains.computeIfAbsent(clazz, EventListenerChain::new); + chain.addListener(listener); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/PlayerEvent.java b/src/org/apollo/game/model/event/PlayerEvent.java new file mode 100644 index 00000000..23272210 --- /dev/null +++ b/src/org/apollo/game/model/event/PlayerEvent.java @@ -0,0 +1,35 @@ +package org.apollo.game.model.event; + +import org.apollo.game.model.entity.Player; + +/** + * An {@link Event} involving a {@link Player}. + * + * @author Major + */ +public abstract class PlayerEvent extends Event { + + /** + * The Player. + */ + private final Player player; + + /** + * Creates the PlayerEvent. + * + * @param player The {@link Player}. + */ + public PlayerEvent(Player player) { + this.player = player; + } + + /** + * Gets the {@link Player}. + * + * @return The Player. + */ + public Player getPlayer() { + return player; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/impl/CloseInterfacesEvent.java b/src/org/apollo/game/model/event/impl/CloseInterfacesEvent.java new file mode 100644 index 00000000..c2f08c74 --- /dev/null +++ b/src/org/apollo/game/model/event/impl/CloseInterfacesEvent.java @@ -0,0 +1,23 @@ +package org.apollo.game.model.event.impl; + +import org.apollo.game.model.entity.Player; +import org.apollo.game.model.event.Event; +import org.apollo.game.model.event.PlayerEvent; + +/** + * An {@link Event} indicating that a player's open interfaces are about to be closed. + * + * @author Major + */ +public final class CloseInterfacesEvent extends PlayerEvent { + + /** + * Creates the CloseInterfacesEvent. + * + * @param player The {@link Player} whose interfaces are being closed. + */ + public CloseInterfacesEvent(Player player) { + super(player); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/impl/LoginEvent.java b/src/org/apollo/game/model/event/impl/LoginEvent.java new file mode 100644 index 00000000..307764f4 --- /dev/null +++ b/src/org/apollo/game/model/event/impl/LoginEvent.java @@ -0,0 +1,22 @@ +package org.apollo.game.model.event.impl; + +import org.apollo.game.model.entity.Player; +import org.apollo.game.model.event.PlayerEvent; + +/** + * A {@link PlayerEvent} that is fired when a {@link Player} logs in. + * + * @author Major + */ +public final class LoginEvent extends PlayerEvent { + + /** + * Creates the LoginEvent. + * + * @param player The {@link Player} logging in. + */ + public LoginEvent(Player player) { + super(player); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/impl/LogoutEvent.java b/src/org/apollo/game/model/event/impl/LogoutEvent.java new file mode 100644 index 00000000..16f17390 --- /dev/null +++ b/src/org/apollo/game/model/event/impl/LogoutEvent.java @@ -0,0 +1,22 @@ +package org.apollo.game.model.event.impl; + +import org.apollo.game.model.entity.Player; +import org.apollo.game.model.event.PlayerEvent; + +/** + * A {@link PlayerEvent} that is fired when a {@link Player} logs out. + * + * @author Major + */ +public final class LogoutEvent extends PlayerEvent { + + /** + * Creates the LogoutEvent. + * + * @param player The {@link Player} logging out. + */ + public LogoutEvent(Player player) { + super(player); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java b/src/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java new file mode 100644 index 00000000..82f9a470 --- /dev/null +++ b/src/org/apollo/game/model/event/impl/MobPositionUpdateEvent.java @@ -0,0 +1,56 @@ +package org.apollo.game.model.event.impl; + +import org.apollo.game.model.Position; +import org.apollo.game.model.entity.Mob; +import org.apollo.game.model.event.Event; + +/** + * An {@link Event} created when a Mob's Position is being updated. + *

+ * This Event intentionally ignores the result of execution - it should not be possible for a plugin to prevent this + * Event from happening, only to listen for it. + * + * @author Major + */ +public final class MobPositionUpdateEvent extends Event { + + /** + * The Mob whose position is being updated. + */ + private final Mob mob; + + /** + * The next Position of the Mob. + */ + private final Position next; + + /** + * Creates the MobPositionUpdateEvent. + * + * @param mob The {@link Mob} whose Position is being updated. + * @param next The next {@link Position} of the Mob. + */ + public MobPositionUpdateEvent(Mob mob, Position next) { + this.mob = mob; + this.next = next; + } + + /** + * Gets the {@link Mob} being moved. + * + * @return The Mob. + */ + public Mob getMob() { + return mob; + } + + /** + * Gets the {@link Position} this {@link Mob} is being moved to. + * + * @return The Position. + */ + public Position getNext() { + return next; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/event/impl/package-info.java b/src/org/apollo/game/model/event/impl/package-info.java new file mode 100644 index 00000000..a6527103 --- /dev/null +++ b/src/org/apollo/game/model/event/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains Event implementations. + */ +package org.apollo.game.model.event.impl; \ No newline at end of file diff --git a/src/org/apollo/game/model/event/package-info.java b/src/org/apollo/game/model/event/package-info.java new file mode 100644 index 00000000..8167081f --- /dev/null +++ b/src/org/apollo/game/model/event/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains event-related classes. + */ +package org.apollo.game.model.event; \ No newline at end of file diff --git a/src/org/apollo/game/model/inter/InterfaceConstants.java b/src/org/apollo/game/model/inter/InterfaceConstants.java index d16e6cbb..d0e44330 100644 --- a/src/org/apollo/game/model/inter/InterfaceConstants.java +++ b/src/org/apollo/game/model/inter/InterfaceConstants.java @@ -7,11 +7,6 @@ package org.apollo.game.model.inter; */ public class InterfaceConstants { - /** - * The avatar design interface id. - */ - public static final int AVATAR_DESIGN = 3559; - /** * The default inventory tab ids. */ diff --git a/src/org/apollo/game/model/inter/InterfaceSet.java b/src/org/apollo/game/model/inter/InterfaceSet.java index 72835dac..3e77387e 100644 --- a/src/org/apollo/game/model/inter/InterfaceSet.java +++ b/src/org/apollo/game/model/inter/InterfaceSet.java @@ -7,10 +7,16 @@ import java.util.Optional; import org.apollo.game.message.impl.CloseInterfaceMessage; import org.apollo.game.message.impl.EnterAmountMessage; import org.apollo.game.message.impl.OpenDialogueInterfaceMessage; +import org.apollo.game.message.impl.OpenDialogueOverlayMessage; import org.apollo.game.message.impl.OpenInterfaceMessage; import org.apollo.game.message.impl.OpenInterfaceSidebarMessage; +import org.apollo.game.message.impl.OpenOverlayMessage; +import org.apollo.game.message.impl.OpenSidebarMessage; +import org.apollo.game.model.World; import org.apollo.game.model.entity.Player; +import org.apollo.game.model.event.impl.CloseInterfacesEvent; import org.apollo.game.model.inter.dialogue.DialogueListener; +import org.apollo.game.model.inv.InventoryListener; /** * Represents the set of interfaces the player has open. @@ -83,8 +89,11 @@ public final class InterfaceSet { * Closes the current open interface(s). */ public void close() { - closeAndNotify(); - player.send(new CloseInterfaceMessage()); + CloseInterfacesEvent event = new CloseInterfacesEvent(player); + if (World.getWorld().submit(event)) { + closeAndNotify(); + player.send(new CloseInterfaceMessage()); + } } /** @@ -136,10 +145,10 @@ public final class InterfaceSet { } /** - * Opens a chat box dialogue. + * Opens a dialogue interface. * - * @param listener The listener for the dialogue. - * @param dialogueId The dialogue's id. + * @param listener The {@link DialogueListener}. + * @param dialogueId The dialogue id. */ public void openDialogue(DialogueListener listener, int dialogueId) { closeAndNotify(); @@ -152,14 +161,39 @@ public final class InterfaceSet { } /** - * Opens a chat box dialogue. + * Opens a dialogue. * - * @param dialogueId The dialogue's id. + * @param dialogueId The dialogue id. */ public void openDialogue(int dialogueId) { openDialogue(null, dialogueId); } + /** + * Opens a dialogue overlay interface. + * + * @param listener The {@link DialogueListener}. + * @param dialogueId The dialogue id. + */ + public void openDialogueOverlay(DialogueListener listener, int dialogueId) { + closeAndNotify(); + + this.dialogueListener = Optional.ofNullable(listener); + this.listener = Optional.ofNullable(listener); + + interfaces.put(InterfaceType.DIALOGUE, dialogueId); + player.send(new OpenDialogueOverlayMessage(dialogueId)); + } + + /** + * Opens a dialogue overlay. + * + * @param dialogueId The dialogue id. + */ + public void openDialogueOverlay(int dialogueId) { + openDialogueOverlay(null, dialogueId); + } + /** * Opens the enter amount dialogue. * @@ -170,6 +204,42 @@ public final class InterfaceSet { player.send(new EnterAmountMessage()); } + /** + * Opens an overlay interface. + * + * @param overlay The overlay id. + */ + public void openOverlay(int overlay) { + interfaces.put(InterfaceType.OVERLAY, overlay); + player.send(new OpenOverlayMessage(overlay)); + } + + /** + * Opens an sidebar interface. + * + * @param sidebar The sidebar id. + */ + public void openSidebar(int sidebar) { + closeAndNotify(); + interfaces.put(InterfaceType.SIDEBAR, sidebar); + + player.send(new OpenSidebarMessage(sidebar)); + } + + /** + * Opens an sidebar interface with the specified {@link InventoryListener}. + * + * @param listener The listener. + * @param sidebar The sidebar id. + */ + public void openSidebar(InterfaceListener listener, int sidebar) { + closeAndNotify(); + this.listener = Optional.ofNullable(listener); + interfaces.put(InterfaceType.SIDEBAR, sidebar); + + player.send(new OpenSidebarMessage(sidebar)); + } + /** * Opens a window. * diff --git a/src/org/apollo/game/model/inter/bank/BankUtils.java b/src/org/apollo/game/model/inter/bank/BankUtils.java index faa81818..75cffec8 100644 --- a/src/org/apollo/game/model/inter/bank/BankUtils.java +++ b/src/org/apollo/game/model/inter/bank/BankUtils.java @@ -3,10 +3,7 @@ package org.apollo.game.model.inter.bank; import org.apollo.game.model.Item; import org.apollo.game.model.def.ItemDefinition; import org.apollo.game.model.entity.Player; -import org.apollo.game.model.inter.InterfaceListener; import org.apollo.game.model.inv.Inventory; -import org.apollo.game.model.inv.InventoryListener; -import org.apollo.game.model.inv.SynchronizationInventoryListener; /** * Contains bank-related utility methods. @@ -65,18 +62,8 @@ public final class BankUtils { * @param player The player. */ public static void openBank(Player player) { - InventoryListener invListener = new SynchronizationInventoryListener(player, BankConstants.SIDEBAR_INVENTORY_ID); - InventoryListener bankListener = new SynchronizationInventoryListener(player, BankConstants.BANK_INVENTORY_ID); - - player.getInventory().addListener(invListener); - player.getBank().addListener(bankListener); - - player.getInventory().forceRefresh(); - player.getBank().forceRefresh(); - - InterfaceListener interListener = new BankInterfaceListener(player, invListener, bankListener); - - player.getInterfaceSet().openWindowWithSidebar(interListener, BankConstants.BANK_WINDOW_ID, BankConstants.SIDEBAR_ID); + // Required for access within plugin Actions. + player.openBank(); } /** diff --git a/src/org/apollo/game/sync/block/ChatBlock.java b/src/org/apollo/game/sync/block/ChatBlock.java index de3eb001..14bbba81 100644 --- a/src/org/apollo/game/sync/block/ChatBlock.java +++ b/src/org/apollo/game/sync/block/ChatBlock.java @@ -1,7 +1,7 @@ package org.apollo.game.sync.block; import org.apollo.game.message.impl.ChatMessage; -import org.apollo.game.model.setting.PrivilegeLevel; +import org.apollo.game.model.entity.setting.PrivilegeLevel; /** * The chat {@link SynchronizationBlock}. Only players can utilise this block. diff --git a/src/org/apollo/io/player/impl/BinaryPlayerLoader.java b/src/org/apollo/io/player/impl/BinaryPlayerLoader.java index ada3d857..19d77500 100644 --- a/src/org/apollo/io/player/impl/BinaryPlayerLoader.java +++ b/src/org/apollo/io/player/impl/BinaryPlayerLoader.java @@ -20,11 +20,12 @@ import org.apollo.game.model.entity.attr.AttributeType; import org.apollo.game.model.entity.attr.BooleanAttribute; import org.apollo.game.model.entity.attr.NumericalAttribute; import org.apollo.game.model.entity.attr.StringAttribute; +import org.apollo.game.model.entity.setting.Gender; +import org.apollo.game.model.entity.setting.MembershipStatus; +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.game.model.setting.Gender; -import org.apollo.game.model.setting.PrivacyState; -import org.apollo.game.model.setting.PrivilegeLevel; -import org.apollo.game.model.setting.ScreenBrightness; import org.apollo.io.player.PlayerLoader; import org.apollo.io.player.PlayerLoaderResponse; import org.apollo.net.codec.login.LoginConstants; @@ -57,35 +58,28 @@ public final class BinaryPlayerLoader implements PlayerLoader { } try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { - // read credentials and privileges String name = StreamUtil.readString(in); - String pass = StreamUtil.readString(in); + String password = StreamUtil.readString(in); - if (!name.equalsIgnoreCase(credentials.getUsername()) || !SCryptUtil.check(credentials.getPassword(), pass)) { + if (!name.equalsIgnoreCase(credentials.getUsername()) || !SCryptUtil.check(credentials.getPassword(), password)) { return new PlayerLoaderResponse(LoginConstants.STATUS_INVALID_CREDENTIALS); } - // set the credentials password to the scrypted one - credentials.setPassword(pass); + credentials.setPassword(password); // Update password to the hashed one. PrivilegeLevel privilegeLevel = PrivilegeLevel.valueOf(in.readByte()); - boolean members = in.readBoolean(); + MembershipStatus members = MembershipStatus.valueOf(in.readByte()); - // read settings 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()); - // read position int x = in.readUnsignedShort(); int y = in.readUnsignedShort(); int height = in.readUnsignedByte(); - // read appearance - boolean designed = in.readBoolean(); - int genderIntValue = in.readUnsignedByte(); Gender gender = genderIntValue == Gender.MALE.toInteger() ? Gender.MALE : Gender.FEMALE; int[] style = new int[7]; @@ -106,15 +100,12 @@ public final class BinaryPlayerLoader implements PlayerLoader { player.setRunEnergy(runEnergy); player.setScreenBrightness(brightness); - player.setNew(designed); player.setAppearance(new Appearance(gender, style, colors)); - // read inventories readInventory(in, player.getInventory()); readInventory(in, player.getEquipment()); readInventory(in, player.getBank()); - // read skills int size = in.readUnsignedByte(); SkillSet skills = player.getSkillSet(); skills.stopFiringEvents(); @@ -138,7 +129,7 @@ public final class BinaryPlayerLoader implements PlayerLoader { int ignoreCount = in.readByte(); List ignores = new ArrayList<>(ignoreCount); - for (int i = 0; i < ignoreCount; i++) { + for (int times = 0; times < ignoreCount; times++) { ignores.add(NameUtil.decodeBase37(in.readLong())); } player.setIgnoredUsernames(ignores); @@ -160,27 +151,28 @@ public final class BinaryPlayerLoader implements PlayerLoader { private static Map> readAttributes(DataInputStream in) throws IOException { int count = in.readInt(); Map> attributes = new HashMap<>(count); - Attribute attribute; - for (int i = 0; i < count; i++) { + for (int times = 0; times < count; times++) { String name = StreamUtil.readString(in); AttributeType type = AttributeType.valueOf(in.read()); + Attribute attribute; + switch (type) { - case BOOLEAN: - attribute = new BooleanAttribute(in.read() == 1); - break; - case DOUBLE: - attribute = new NumericalAttribute(in.readDouble()); - break; - case LONG: - attribute = new NumericalAttribute(in.readLong()); - break; - case STRING: - case SYMBOL: - attribute = new StringAttribute(StreamUtil.readString(in), type == AttributeType.SYMBOL); - break; - default: - throw new IllegalArgumentException("Undefined attribute type: " + type + "."); + case BOOLEAN: + attribute = new BooleanAttribute(in.read() == 1); + break; + case DOUBLE: + attribute = new NumericalAttribute(in.readDouble()); + break; + case LONG: + attribute = new NumericalAttribute(in.readLong()); + break; + case STRING: + case SYMBOL: + attribute = new StringAttribute(StreamUtil.readString(in), type == AttributeType.SYMBOL); + break; + default: + throw new IllegalArgumentException("Undefined attribute type: " + type + "."); } attributes.put(name, attribute); } diff --git a/src/org/apollo/io/player/impl/BinaryPlayerSaver.java b/src/org/apollo/io/player/impl/BinaryPlayerSaver.java index 082731cf..5fca15a2 100644 --- a/src/org/apollo/io/player/impl/BinaryPlayerSaver.java +++ b/src/org/apollo/io/player/impl/BinaryPlayerSaver.java @@ -17,7 +17,6 @@ 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.inv.Inventory; import org.apollo.io.player.PlayerSaver; import org.apollo.util.NameUtil; @@ -35,27 +34,22 @@ public final class BinaryPlayerSaver implements PlayerSaver { File file = BinaryPlayerUtil.getFile(player.getUsername()); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { - // write credentials and privileges StreamUtil.writeString(out, player.getUsername()); StreamUtil.writeString(out, player.getCredentials().getPassword()); out.writeByte(player.getPrivilegeLevel().toInteger()); - out.writeBoolean(player.isMembers()); + out.writeByte(player.getMembershipStatus().getValue()); - // write settings 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()); - // write position Position position = player.getPosition(); out.writeShort(position.getX()); out.writeShort(position.getY()); out.writeByte(position.getHeight()); - // write appearance - out.writeBoolean(player.isNew()); Appearance appearance = player.getAppearance(); out.writeByte(appearance.getGender().toInteger()); int[] style = appearance.getStyle(); @@ -66,14 +60,11 @@ public final class BinaryPlayerSaver implements PlayerSaver { for (int color : colors) { out.writeByte(color); } - out.flush(); - // write inventories writeInventory(out, player.getInventory()); writeInventory(out, player.getEquipment()); writeInventory(out, player.getBank()); - // write skills SkillSet skills = player.getSkillSet(); out.writeByte(skills.size()); for (int id = 0; id < skills.size(); id++) { @@ -95,47 +86,20 @@ public final class BinaryPlayerSaver implements PlayerSaver { } Set>> attributes = player.getAttributes().entrySet(); - attributes.removeIf(e -> AttributeMap.getDefinition(e.getKey()).getPersistence() != AttributePersistence.SERIALIZED); + attributes.removeIf(e -> AttributeMap.getDefinition(e.getKey()).getPersistence() != AttributePersistence.PERSISTENT); out.writeInt(attributes.size()); for (Entry> entry : attributes) { String name = entry.getKey(); StreamUtil.writeString(out, name); - saveAttribute(out, entry.getValue()); + + Attribute attribute = entry.getValue(); + out.writeByte(attribute.getType().getValue()); + out.write(attribute.encode()); } } } - /** - * Writes an {@link Attribute} to the specified output stream. - * - * @param out The output stream. - * @param attribute The attribute. - * @throws IOException If an I/O error occurs. - */ - private static void saveAttribute(DataOutputStream out, Attribute attribute) throws IOException { - AttributeType type = attribute.getType(); - - out.writeByte(type.getValue()); - switch (type) { - case BOOLEAN: - out.writeByte((Boolean) attribute.getValue() ? 1 : 0); - break; - case DOUBLE: - out.writeDouble((Double) attribute.getValue()); - break; - case LONG: - out.writeLong((Long) attribute.getValue()); - break; - case STRING: - case SYMBOL: - StreamUtil.writeString(out, (String) attribute.getValue()); - break; - default: - throw new IllegalArgumentException("Undefined attribute type " + type + "."); - } - } - /** * Writes an inventory to the specified output stream. * diff --git a/src/org/apollo/io/player/impl/BinaryPlayerUtil.java b/src/org/apollo/io/player/impl/BinaryPlayerUtil.java index 85ce31b5..9c4ca633 100644 --- a/src/org/apollo/io/player/impl/BinaryPlayerUtil.java +++ b/src/org/apollo/io/player/impl/BinaryPlayerUtil.java @@ -32,8 +32,8 @@ public final class BinaryPlayerUtil { * @return The file. */ public static File getFile(String username) { - username = NameUtil.decodeBase37(NameUtil.encodeBase37(username)); - return new File(SAVED_GAMES_DIRECTORY, username + ".dat"); + String filtered = NameUtil.decodeBase37(NameUtil.encodeBase37(username)); + return new File(SAVED_GAMES_DIRECTORY, filtered + ".dat"); } /** diff --git a/src/org/apollo/io/player/impl/DummyPlayerLoader.java b/src/org/apollo/io/player/impl/DummyPlayerLoader.java index 0cc48ed4..83b0aedf 100644 --- a/src/org/apollo/io/player/impl/DummyPlayerLoader.java +++ b/src/org/apollo/io/player/impl/DummyPlayerLoader.java @@ -2,7 +2,8 @@ package org.apollo.io.player.impl; import org.apollo.game.model.Position; import org.apollo.game.model.entity.Player; -import org.apollo.game.model.setting.PrivilegeLevel; +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; @@ -26,7 +27,7 @@ public final class DummyPlayerLoader implements PlayerLoader { Player player = new Player(credentials, DEFAULT_POSITION); player.setPrivilegeLevel(PrivilegeLevel.ADMINISTRATOR); - player.setMembers(true); + player.setMembers(MembershipStatus.PAID); return new PlayerLoaderResponse(status, player); } diff --git a/src/org/apollo/net/ApolloHandler.java b/src/org/apollo/net/ApolloHandler.java index ad51eb83..ce0adfc9 100644 --- a/src/org/apollo/net/ApolloHandler.java +++ b/src/org/apollo/net/ApolloHandler.java @@ -5,6 +5,8 @@ import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.Attribute; +import io.netty.util.ReferenceCountUtil; import java.util.logging.Level; import java.util.logging.Logger; @@ -65,25 +67,36 @@ public final class ApolloHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object message) { - if (ctx.attr(NetworkConstants.SESSION_KEY).get() == null) { + try { + Attribute attribute = ctx.attr(NetworkConstants.SESSION_KEY); + Session session = attribute.get(); + if (message instanceof HttpRequest || message instanceof JagGrabRequest) { - new UpdateSession(ctx.channel(), serverContext).messageReceived(message); - } else { + session = new UpdateSession(ctx.channel(), serverContext); + } + + if (session != null) { + session.messageReceived(message); + return; + } + + // TODO: Perhaps let HandshakeMessage implement Message to remove this explicit check + if (message instanceof HandshakeMessage) { HandshakeMessage handshakeMessage = (HandshakeMessage) message; switch (handshakeMessage.getServiceId()) { case HandshakeConstants.SERVICE_GAME: - ctx.attr(NetworkConstants.SESSION_KEY).set(new LoginSession(ctx, serverContext)); + attribute.set(new LoginSession(ctx, serverContext)); break; + case HandshakeConstants.SERVICE_UPDATE: - ctx.attr(NetworkConstants.SESSION_KEY).set(new UpdateSession(ctx.channel(), serverContext)); + attribute.set(new UpdateSession(ctx.channel(), serverContext)); break; - default: - throw new IllegalStateException("Invalid service id."); } } - } else { - ctx.attr(NetworkConstants.SESSION_KEY).get().messageReceived(message); + + } finally { + ReferenceCountUtil.release(message); } } diff --git a/src/org/apollo/net/codec/game/GamePacketDecoder.java b/src/org/apollo/net/codec/game/GamePacketDecoder.java index 5434ce1e..947cd87f 100644 --- a/src/org/apollo/net/codec/game/GamePacketDecoder.java +++ b/src/org/apollo/net/codec/game/GamePacketDecoder.java @@ -4,7 +4,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; -import java.io.IOException; import java.util.List; import net.burtleburtle.bob.rand.IsaacRandom; @@ -61,7 +60,7 @@ public final class GamePacketDecoder extends StatefulFrameDecoder out, GameDecoderState state) throws IOException { + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out, GameDecoderState state) { switch (state) { case GAME_OPCODE: decodeOpcode(in, out); @@ -96,9 +95,8 @@ public final class GamePacketDecoder extends StatefulFrameDecoder out) throws IOException { + private void decodeOpcode(ByteBuf buffer, List out) { if (buffer.isReadable()) { int encryptedOpcode = buffer.readUnsignedByte(); opcode = encryptedOpcode - random.nextInt() & 0xFF; @@ -121,7 +119,7 @@ public final class GamePacketDecoder extends StatefulFrameDecoder out) { - if (buffer.isReadable()) { - 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_UPDATE: - ctx.pipeline().addFirst("updateEncoder", new UpdateEncoder()); - ctx.pipeline().addBefore("handler", "updateDecoder", new UpdateDecoder()); - ByteBuf buf = ctx.alloc().buffer(8); - buf.writeLong(0); - ctx.channel().writeAndFlush(buf); - break; - default: - throw new IllegalArgumentException("Invalid service id."); - } - - ctx.pipeline().remove(this); - HandshakeMessage message = new HandshakeMessage(id); - - out.add(message); - if (buffer.isReadable()) { - out.add(buffer.readBytes(buffer.readableBytes())); - } + if (!buffer.isReadable()) { + return; } + + 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_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; + + 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); + out.add(new HandshakeMessage(id)); } } \ No newline at end of file diff --git a/src/org/apollo/net/codec/login/LoginDecoder.java b/src/org/apollo/net/codec/login/LoginDecoder.java index 1c96f631..23b7eb7b 100644 --- a/src/org/apollo/net/codec/login/LoginDecoder.java +++ b/src/org/apollo/net/codec/login/LoginDecoder.java @@ -2,9 +2,9 @@ package org.apollo.net.codec.login; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import java.io.IOException; import java.math.BigInteger; import java.security.SecureRandom; import java.util.List; @@ -28,7 +28,7 @@ public final class LoginDecoder extends StatefulFrameDecoder /** * The secure random number generator. */ - private static final SecureRandom random = new SecureRandom(); + private static final SecureRandom RANDOM = new SecureRandom(); /** * The login packet length. @@ -54,11 +54,11 @@ public final class LoginDecoder extends StatefulFrameDecoder * Creates the login decoder with the default initial state. */ public LoginDecoder() { - super(LoginDecoderState.LOGIN_HANDSHAKE, true); + super(LoginDecoderState.LOGIN_HANDSHAKE); } @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out, LoginDecoderState state) throws Exception { + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out, LoginDecoderState state) { switch (state) { case LOGIN_HANDSHAKE: decodeHandshake(ctx, in, out); @@ -70,7 +70,7 @@ public final class LoginDecoder extends StatefulFrameDecoder decodePayload(ctx, in, out); break; default: - throw new IllegalStateException("Invalid login decoder state."); + throw new IllegalStateException("Invalid login decoder state: " + state); } } @@ -84,7 +84,7 @@ public final class LoginDecoder extends StatefulFrameDecoder private void decodeHandshake(ChannelHandlerContext ctx, ByteBuf buffer, List out) { if (buffer.isReadable()) { usernameHash = buffer.readUnsignedByte(); - serverSeed = random.nextLong(); + serverSeed = RANDOM.nextLong(); ByteBuf response = ctx.alloc().buffer(17); response.writeByte(LoginConstants.STATUS_EXCHANGE_DATA); @@ -102,14 +102,14 @@ public final class LoginDecoder extends StatefulFrameDecoder * @param ctx The channel handler context. * @param buffer The buffer. * @param out The {@link List} of objects to pass forward through the pipeline. - * @throws IOException If the login type sent by the client is invalid. */ - private void decodeHeader(ChannelHandlerContext ctx, ByteBuf buffer, List out) throws IOException { + private void decodeHeader(ChannelHandlerContext ctx, ByteBuf buffer, List out) { if (buffer.readableBytes() >= 2) { int loginType = buffer.readUnsignedByte(); if (loginType != LoginConstants.TYPE_STANDARD && loginType != LoginConstants.TYPE_RECONNECTION) { - throw new IOException("Invalid login type."); + writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION); + return; } reconnecting = loginType == LoginConstants.TYPE_RECONNECTION; @@ -125,9 +125,8 @@ public final class LoginDecoder extends StatefulFrameDecoder * @param ctx The channel handler context. * @param buffer The buffer. * @param out The {@link List} of objects to pass forward through the pipeline. - * @throws Exception If an error occurs. */ - private void decodePayload(ChannelHandlerContext ctx, ByteBuf buffer, List out) throws Exception { + private void decodePayload(ChannelHandlerContext ctx, ByteBuf buffer, List out) { if (buffer.readableBytes() >= loginLength) { ByteBuf payload = buffer.readBytes(loginLength); int clientVersion = 255 - payload.readUnsignedByte(); @@ -136,7 +135,8 @@ public final class LoginDecoder extends StatefulFrameDecoder int lowMemoryFlag = payload.readUnsignedByte(); if (lowMemoryFlag != 0 && lowMemoryFlag != 1) { - throw new Exception("Invalid value for low memory flag."); + writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION); + return; } boolean lowMemory = lowMemoryFlag == 1; @@ -148,7 +148,8 @@ public final class LoginDecoder extends StatefulFrameDecoder int securePayloadLength = payload.readUnsignedByte(); if (securePayloadLength != loginLength - 41) { - throw new Exception("Secure payload length mismatch."); + writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION); + return; } ByteBuf securePayload = payload.readBytes(securePayloadLength); @@ -160,13 +161,15 @@ public final class LoginDecoder extends StatefulFrameDecoder int secureId = securePayload.readUnsignedByte(); if (secureId != 10) { - throw new Exception("Invalid secure payload id."); + writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION); + return; } long clientSeed = securePayload.readLong(); long reportedServerSeed = securePayload.readLong(); if (reportedServerSeed != serverSeed) { - throw new Exception("Server seed mismatch."); + writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION); + return; } int uid = securePayload.readInt(); @@ -174,10 +177,9 @@ public final class LoginDecoder extends StatefulFrameDecoder String username = BufferUtil.readString(securePayload); String password = BufferUtil.readString(securePayload); - if (password.length() < 6 || password.length() > 20) { - throw new Exception("Invalid password."); - } else if (username.isEmpty() || username.length() > 12) { - throw new Exception("Invalid username."); + if (password.length() < 6 || password.length() > 20 || username.isEmpty() || username.length() > 12) { + writeResponseCode(ctx, LoginConstants.STATUS_INVALID_CREDENTIALS); + return; } int[] seed = new int[4]; @@ -196,14 +198,23 @@ public final class LoginDecoder extends StatefulFrameDecoder 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); + LoginRequest request = new LoginRequest(credentials, randomPair, reconnecting, lowMemory, releaseNumber, archiveCrcs, clientVersion); out.add(request); - if (buffer.isReadable()) { - out.add(buffer.readBytes(buffer.readableBytes())); - } } } + /** + * 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. + */ + private void writeResponseCode(ChannelHandlerContext ctx, int responseCode) { + ByteBuf buffer = ctx.alloc().buffer(1); + buffer.writeByte(responseCode); + + ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE); + } + } \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/FlashTabInterfaceMessageEncoder.java b/src/org/apollo/net/release/r317/FlashTabInterfaceMessageEncoder.java new file mode 100644 index 00000000..1bc1d698 --- /dev/null +++ b/src/org/apollo/net/release/r317/FlashTabInterfaceMessageEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.FlashTabInterfaceMessage; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link FlashTabInterfaceMessage}. + * + * @author Major + */ +public final class FlashTabInterfaceMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(FlashTabInterfaceMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(24); + builder.put(DataType.BYTE, DataTransformation.SUBTRACT, message.getTab()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/FlashingTabClickedMessageDecoder.java b/src/org/apollo/net/release/r317/FlashingTabClickedMessageDecoder.java new file mode 100644 index 00000000..780be27c --- /dev/null +++ b/src/org/apollo/net/release/r317/FlashingTabClickedMessageDecoder.java @@ -0,0 +1,23 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.FlashingTabClickedMessage; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.MessageDecoder; + +/** + * A {@link MessageDecoder} for the {@link FlashingTabClickedMessage}. + * + * @author Major + */ +public final class FlashingTabClickedMessageDecoder extends MessageDecoder { + + @Override + public FlashingTabClickedMessage decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int tab = (int) reader.getUnsigned(DataType.BYTE); + return new FlashingTabClickedMessage(tab); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/OpenDialogueOverlayMessageEncoder.java b/src/org/apollo/net/release/r317/OpenDialogueOverlayMessageEncoder.java new file mode 100644 index 00000000..a53afa78 --- /dev/null +++ b/src/org/apollo/net/release/r317/OpenDialogueOverlayMessageEncoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.OpenDialogueOverlayMessage; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link OpenDialogueOverlayMessage}. + * + * @author Major + */ +public final class OpenDialogueOverlayMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(OpenDialogueOverlayMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(218); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, message.getInterfaceId()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/OpenOverlayMessageEncoder.java b/src/org/apollo/net/release/r317/OpenOverlayMessageEncoder.java new file mode 100644 index 00000000..73ab99e9 --- /dev/null +++ b/src/org/apollo/net/release/r317/OpenOverlayMessageEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.OpenOverlayMessage; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link OpenOverlayMessage}. + * + * @author Major + */ +public final class OpenOverlayMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(OpenOverlayMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(208); + builder.put(DataType.SHORT, DataOrder.LITTLE, message.getOverlayId()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/OpenSidebarMessageEncoder.java b/src/org/apollo/net/release/r317/OpenSidebarMessageEncoder.java new file mode 100644 index 00000000..fb2c0665 --- /dev/null +++ b/src/org/apollo/net/release/r317/OpenSidebarMessageEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.OpenSidebarMessage; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link OpenSidebarMessage}. + * + * @author Major + */ +public final class OpenSidebarMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(OpenSidebarMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(142); + builder.put(DataType.SHORT, DataOrder.LITTLE, message.getSidebarId()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/PlayerDesignMessageDecoder.java b/src/org/apollo/net/release/r317/PlayerDesignMessageDecoder.java index 9aaaaf51..52d684ef 100644 --- a/src/org/apollo/net/release/r317/PlayerDesignMessageDecoder.java +++ b/src/org/apollo/net/release/r317/PlayerDesignMessageDecoder.java @@ -2,7 +2,7 @@ package org.apollo.net.release.r317; import org.apollo.game.message.impl.PlayerDesignMessage; import org.apollo.game.model.Appearance; -import org.apollo.game.model.setting.Gender; +import org.apollo.game.model.entity.setting.Gender; import org.apollo.net.codec.game.DataType; import org.apollo.net.codec.game.GamePacket; import org.apollo.net.codec.game.GamePacketReader; diff --git a/src/org/apollo/net/release/r317/PlayerSynchronizationMessageEncoder.java b/src/org/apollo/net/release/r317/PlayerSynchronizationMessageEncoder.java index 5908d2e9..a1a55d14 100644 --- a/src/org/apollo/net/release/r317/PlayerSynchronizationMessageEncoder.java +++ b/src/org/apollo/net/release/r317/PlayerSynchronizationMessageEncoder.java @@ -9,8 +9,8 @@ import org.apollo.game.model.Item; import org.apollo.game.model.Position; import org.apollo.game.model.def.EquipmentDefinition; import org.apollo.game.model.entity.EquipmentConstants; +import org.apollo.game.model.entity.setting.Gender; import org.apollo.game.model.inv.Inventory; -import org.apollo.game.model.setting.Gender; import org.apollo.game.sync.block.AnimationBlock; import org.apollo.game.sync.block.AppearanceBlock; import org.apollo.game.sync.block.ChatBlock; diff --git a/src/org/apollo/net/release/r317/Release317.java b/src/org/apollo/net/release/r317/Release317.java index e1abf5d8..39c77ab4 100644 --- a/src/org/apollo/net/release/r317/Release317.java +++ b/src/org/apollo/net/release/r317/Release317.java @@ -7,6 +7,7 @@ import org.apollo.game.message.impl.ConfigMessage; import org.apollo.game.message.impl.DisplayCrossbonesMessage; import org.apollo.game.message.impl.DisplayTabInterfaceMessage; import org.apollo.game.message.impl.EnterAmountMessage; +import org.apollo.game.message.impl.FlashTabInterfaceMessage; import org.apollo.game.message.impl.ForwardPrivateChatMessage; import org.apollo.game.message.impl.FriendServerStatusMessage; import org.apollo.game.message.impl.HintIconMessage; @@ -15,8 +16,11 @@ import org.apollo.game.message.impl.IgnoreListMessage; import org.apollo.game.message.impl.LogoutMessage; import org.apollo.game.message.impl.NpcSynchronizationMessage; import org.apollo.game.message.impl.OpenDialogueInterfaceMessage; +import org.apollo.game.message.impl.OpenDialogueOverlayMessage; import org.apollo.game.message.impl.OpenInterfaceMessage; import org.apollo.game.message.impl.OpenInterfaceSidebarMessage; +import org.apollo.game.message.impl.OpenOverlayMessage; +import org.apollo.game.message.impl.OpenSidebarMessage; import org.apollo.game.message.impl.PlayerSynchronizationMessage; import org.apollo.game.message.impl.PositionMessage; import org.apollo.game.message.impl.PrivacyOptionMessage; @@ -124,6 +128,7 @@ public final class Release317 extends Release { register(130, new ClosedInterfaceMessageDecoder()); register(208, new EnteredAmountMessageDecoder()); register(40, new DialogueContinueMessageDecoder()); + register(120, new FlashingTabClickedMessageDecoder()); register(53, new ItemOnItemMessageDecoder()); register(237, new MagicOnItemMessageDecoder()); @@ -207,5 +212,10 @@ public final class Release317 extends Release { register(IgnoreListMessage.class, new IgnoreListMessageEncoder()); register(SendFriendMessage.class, new SendFriendMessageEncoder()); register(HintIconMessage.class, new HintIconMessageEncoder()); + register(FlashTabInterfaceMessage.class, new FlashTabInterfaceMessageEncoder()); + register(OpenSidebarMessage.class, new OpenSidebarMessageEncoder()); + register(OpenOverlayMessage.class, new OpenOverlayMessageEncoder()); + register(OpenDialogueOverlayMessage.class, new OpenDialogueOverlayMessageEncoder()); } + } \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/FlashTabInterfaceMessageEncoder.java b/src/org/apollo/net/release/r377/FlashTabInterfaceMessageEncoder.java new file mode 100644 index 00000000..b64eeaff --- /dev/null +++ b/src/org/apollo/net/release/r377/FlashTabInterfaceMessageEncoder.java @@ -0,0 +1,23 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.FlashTabInterfaceMessage; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link FlashTabInterfaceMessage}. + * + * @author Major + */ +public final class FlashTabInterfaceMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(FlashTabInterfaceMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(283); + builder.put(DataType.BYTE, message.getTab()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/FlashingTabClickedMessageDecoder.java b/src/org/apollo/net/release/r377/FlashingTabClickedMessageDecoder.java new file mode 100644 index 00000000..7d97ea93 --- /dev/null +++ b/src/org/apollo/net/release/r377/FlashingTabClickedMessageDecoder.java @@ -0,0 +1,23 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.FlashingTabClickedMessage; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.MessageDecoder; + +/** + * A {@link MessageDecoder} for the {@link FlashingTabClickedMessage}. + * + * @author Major + */ +public final class FlashingTabClickedMessageDecoder extends MessageDecoder { + + @Override + public FlashingTabClickedMessage decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int tab = (int) reader.getUnsigned(DataType.BYTE); + return new FlashingTabClickedMessage(tab); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/OpenDialogueOverlayMessageEncoder.java b/src/org/apollo/net/release/r377/OpenDialogueOverlayMessageEncoder.java new file mode 100644 index 00000000..aff73005 --- /dev/null +++ b/src/org/apollo/net/release/r377/OpenDialogueOverlayMessageEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.OpenDialogueOverlayMessage; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link OpenDialogueOverlayMessage}. + * + * @author Major + */ +public final class OpenDialogueOverlayMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(OpenDialogueOverlayMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(158); + builder.put(DataType.SHORT, DataOrder.LITTLE, message.getInterfaceId()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/OpenOverlayMessageEncoder.java b/src/org/apollo/net/release/r377/OpenOverlayMessageEncoder.java new file mode 100644 index 00000000..45d210af --- /dev/null +++ b/src/org/apollo/net/release/r377/OpenOverlayMessageEncoder.java @@ -0,0 +1,23 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.OpenOverlayMessage; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link OpenOverlayMessage}. + * + * @author Major + */ +public final class OpenOverlayMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(OpenOverlayMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(50); + builder.put(DataType.SHORT, message.getOverlayId()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/OpenSidebarMessageEncoder.java b/src/org/apollo/net/release/r377/OpenSidebarMessageEncoder.java new file mode 100644 index 00000000..fc80bda5 --- /dev/null +++ b/src/org/apollo/net/release/r377/OpenSidebarMessageEncoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.OpenSidebarMessage; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link OpenSidebarMessage}. + * + * @author Major + */ +public final class OpenSidebarMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(OpenSidebarMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(246); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, message.getSidebarId()); + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/PlayerDesignMessageDecoder.java b/src/org/apollo/net/release/r377/PlayerDesignMessageDecoder.java index 0572398e..61593731 100644 --- a/src/org/apollo/net/release/r377/PlayerDesignMessageDecoder.java +++ b/src/org/apollo/net/release/r377/PlayerDesignMessageDecoder.java @@ -2,7 +2,7 @@ package org.apollo.net.release.r377; import org.apollo.game.message.impl.PlayerDesignMessage; import org.apollo.game.model.Appearance; -import org.apollo.game.model.setting.Gender; +import org.apollo.game.model.entity.setting.Gender; import org.apollo.net.codec.game.DataType; import org.apollo.net.codec.game.GamePacket; import org.apollo.net.codec.game.GamePacketReader; diff --git a/src/org/apollo/net/release/r377/PlayerSynchronizationMessageEncoder.java b/src/org/apollo/net/release/r377/PlayerSynchronizationMessageEncoder.java index cc68abd8..341a1b17 100644 --- a/src/org/apollo/net/release/r377/PlayerSynchronizationMessageEncoder.java +++ b/src/org/apollo/net/release/r377/PlayerSynchronizationMessageEncoder.java @@ -9,8 +9,8 @@ import org.apollo.game.model.Item; import org.apollo.game.model.Position; import org.apollo.game.model.def.EquipmentDefinition; import org.apollo.game.model.entity.EquipmentConstants; +import org.apollo.game.model.entity.setting.Gender; import org.apollo.game.model.inv.Inventory; -import org.apollo.game.model.setting.Gender; import org.apollo.game.sync.block.AnimationBlock; import org.apollo.game.sync.block.AppearanceBlock; import org.apollo.game.sync.block.ChatBlock; diff --git a/src/org/apollo/net/release/r377/Release377.java b/src/org/apollo/net/release/r377/Release377.java index 72bf88f7..01b663db 100644 --- a/src/org/apollo/net/release/r377/Release377.java +++ b/src/org/apollo/net/release/r377/Release377.java @@ -7,6 +7,7 @@ import org.apollo.game.message.impl.ConfigMessage; import org.apollo.game.message.impl.DisplayCrossbonesMessage; import org.apollo.game.message.impl.DisplayTabInterfaceMessage; import org.apollo.game.message.impl.EnterAmountMessage; +import org.apollo.game.message.impl.FlashTabInterfaceMessage; import org.apollo.game.message.impl.ForwardPrivateChatMessage; import org.apollo.game.message.impl.FriendServerStatusMessage; import org.apollo.game.message.impl.HintIconMessage; @@ -15,8 +16,11 @@ import org.apollo.game.message.impl.IgnoreListMessage; import org.apollo.game.message.impl.LogoutMessage; import org.apollo.game.message.impl.NpcSynchronizationMessage; import org.apollo.game.message.impl.OpenDialogueInterfaceMessage; +import org.apollo.game.message.impl.OpenDialogueOverlayMessage; import org.apollo.game.message.impl.OpenInterfaceMessage; import org.apollo.game.message.impl.OpenInterfaceSidebarMessage; +import org.apollo.game.message.impl.OpenOverlayMessage; +import org.apollo.game.message.impl.OpenSidebarMessage; import org.apollo.game.message.impl.PlayerSynchronizationMessage; import org.apollo.game.message.impl.PositionMessage; import org.apollo.game.message.impl.PrivacyOptionMessage; @@ -124,6 +128,7 @@ public final class Release377 extends Release { register(110, new ClosedInterfaceMessageDecoder()); register(75, new EnteredAmountMessageDecoder()); register(226, new DialogueContinueMessageDecoder()); + register(119, new FlashingTabClickedMessageDecoder()); register(1, new ItemOnItemMessageDecoder()); register(36, new MagicOnItemMessageDecoder()); @@ -203,6 +208,10 @@ public final class Release377 extends Release { register(IgnoreListMessage.class, new IgnoreListMessageEncoder()); register(SendFriendMessage.class, new SendFriendMessageEncoder()); register(HintIconMessage.class, new HintIconMessageEncoder()); + register(FlashTabInterfaceMessage.class, new FlashTabInterfaceMessageEncoder()); + register(OpenSidebarMessage.class, new OpenSidebarMessageEncoder()); + register(OpenOverlayMessage.class, new OpenOverlayMessageEncoder()); + register(OpenDialogueOverlayMessage.class, new OpenDialogueOverlayMessageEncoder()); } } \ No newline at end of file diff --git a/src/org/apollo/net/session/LoginSession.java b/src/org/apollo/net/session/LoginSession.java index 1d0712aa..3fb9c221 100644 --- a/src/org/apollo/net/session/LoginSession.java +++ b/src/org/apollo/net/session/LoginSession.java @@ -85,37 +85,34 @@ public final class LoginSession extends Session { * @param response The response. */ public void handlePlayerLoaderResponse(LoginRequest request, PlayerLoaderResponse response) { - GameService gameService = serverContext.getService(GameService.class); + GameService service = serverContext.getService(GameService.class); Channel channel = getChannel(); - Optional responsePlayer = response.getPlayer(); + Optional optional = response.getPlayer(); int status = response.getStatus(), rights = 0; boolean flagged = false; - if (responsePlayer.isPresent()) { - Player player = responsePlayer.get(); + if (optional.isPresent()) { + Player player = optional.get(); rights = player.getPrivilegeLevel().toInteger(); GameSession session = new GameSession(channel, serverContext, player); - player.setSession(session, false /* TODO */); + RegistrationStatus registration = service.registerPlayer(player, session); - RegistrationStatus registrationStatus = gameService.registerPlayer(player); - - if (registrationStatus != RegistrationStatus.OK) { - responsePlayer = Optional.empty(); + if (registration != RegistrationStatus.OK) { + optional = Optional.empty(); rights = 0; - if (registrationStatus == RegistrationStatus.ALREADY_ONLINE) { - status = LoginConstants.STATUS_ACCOUNT_ONLINE; - } else { - status = LoginConstants.STATUS_SERVER_FULL; - } + + status = (registration == RegistrationStatus.ALREADY_ONLINE) ? LoginConstants.STATUS_ACCOUNT_ONLINE + : LoginConstants.STATUS_SERVER_FULL; } } ChannelFuture future = channel.writeAndFlush(new LoginResponse(status, rights, flagged)); + destroy(); - if (responsePlayer.isPresent()) { + if (optional.isPresent()) { IsaacRandomPair randomPair = request.getRandomPair(); Release release = serverContext.getRelease(); @@ -129,10 +126,14 @@ public final class LoginSession extends Session { channel.pipeline().remove("loginDecoder"); channel.pipeline().remove("loginEncoder"); - channelContext.attr(NetworkConstants.SESSION_KEY).set(responsePlayer.get().getSession()); + channelContext.attr(NetworkConstants.SESSION_KEY).set(optional.get().getSession()); } else { future.addListener(ChannelFutureListener.CLOSE); } + + if (optional.isPresent() && !request.isReconnecting()) { + optional.get().sendInitialMessages(); + } } @Override diff --git a/src/org/apollo/tools/NoteUpdater.java b/src/org/apollo/tools/NoteUpdater.java deleted file mode 100644 index 1c918b24..00000000 --- a/src/org/apollo/tools/NoteUpdater.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.apollo.tools; - -import java.io.BufferedOutputStream; -import java.io.DataOutputStream; -import java.io.FileOutputStream; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; - -import org.apollo.fs.IndexedFileSystem; -import org.apollo.fs.decoder.ItemDefinitionDecoder; -import org.apollo.game.model.def.ItemDefinition; - -import com.google.common.base.Preconditions; - -/** - * A tool for updating the note data. - * - * @author Graham - */ -public final class NoteUpdater { - - /** - * The entry point of the application. - * - * @param args The command line arguments. - * @throws Exception If an error occurs. - */ - public static void main(String[] args) throws Exception { - Preconditions.checkArgument(args.length == 1, "Usage:\njava -cp ... org.apollo.tools.NoteUpdater [release]."); - String release = args[0]; - - try (DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data/note-" + release - + ".dat"))); - IndexedFileSystem fs = new IndexedFileSystem(Paths.get("data/fs/", release), true)) { - ItemDefinitionDecoder decoder = new ItemDefinitionDecoder(fs); - ItemDefinition[] defs = decoder.decode(); - ItemDefinition.init(defs); - - os.writeShort(defs.length); - Map itemToNote = new HashMap<>(); - - for (int id = 0; id < defs.length; id++) { - ItemDefinition def = ItemDefinition.lookup(id); - if (def.isNote()) { - itemToNote.put(def.getNoteInfoId(), def.getId()); - } - } - - for (int id = 0; id < defs.length; id++) { - if (itemToNote.containsKey(id)) { - os.writeBoolean(true); // notable - os.writeShort(itemToNote.get(id)); - } else { - os.writeBoolean(false); // not notable - } - } - } - } - -} \ No newline at end of file diff --git a/src/org/apollo/update/HttpRequestWorker.java b/src/org/apollo/update/HttpRequestWorker.java index 5c7e82bb..b57f41b1 100644 --- a/src/org/apollo/update/HttpRequestWorker.java +++ b/src/org/apollo/update/HttpRequestWorker.java @@ -9,11 +9,13 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; -import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Date; +import java.util.Optional; import org.apollo.fs.IndexedFileSystem; import org.apollo.update.resource.CombinedResourceProvider; @@ -21,6 +23,8 @@ import org.apollo.update.resource.HypertextResourceProvider; import org.apollo.update.resource.ResourceProvider; import org.apollo.update.resource.VirtualResourceProvider; +import com.google.common.base.Charsets; + /** * A worker which services HTTP requests. * @@ -31,7 +35,7 @@ public final class HttpRequestWorker extends RequestWorker buf = provider.get(path); - ByteBuf wrapped; HttpResponseStatus status = HttpResponseStatus.OK; - String mime = getMimeType(request.getUri()); - if (buf == null) { + if (!buf.isPresent()) { status = HttpResponseStatus.NOT_FOUND; - wrapped = createErrorPage(status, "The page you requested could not be found."); mime = "text/html"; - } else { - wrapped = Unpooled.wrappedBuffer(buf); } + ByteBuf wrapped = buf.isPresent() ? Unpooled.wrappedBuffer(buf.get()) : createErrorPage(status, "The page you requested could not be found."); + HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), status); response.headers().set("Date", new Date()); diff --git a/src/org/apollo/update/JagGrabRequestWorker.java b/src/org/apollo/update/JagGrabRequestWorker.java index f763f7f8..6f8c9528 100644 --- a/src/org/apollo/update/JagGrabRequestWorker.java +++ b/src/org/apollo/update/JagGrabRequestWorker.java @@ -7,6 +7,7 @@ import io.netty.channel.ChannelFutureListener; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Optional; import org.apollo.fs.IndexedFileSystem; import org.apollo.net.codec.jaggrab.JagGrabRequest; @@ -38,12 +39,13 @@ public final class JagGrabRequestWorker extends RequestWorker buffer = provider.get(request.getFilePath()); + + if (buffer.isPresent()) { + ByteBuf wrapped = Unpooled.wrappedBuffer(buffer.get()); channel.writeAndFlush(new JagGrabResponse(wrapped)).addListener(ChannelFutureListener.CLOSE); + } else { + channel.close(); } } diff --git a/src/org/apollo/update/resource/CombinedResourceProvider.java b/src/org/apollo/update/resource/CombinedResourceProvider.java index b2487565..1395c55f 100644 --- a/src/org/apollo/update/resource/CombinedResourceProvider.java +++ b/src/org/apollo/update/resource/CombinedResourceProvider.java @@ -2,13 +2,14 @@ package org.apollo.update.resource; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Optional; /** * A resource provider composed of multiple resource providers. * * @author Graham */ -public final class CombinedResourceProvider extends ResourceProvider { +public final class CombinedResourceProvider implements ResourceProvider { /** * An array of resource providers. @@ -30,13 +31,13 @@ public final class CombinedResourceProvider extends ResourceProvider { } @Override - public ByteBuffer get(String path) throws IOException { + public Optional get(String path) throws IOException { for (ResourceProvider provider : providers) { if (provider.accept(path)) { return provider.get(path); } } - return null; + return Optional.empty(); } } \ No newline at end of file diff --git a/src/org/apollo/update/resource/HypertextResourceProvider.java b/src/org/apollo/update/resource/HypertextResourceProvider.java index 7b6fed1f..7dda56e9 100644 --- a/src/org/apollo/update/resource/HypertextResourceProvider.java +++ b/src/org/apollo/update/resource/HypertextResourceProvider.java @@ -1,62 +1,67 @@ package org.apollo.update.resource; -import java.io.File; import java.io.IOException; -import java.io.RandomAccessFile; import java.net.URI; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; /** * A {@link ResourceProvider} which provides additional hypertext resources. * * @author Graham */ -public final class HypertextResourceProvider extends ResourceProvider { +public final class HypertextResourceProvider implements ResourceProvider { /** - * The base directory from which documents are served. + * The base {@link Path} from which documents are served. */ - private final File base; + private final Path base; /** * Creates a new hypertext resource provider with the specified base directory. * * @param base The base directory. */ - public HypertextResourceProvider(File base) { + public HypertextResourceProvider(Path base) { this.base = base; } @Override public boolean accept(String path) throws IOException { - File file = new File(base, path); - URI target = file.toURI().normalize(); - if (target.toASCIIString().startsWith(base.toURI().normalize().toASCIIString())) { - if (file.isDirectory()) { - file = new File(file, "index.html"); - } - return file.exists(); + Path file = base.resolve(path); + + URI target = file.toUri().normalize(); + if (!target.toASCIIString().startsWith(base.toUri().normalize().toASCIIString())) { + return false; } - return false; + + if (Files.isDirectory(file)) { + file = file.resolve("index.html"); + } + + return Files.exists(file); } @Override - public ByteBuffer get(String path) throws IOException { - File file = new File(base, path); - if (file.isDirectory()) { - file = new File(file, "index.html"); - } - if (!file.exists()) { - return null; + public Optional get(String path) throws IOException { + Path root = base.resolve(path); + + if (Files.isDirectory(root)) { + root = root.resolve("index.html"); } - ByteBuffer buffer; - try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { - buffer = raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()); + if (!Files.exists(root)) { + return Optional.empty(); } - return buffer; + try (FileChannel channel = FileChannel.open(root)) { + ByteBuffer buf = channel.map(MapMode.READ_ONLY, 0, Files.size(root)); + return Optional.of(buf); + } } } \ No newline at end of file diff --git a/src/org/apollo/update/resource/ResourceProvider.java b/src/org/apollo/update/resource/ResourceProvider.java index f250b90f..dcb1e254 100644 --- a/src/org/apollo/update/resource/ResourceProvider.java +++ b/src/org/apollo/update/resource/ResourceProvider.java @@ -2,13 +2,14 @@ package org.apollo.update.resource; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Optional; /** * A class which provides resources. * * @author Graham */ -public abstract class ResourceProvider { +public interface ResourceProvider { /** * Checks that this provider can fulfil a request to the specified resource. @@ -17,15 +18,16 @@ public abstract class ResourceProvider { * @return {@code true} if the provider can fulfil a request to the resource, {@code false} otherwise. * @throws IOException If an I/O error occurs. */ - public abstract boolean accept(String path) throws IOException; + public boolean accept(String path) throws IOException; /** - * Gets a resource by its path. + * Gets the resource data, as a {@link ByteBuffer}, wrapped in an {@link Optional}. * - * @param path The path. - * @return The resource, or {@code null} if it doesn't exist. - * @throws IOException If an I/O error occurs. + * @param path The path to the resource. + * @return A {@code ByteBuffer} representation of a resource if it exists otherwise {@link Optional#empty()} is + * returned. + * @throws IOException If some I/O exception occurs. */ - public abstract ByteBuffer get(String path) throws IOException; + public Optional get(String path) throws IOException; } \ No newline at end of file diff --git a/src/org/apollo/update/resource/VirtualResourceProvider.java b/src/org/apollo/update/resource/VirtualResourceProvider.java index 51d76326..354d8927 100644 --- a/src/org/apollo/update/resource/VirtualResourceProvider.java +++ b/src/org/apollo/update/resource/VirtualResourceProvider.java @@ -2,6 +2,10 @@ package org.apollo.update.resource; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import org.apollo.fs.IndexedFileSystem; @@ -11,13 +15,12 @@ import org.apollo.fs.IndexedFileSystem; * * @author Graham */ -public final class VirtualResourceProvider extends ResourceProvider { +public final class VirtualResourceProvider implements ResourceProvider { /** - * An array of valid prefixes. + * A {@link List} of valid prefixes. */ - private static final String[] VALID_PREFIXES = { "crc", "title", "config", "interface", "media", "versionlist", "textures", - "wordenc", "sounds" }; + private static final List VALID_PREFIXES = Arrays.asList("/crc", "/title", "/config", "/interface", "/media", "/versionlist", "/textures", "/wordenc", "/sounds"); /** * The file system. @@ -26,7 +29,7 @@ public final class VirtualResourceProvider extends ResourceProvider { /** * Creates a new virtual resource provider with the specified file system. - * + * * @param fs The file system. */ public VirtualResourceProvider(IndexedFileSystem fs) { @@ -35,36 +38,34 @@ public final class VirtualResourceProvider extends ResourceProvider { @Override public boolean accept(String path) throws IOException { - for (String prefix : VALID_PREFIXES) { - if (path.startsWith("/" + prefix)) { - return true; - } - } - return false; + Objects.requireNonNull(path); + + return VALID_PREFIXES.stream().anyMatch(path::startsWith); } @Override - public ByteBuffer get(String path) throws IOException { + public Optional get(String path) throws IOException { if (path.startsWith("/crc")) { - return fs.getCrcTable(); + return Optional.of(fs.getCrcTable()); } else if (path.startsWith("/title")) { - return fs.getFile(0, 1); + return Optional.of(fs.getFile(0, 1)); } else if (path.startsWith("/config")) { - return fs.getFile(0, 2); + return Optional.of(fs.getFile(0, 2)); } else if (path.startsWith("/interface")) { - return fs.getFile(0, 3); + return Optional.of(fs.getFile(0, 3)); } else if (path.startsWith("/media")) { - return fs.getFile(0, 4); + return Optional.of(fs.getFile(0, 4)); } else if (path.startsWith("/versionlist")) { - return fs.getFile(0, 5); + return Optional.of(fs.getFile(0, 5)); } else if (path.startsWith("/textures")) { - return fs.getFile(0, 6); + return Optional.of(fs.getFile(0, 6)); } else if (path.startsWith("/wordenc")) { - return fs.getFile(0, 7); + return Optional.of(fs.getFile(0, 7)); } else if (path.startsWith("/sounds")) { - return fs.getFile(0, 8); + return Optional.of(fs.getFile(0, 8)); } - return null; + + return Optional.empty(); } } \ No newline at end of file diff --git a/src/org/apollo/util/StatefulFrameDecoder.java b/src/org/apollo/util/StatefulFrameDecoder.java index c34ee522..18234ec2 100644 --- a/src/org/apollo/util/StatefulFrameDecoder.java +++ b/src/org/apollo/util/StatefulFrameDecoder.java @@ -5,8 +5,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; - -import com.google.common.base.Preconditions; +import java.util.Objects; /** * A stateful implementation of a {@link ByteToMessageDecoder} which may be extended and used by other classes. The @@ -17,7 +16,7 @@ import com.google.common.base.Preconditions; * The current state is supplied as a parameter in the {@link StatefulFrameDecoder#decode} and * {@link StatefulFrameDecoder#decodeLast} methods. * - * This class is not thread safe: it is recommended that the state is only set in the decode methods overriden. + * This class is not thread safe: it is recommended that the state is only set in the decode methods overridden. * * @author Graham * @param The state enumeration. @@ -36,17 +35,6 @@ public abstract class StatefulFrameDecoder> extends ByteToMess * @throws NullPointerException If the state is {@code null}. */ public StatefulFrameDecoder(T state) { - this(state, false); - } - - /** - * Creates the stateful frame decoder with the specified initial state and unwrap flag. - * - * @param state The initial state. - * @param unwrap The unwrap flag. - * @throws NullPointerException If the state is {@code null}. - */ - public StatefulFrameDecoder(T state, boolean unwrap) { setState(state); } @@ -66,24 +54,6 @@ public abstract class StatefulFrameDecoder> extends ByteToMess */ protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List out, T state) throws Exception; - @Override - protected final void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List out) { - decodeLast(ctx, in, out, state); - } - - /** - * Decodes remaining data before the channel is closed into a frame. You may override this method, but it is not - * required. If you do not, remaining data will be discarded! - * - * @param ctx The current context of this handler. - * @param in The cumulative buffer, which may contain zero or more bytes. - * @param out The {@link List} of objects to pass forward through the pipeline. - * @param state The current state. The state may be changed by calling {@link #setState}. - */ - protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List out, T state) { - - } - /** * Sets a new state. * @@ -91,8 +61,7 @@ public abstract class StatefulFrameDecoder> extends ByteToMess * @throws NullPointerException If the state is {@code null}. */ public final void setState(T state) { - Preconditions.checkNotNull(state, "State cannot be null."); - this.state = state; + this.state = Objects.requireNonNull(state, "State cannot be null."); } } \ No newline at end of file diff --git a/src/org/apollo/util/plugin/PluginContext.java b/src/org/apollo/util/plugin/PluginContext.java index 8d88f9f1..a33fe386 100644 --- a/src/org/apollo/util/plugin/PluginContext.java +++ b/src/org/apollo/util/plugin/PluginContext.java @@ -3,24 +3,24 @@ package org.apollo.util.plugin; import org.apollo.ServerContext; import org.apollo.game.GameService; import org.apollo.game.command.CommandListener; -import org.apollo.game.login.LoginListener; -import org.apollo.game.login.LogoutListener; import org.apollo.game.message.Message; import org.apollo.game.message.handler.MessageHandler; import org.apollo.game.message.handler.MessageHandlerChain; import org.apollo.game.message.handler.MessageHandlerChainGroup; import org.apollo.game.model.World; +import org.apollo.game.model.event.EventListener; +import org.apollo.game.model.event.impl.LoginEvent; +import org.apollo.game.model.event.impl.LogoutEvent; /** * The {@link PluginContext} contains methods a plugin can use to interface with the server, for example, by adding * {@link MessageHandler}s to {@link MessageHandlerChain}s. * * @author Graham + * @author Major */ public final class PluginContext { - // TODO move listeners to world? - /** * Adds a {@link CommandListener}. * @@ -32,21 +32,21 @@ public final class PluginContext { } /** - * Adds a {@link LoginListener}. + * Adds an {@link EventListener} for a {@link LoginEvent}. * * @param listener The listener. */ - public static void addLoginListener(LoginListener listener) { - World.getWorld().getLoginDispatcher().register(listener); + public static void addLoginListener(EventListener listener) { + World.getWorld().listenFor(LoginEvent.class, listener); } /** - * Adds a {@link LogoutListener}. + * Adds an {@link EventListener} for a {@link LogoutEvent}. * * @param listener The listener. */ - public static void addLogoutListener(LogoutListener listener) { - World.getWorld().getLogoutDispatcher().register(listener); + public static void addLogoutListener(EventListener listener) { + World.getWorld().listenFor(LogoutEvent.class, listener); } /**