Module: Quake::EntityParser

Defined in:
lib/quake/entity.rb

Overview

Parses the BSP entity string into an array of Entity objects.

Constant Summary collapse

SINGLE_CHAR_TOKENS =
"{})(:'"
FIELD_NAMES =
%w[
  absmax absmin aflag aiment air_finished ammo_cells ammo_nails
  ammo_rockets ammo_shells angles armortype armorvalue attack_finished
  attack_state avelocity axhitme blocked bubble_count button0 button1
  button2 chain classname cnt colormap count currentammo deadflag
  deathtype delay dest dest1 dest2 distance dmg dmg_inflictor dmg_save
  dmg_take effects enemy finalangle finaldest fixangle flags fly_sound
  frags frame goalentity gravity groundentity health height ideal_yaw
  impulse invincible_finished invincible_sound invincible_time
  invisible_finished invisible_sound invisible_time items jump_flag
  killtarget lastruntime lefty light_lev lip ltime mangle map max_health
  maxs maxspeed mdl message mins model modelindex movedir movetarget
  movetype netname nextthink noise noise1 noise2 noise3 noise4 oldenemy
  oldorigin origin owner pain_finished pausetime pos1 pos2 rad_time
  radsuit_finished search_time show_hostile size skin solid sounds
  spawnflags speed state style super_damage_finished super_sound
  super_time swim_flag t_length t_width takedamage target targetname team
  teleport_time think touch trigger_field use v_angle velocity view_ofs
  voided volume wad wait waitmax waitmin walkframe waterlevel watertype
  weapon weaponframe weaponmodel worldtype yaw_speed
].each_with_object({}) { |name, fields| fields[name] = true }.freeze

Class Method Summary collapse

Class Method Details

.build_target_map(entities) ⇒ Object

Build a lookup table: targetname -> [Entity]



496
497
498
499
500
501
502
# File 'lib/quake/entity.rb', line 496

def self.build_target_map(entities)
  map = Hash.new { |h, k| h[k] = [] }
  entities.each do |ent|
    map[ent.targetname] << ent if ent.targetname
  end
  map
end

.decode_string(value) ⇒ Object



491
492
493
# File 'lib/quake/entity.rb', line 491

def self.decode_string(value)
  value.gsub(/\\./) { |match| match == "\\n" ? "\n" : "\\" }
end

.normalize_key(key) ⇒ Object



486
487
488
489
# File 'lib/quake/entity.rb', line 486

def self.normalize_key(key)
  key = "light_lev" if key == "light"
  key.rstrip
end

.parse(entity_string) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/quake/entity.rb', line 393

def self.parse(entity_string)
  return [] if entity_string.nil?

  entities = []
  current = nil
  tokens = tokenize(entity_string)
  index = 0

  while index < tokens.length
    token = tokens[index]
    if current.nil?
      raise "ED_LoadFromFile: found #{token} when expecting {" unless token == "{"

      current = {}
      index += 1
      next
    end

    if token == "}"
      entities << Entity.new(current) if current["classname"]
      current = nil
      index += 1
      next
    end

    key = normalize_key(token)
    index += 1
    raise "ED_ParseEntity: EOF without closing brace" if index >= tokens.length

    value = tokens[index]
    raise "ED_ParseEntity: closing brace without data" if value == "}"

    value = decode_string(value)

    unless key.start_with?("_") || (key != "angle" && !FIELD_NAMES[key])
      if key == "angle"
        current["angles"] = "0 #{value} 0"
      else
        current[key] = value
      end
    end
    index += 1
  end

  raise "ED_ParseEntity: EOF without closing brace" if current

  entities
end

.tokenize(data) ⇒ Object



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/quake/entity.rb', line 442

def self.tokenize(data)
  tokens = []
  index = 0

  while index < data.length
    char = data[index]
    break if char == "\0"

    if char.ord <= 32
      index += 1
      next
    end

    if char == "/" && data[index + 1] == "/"
      index += 2
      index += 1 while index < data.length && data[index] != "\n"
      next
    end

    if char == '"'
      index += 1
      start = index
      index += 1 while index < data.length && data[index] != '"'
      tokens << data[start...index]
      index += 1
      next
    end

    if SINGLE_CHAR_TOKENS.include?(char)
      tokens << char
      index += 1
      next
    end

    start = index
    while index < data.length && data[index].ord > 32 && !SINGLE_CHAR_TOKENS.include?(data[index])
      index += 1
    end
    tokens << data[start...index]
  end

  tokens
end