class Game

Attributes

tick[R]

Public Class Methods

new(args) click to toggle source
# File lib/game_2d/game.rb, line 30
def initialize(args)
  all_storage = Storage.in_home_dir(args[:storage] || DEFAULT_STORAGE)
  @player_storage = all_storage.dir('players')['players']
  @levels_storage = all_storage.dir('levels')
  level_storage = @levels_storage[args[:level]]

  if level_storage.empty?
    @space = GameSpace.new(self).establish_world(
      args[:level],
      nil, # level ID
      args[:width] || WORLD_WIDTH,
      args[:height] || WORLD_HEIGHT)

    @space << Entity::Base.new(*@space.center)

    @space.storage = level_storage
  else
    @space = GameSpace.load(self, level_storage)
  end

  @tick = -1
  @player_actions = Hash.new {|h,tick| h[tick] = Array.new}

  @self_check, @profile, @registry_broadcast_every = args.values_at(
  :self_check, :profile, :registry_broadcast_every)
  @registry_broadcast_every ||= DEFAULT_REGISTRY_BROADCAST_EVERY

  # This should never happen.  It can only happen client-side because a
  # registry update may create an entity before we get around to it in,
  # say, add_npc
  def @space.fire_duplicate_id(old_entity, new_entity)
    raise "#{old_entity} and #{new_entity} have same ID!"
  end

  # This should never happen.  It can only happen client-side because a
  # registry update may delete an entity before we get around to it in
  # purge_doomed_entities
  def @space.fire_entity_not_found(entity)
    raise "Object #{entity} not in registry"
  end

  @port = _create_server_port(self,
    args[:port] || DEFAULT_PORT,
    args[:max_clients] || MAX_CLIENTS)
end

Public Instance Methods

[](id) click to toggle source
# File lib/game_2d/game.rb, line 168
def [](id)
  @space[id]
end
_create_server_port(*args) click to toggle source
# File lib/game_2d/game.rb, line 76
def _create_server_port(*args)
  ServerPort.new *args
end
add_npcs(npcs_json) click to toggle source

Answering request from client

# File lib/game_2d/game.rb, line 148
def add_npcs(npcs_json)
  npcs_json.each {|json| @space << Serializable.from_json(json) }
end
add_player(player_name) click to toggle source
# File lib/game_2d/game.rb, line 101
def add_player(player_name)
  if base = @space.available_base
    player = Entity::Gecko.new(player_name)
    player.x, player.y, player.a = base.x, base.y, base.a
  else
    player = Entity::Ghost.new(player_name)
    player.x, player.y = @space.center
  end
  @space << player

  each_player_conn do |c|
    c.add_player(player, @tick) unless c.player_name == player_name
  end
  player
end
add_player_action(action) click to toggle source
# File lib/game_2d/game.rb, line 180
def add_player_action(action)
  at_tick, player_name = action[:at_tick], action[:player_name]
  unless at_tick
    warn "Received update from #{player_name} without at_tick!"
    at_tick = @tick + 1
  end
  if at_tick <= @tick
    warn "Received update from #{player_name} #{@tick + 1 - at_tick} ticks late"
    at_tick = @tick + 1
  end
  @player_actions[at_tick] << action
end
delete_entities(entities) click to toggle source
# File lib/game_2d/game.rb, line 140
def delete_entities(entities)
  entities.each do |registry_id|
    @space.doom(@space[registry_id])
  end
  @space.purge_doomed_entities
end
each_player_conn() { |pc| ... } click to toggle source
# File lib/game_2d/game.rb, line 131
def each_player_conn
  get_all_players.each {|p| pc = player_connection(p) and yield pc}
end
get_all_npcs() click to toggle source
# File lib/game_2d/game.rb, line 176
def get_all_npcs
  @space.npcs
end
get_all_players() click to toggle source
# File lib/game_2d/game.rb, line 172
def get_all_players
  @space.players
end
player_connection(player) click to toggle source
# File lib/game_2d/game.rb, line 127
def player_connection(player)
  player_name_connection(player.player_name)
end
player_data(player_name) click to toggle source
# File lib/game_2d/game.rb, line 92
def player_data(player_name)
  @player_storage[player_name]
end
player_name_connection(player_name) click to toggle source
# File lib/game_2d/game.rb, line 123
def player_name_connection(player_name)
  @port.player_name_connection(player_name)
end
process_player_actions() click to toggle source
# File lib/game_2d/game.rb, line 193
def process_player_actions
  if actions = @player_actions.delete(@tick)
    actions.each do |action|
      player_name = action.delete :player_name
      conn = player_name_connection(player_name)
      unless conn
        warn "No connection -- dropping move from #{player_name}"
        next
      end
      player_id = conn.player_id
      player = @space[player_id]
      unless player
        warn "No such player #{player_id} -- dropping move from #{player_name}"
        next
      end
      if (move = action[:move])
        player.add_move move
      elsif (npcs = action[:add_npcs])
        add_npcs npcs
      elsif (entities = action[:update_entities])
        update_npcs entities
      elsif (entities = action[:delete_entities])
        delete_entities entities
      elsif (entity_id = action[:snap_to_grid])
        @space.snap_to_grid entity_id.to_sym
      else
        warn "IGNORING BAD DATA from #{player_name}: #{action.inspect}"
      end
    end
  end
end
replace_player_entity(player_name, new_player_id) click to toggle source
# File lib/game_2d/game.rb, line 117
def replace_player_entity(player_name, new_player_id)
  conn = player_name_connection(player_name)
  old = conn.player_id
  conn.player_id = new_player_id
end
run() click to toggle source
# File lib/game_2d/game.rb, line 288
def run
  run_start = Time.now.to_r
  loop do
    TICKS_PER_SECOND.times do |n|
      update

      # This results in something approaching TICKS_PER_SECOND
      @port.update_until(run_start + Rational(@tick, TICKS_PER_SECOND))

      warn "Updates per second: #{@tick / (Time.now.to_r - run_start)}" if @profile
    end # times
  end # infinite loop
end
save() click to toggle source
# File lib/game_2d/game.rb, line 88
def save
  @space.save
end
send_full_updates() click to toggle source

New players always get a full update (with some additional information) Everyone else gets full registry dump every N ticks, where N == @registry_broadcast_every

# File lib/game_2d/game.rb, line 257
def send_full_updates
  # Set containing brand-new players' IDs
  # This is cleared after we read it
  new_players = @port.new_players

  each_player_conn do |pc|
    if new_players.include? pc.player_name
      response = {
        :you_are => pc.player_id,
        :world => {
          :world_name => world_name,
          :world_id => world_id,
          :highest_id => world_highest_id,
          :cell_width => world_cell_width,
          :cell_height => world_cell_height,
        },
        :add_players => get_all_players,
        :add_npcs => get_all_npcs,
        :at_tick => tick,
      }
      pc.send_record response, true # answer login reliably
    elsif @registry_broadcast_every > 0 && (@tick % @registry_broadcast_every == 0)
      pc.send_record( {
        :registry => @space.all_registered,
        :highest_id => @space.highest_id,
        :at_tick => @tick,
      }, false, 1 )
    end
  end
end
send_player_gone(toast) click to toggle source
# File lib/game_2d/game.rb, line 135
def send_player_gone(toast)
  @space.doom toast
  each_player_conn {|pc| pc.delete_entity toast, @tick }
end
send_updated_entities(*entities) click to toggle source
# File lib/game_2d/game.rb, line 164
def send_updated_entities(*entities)
  each_player_conn {|pc| pc.update_entities entities, @tick }
end
store_player_data(player_name, data) click to toggle source
# File lib/game_2d/game.rb, line 96
def store_player_data(player_name, data)
  @player_storage[player_name] = data
  @player_storage.save
end
update() click to toggle source
# File lib/game_2d/game.rb, line 225
def update
  @tick += 1

  # This will:
  # 1) Queue up player actions for existing players
  #    (create_npc included)
  # 2) Add new players in response to login messages
  # 3) Remove players in response to disconnections
  @port.update

  # This will execute player moves, and create NPCs
  process_player_actions

  # Objects that exist by now will be updated
  # Objects created during this update won't be updated
  # themselves this tick
  @space.update

  # Do this at the end, so the update contains all the
  # latest and greatest news
  send_full_updates

  if @self_check
    @space.check_for_grid_corruption
    @space.check_for_registry_leaks
  end
end
update_npcs(npcs_json) click to toggle source
# File lib/game_2d/game.rb, line 152
def update_npcs(npcs_json)
  npcs_json.each do |json|
    id = json[:registry_id]
    if entity = @space[id]
      entity.update_from_json json
      entity.grab!
    else
      warn "Can't update #{id}, doesn't exist"
    end
  end
end
world_cell_height() click to toggle source
# File lib/game_2d/game.rb, line 86
def world_cell_height; @space.cell_height; end
world_cell_width() click to toggle source
# File lib/game_2d/game.rb, line 85
def world_cell_width; @space.cell_width; end
world_highest_id() click to toggle source
# File lib/game_2d/game.rb, line 84
def world_highest_id; @space.highest_id; end
world_id() click to toggle source
# File lib/game_2d/game.rb, line 83
def world_id; @space.world_id; end
world_name() click to toggle source
# File lib/game_2d/game.rb, line 82
def world_name; @space.world_name; end