class Rsrb::World::Instance

Attributes

door_manager[R]
event_manager[R]
items[R]
loader[R]
npcs[R]
object_manager[R]
object_tick[R]
objects[R]
player_tick[R]
players[R]
region_manager[R]
settings[R]
shop_manager[R]

Public Class Methods

new(label = 'Default') click to toggle source
# File lib/rsrb/world/world.rb, line 20
def initialize(label = 'Default')
  init_loggers
  load_settings

  @label = label
  @players = []
  @npcs = []
  @items = []
  @objects = []

  @player_tick = generate_player_tick_task
  @object_tick = generate_object_tick_task
  @item_tick = generate_item_tick_task

  ##
  # Bad ideas inc.
  @tick_executor = Concurrent::CachedThreadPool.new

  @region_manager = Rsrb::Model::RegionManager.new

  @loader = Rsrb::World::PlayerFileLoader.new
  @shop_manager = Rsrb::Shops::ShopManager.new
  #@door_manager = Rsrb::Doors::DoorManager.new
  log "Initialized world: #{@label}"
rescue StandardError => e
  err!'A fatal error occurred while initializing the world'
  err! e
end

Public Instance Methods

full?() click to toggle source

Is the world full?

# File lib/rsrb/world/world.rb, line 51
def full?
  @players.size >= @settings[:max_players]
end
launch() click to toggle source
# File lib/rsrb/world/world.rb, line 86
def launch
  Rsrb::Item::ItemDefinition.load
  Rsrb::Model::Equipment.load

  shop_manager.load_shops
  #door_manager.load_single_doors
  #door_manager.load_double_doors

  npc = XmlSimple.xml_in('assets/npc_spawns.xml')
  npc['npc'].each_with_index do |row, _idx|
    spawn_npc(row) if @settings[:debug_world]
    log("Spawned #{row}") if @settings[:debug_world]
  end

  items = XmlSimple.xml_in('assets/item_spawns.xml')
  items['item'].each_with_index do |row, _idx|
    @items << Rsrb::Item::Spawn.new(row)
    log("Spawned #{row}") if @settings[:debug_world]
  end

  log 'Launched World!!'
rescue StandardError => e
  err! "A fatal error occurred while launching world: #{self} !"
  err! e
end
receive(session) click to toggle source

Adds a session to the login queue @param session [Rsrb::Net::Session] the session to add

# File lib/rsrb/world/world.rb, line 58
def receive(session)
  begin
    log "Receiving session: #{session}"

    if @players.any? { |player| player.name == session.credentials[:username] }
      log! 'Found duplicate player'
      bldr = Rsrb::Net::PacketBuilder.new(-1, :RAW)
      bldr.add_byte response
      session.connection.send_data bldr.to_packet
      session.connection.close_connection true
    end

    ##
    # TODO: Temp. send bad credentials code if bad
    return unless @loader.validate_credentials(session.credentials)

    player = Rsrb::Model::Player.new(session)
    log "Made player #{player}" if @settings[:debug_world]
    session.player = @loader.load_profile(player)
    log "Registering #{session.player}"
    register(session.player)
  rescue StandardError => e
    err "An error occurred while receiving session for #{session.name}!"
    err e
    return
  end
end
register(player) click to toggle source
# File lib/rsrb/world/world.rb, line 112
def register(player)
  # Register
  player.index = (@players << player).index(player) + 1
  log "Setting #{player} index to #{player.index}" if @settings[:debug_world]

  # Send Login
  player.io.send_login
  player.varp.friends ||= []
  player.varp.ignores ||= []
  #player.var.pm = Rsrb::Content::PM::Presence.new(player)

  HOOKS[:player_login].each do |k, v|
    begin
      log "Running hook #{v}" if @settings[:debug_world]
      v.call(player)
    rescue StandardError => e
      err "Unable to run login hook #{k} for player #{player.name}", e
    end
  end

  log "Registered player #{player.name}!"
end
register_npc(npc) click to toggle source
# File lib/rsrb/world/world.rb, line 156
def register_npc(npc)
  log "Registering npc #{npc.id}" if @settings[:debug_world]
  npc.index = (@npcs << npc).index(npc) + 1
end
reset_ticks() click to toggle source

Attempts to gracefully shutdown the world

# File lib/rsrb/world/world.rb, line 203
def reset_ticks
  log! 'Resetting Ticks!'
  @object_tick.shutdown
  @item_tick.shutdown
  @player_tick.shutdown
  @object_tick = generate_object_tick_task
  @player_tick = generate_player_tick_task
  @item_tick = generate_item_tick_task
  log 'Reset & Regenerated tick tasks.'
end
spawn_npc(data) click to toggle source
# File lib/rsrb/world/world.rb, line 176
def spawn_npc(data)
  npc = Rsrb::NPC::NPC.new(Rsrb::NPC::NPCDefinition.for_id(data['id'].to_i))
  npc.location = Rsrb::Model::Location.new(data['x'].to_i, data['y'].to_i, data['z'].to_i)

  register_npc(npc)

  if data.include?('face')
    npc.direction = data['face'].to_sym

    offsets = DIRECTIONS[npc.direction]
    npc.face(npc.location.transform(offsets[0], offsets[1], 0))
  end
  # Add shop hook if NPC owns a shop
  return unless data.include?('shop')

  handler = HOOKS[:npc_option2][data['id'].to_i]

  return unless handler.instance_of?(Proc)

  on_npc_option2(data['id'].to_i) do |player, mob|
    Rsrb::Shops::ShopManager.open(data['shop'].to_i, player)
    player.interacting_entity = mob
  end
end
submit_task(opts = { count: 1, delay: 0}, &task) click to toggle source
# File lib/rsrb/world/world.rb, line 161
def submit_task(opts = { count: 1, delay: 0}, &task)
  log! "Submitting task #{task}" if @settings[:debug_world]
  Rsrb::World::Task.new(opts) { task.call }
rescue StandardError => e
  err 'An error occurred while submitting a task!', e
end
unregister(player, single = true) click to toggle source
# File lib/rsrb/world/world.rb, line 135
def unregister(player, single = true)
  submit_task do
    return unless @players.include?(player)

    log "Unregistering #{player.name}."
    HOOKS[:player_logout].each do |k, v|
      begin
        log "Running logout hook #{v} for #{player.name}" if @settings[:debug_world]
        v.call(player)
      rescue StandardError => e
        err "Unable to run logout hook #{k} for player #{player.name}", e
      end
    end

    player.destroy
    player.connection.close_connection_after_writing
    @players.delete(player) if single
    @loader.save_profile(player)
  end
end
update(time, result, ex) click to toggle source
# File lib/rsrb/world/world.rb, line 168
def update(time, result, ex)
  if result
    log"(#{result}) Executed successfully." if @settings[:tick_debug]
  else
    err "(#{time}) Execution failed with error #{ex}", ex
  end
end

Private Instance Methods

generate_item_tick_task() click to toggle source

Sets up the item/ground_item respawn/reset tick task. @return [Concurrent::TimerTask] the item tick task.

# File lib/rsrb/world/world.rb, line 290
def generate_item_tick_task
  t = Concurrent::TimerTask.execute(execution_interval: 1) do
    @tick_executor.post do
      @items.each do |item|
        item.respawn -= 1 if item.picked_up

        item.spawn if item.picked_up && item.respawn <= 0
      end
    end
  end
  t.add_observer(self)
  log "Generated & scheduled Item tick task #{t}"
  t
end
generate_object_tick_task() click to toggle source

Sets up the object respawn/reset tick task. @return [Concurrent::TimerTask] the object tick task.

# File lib/rsrb/world/world.rb, line 270
def generate_object_tick_task
  t = Concurrent::TimerTask.execute(execution_interval: 1) do
    @tick_executor.post do
      @objects.each do |object|
        object.delay -= 1
        next unless object.delay <= 0

        object.reset
        @objects.delete(object)
      end
    end
  end
  t.add_observer(self)
  log "Generated & scheduled Object tick task #{t}"
  t
end
generate_player_tick_task() click to toggle source

Sets up the player tick task @return [Concurrent::TimerTask] the player tick task

# File lib/rsrb/world/world.rb, line 231
def generate_player_tick_task
  t = Concurrent::TimerTask.execute(execution_interval: 0.600) do
    @tick_executor.post do
      ticks = []
      updates = []
      resets = []

      @players.each do |p|
        next unless p.index

        ticks << Rsrb::Tasks::PlayerTickTask.new(p)
        resets << Rsrb::Tasks::PlayerResetTask.new(p)
        updates << Rsrb::Tasks::PlayerUpdateTask.new(p)
        updates << Rsrb::Tasks::NPCUpdateTask.new(p)
      end

      @npcs.each do |npc|
        ticks << Rsrb::Tasks::NPCTickTask.new(npc)
        resets << Rsrb::Tasks::NPCResetTask.new(npc)
      end
      ##
      # Tick
      @tick_executor.post { ticks.each(&:execute) }
      ##
      # Update
      @tick_executor.post  { updates.each(&:execute) }
      ##
      # Reset
      @tick_executor.post { resets.each(&:execute) }
    end
  end
  t.add_observer(self)
  log "Generated & scheduled Player tick task #{t}"
  t
end
load_settings() click to toggle source

Loads the settings for the world. Settings are expected to be stored at `$WORKING_DIR/assets/config/.world.env`

# File lib/rsrb/world/world.rb, line 218
def load_settings
  Dotenv.load('assets/config/.world.env')
  @settings = { debug_task: ENV['TASK_DEBUG'].to_i.positive?,
                debug_tick: ENV['TICK_DEBUG'].to_i.positive?,
                debug_world: ENV['WORLD_DEBUG'].to_i.positive?,
                max_players: ENV['MAX_PLAYERS'].to_i,
                max_npcs: ENV['MAX_NPCS'].to_i }.freeze
  log "Read Settings: #{@settings}"
end