Class: Quake::Debug::Script

Inherits:
Object
  • Object
show all
Defined in:
lib/quake/debug/script.rb

Overview

DSL for scripting headless game runs.

Example script.rb:

load_map "maps/e1m1.bsp"
teleport 480, 600, 96, yaw: 90
ticks 30
screenshot "shots/start.png"

hold :w
ticks 60                # 1 second forward
release :w

ticks 30
screenshot "shots/forward.png"
dump_state

Constant Summary collapse

DEFAULT_DT =
1.0 / 60.0
KEY_MAP =
{
  w: SDL::SCANCODE_W, a: SDL::SCANCODE_A,
  s: SDL::SCANCODE_S, d: SDL::SCANCODE_D,
  space: SDL::SCANCODE_SPACE,
  ctrl: SDL::SCANCODE_LCTRL,
  shift: SDL::SCANCODE_LSHIFT,
  c: SDL::SCANCODE_C
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(engine, output: $stdout) ⇒ Script

Returns a new instance of Script.



36
37
38
39
40
# File 'lib/quake/debug/script.rb', line 36

def initialize(engine, output: $stdout)
  @engine = engine
  @output = output
  @held = {}
end

Instance Method Details

#clear_keysObject



96
97
98
99
# File 'lib/quake/debug/script.rb', line 96

def clear_keys
  @held = {}
  @engine.clear_keys
end

#dump(*paths) ⇒ Object

Print just specific fields



127
128
129
130
131
132
133
134
135
# File 'lib/quake/debug/script.rb', line 127

def dump(*paths)
  state = @engine.dump_state
  paths.each do |path|
    value = path.to_s.split(".").inject(state) do |acc, key|
      acc.is_a?(Hash) ? acc[key.to_sym] : nil
    end
    @output.puts "  #{path} = #{value.inspect}"
  end
end

#dump_stateObject



119
120
121
122
123
124
# File 'lib/quake/debug/script.rb', line 119

def dump_state
  state = @engine.dump_state
  @output.puts ">> state"
  @output.puts JSON.pretty_generate(state)
  state
end

#find_entity(classname:, targetname: nil) ⇒ Object



137
138
139
140
141
# File 'lib/quake/debug/script.rb', line 137

def find_entity(classname:, targetname: nil)
  @engine.entities.find do |e|
    e.classname == classname && (targetname.nil? || e.targetname == targetname)
  end
end

#hold(key) ⇒ Object

—- Input —-



78
79
80
81
82
# File 'lib/quake/debug/script.rb', line 78

def hold(key)
  sc = scancode(key)
  @held[sc] = true
  @engine.set_key(sc, true)
end

#list_entities(classname_pattern = nil) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/quake/debug/script.rb', line 143

def list_entities(classname_pattern = nil)
  ents = @engine.entities
  if classname_pattern
    re = Regexp.new(classname_pattern)
    ents = ents.select { |e| e.classname =~ re }
  end
  ents.each do |e|
    @output.puts "  #{e.classname} pos=#{[e.position.x, e.position.y, e.position.z].inspect} " \
                 "target=#{e.target.inspect} targetname=#{e.targetname.inspect}"
  end
  ents.size
end

#load_map(map_name) ⇒ Object

—- Map / level —-



52
53
54
55
# File 'lib/quake/debug/script.rb', line 52

def load_map(map_name)
  @output.puts ">> load_map #{map_name}"
  @engine.load_map(map_name)
end

#log(msg) ⇒ Object



162
163
164
# File 'lib/quake/debug/script.rb', line 162

def log(msg)
  @output.puts msg
end

#noclip(enabled = true) ⇒ Object



72
73
74
# File 'lib/quake/debug/script.rb', line 72

def noclip(enabled = true)
  @engine.player.noclip = enabled
end

#press(key, frames: 2) ⇒ Object



90
91
92
93
94
# File 'lib/quake/debug/script.rb', line 90

def press(key, frames: 2)
  hold(key)
  ticks(frames)
  release(key)
end

#release(key) ⇒ Object



84
85
86
87
88
# File 'lib/quake/debug/script.rb', line 84

def release(key)
  sc = scancode(key)
  @held[sc] = false
  @engine.set_key(sc, false)
end

#run_block(&block) ⇒ Object



46
47
48
# File 'lib/quake/debug/script.rb', line 46

def run_block(&block)
  instance_eval(&block)
end

#run_file(path) ⇒ Object



42
43
44
# File 'lib/quake/debug/script.rb', line 42

def run_file(path)
  instance_eval(File.read(path), path)
end

#screenshot(filename) ⇒ Object

—- Output —-



113
114
115
116
117
# File 'lib/quake/debug/script.rb', line 113

def screenshot(filename)
  FileUtils.mkdir_p(File.dirname(filename)) if filename.include?("/")
  @engine.screenshot(filename)
  @output.puts ">> screenshot #{filename}"
end

#set_pitch(pitch) ⇒ Object



68
69
70
# File 'lib/quake/debug/script.rb', line 68

def set_pitch(pitch)
  @engine.player.instance_variable_set(:@pitch, pitch.to_f)
end

#set_yaw(yaw) ⇒ Object



64
65
66
# File 'lib/quake/debug/script.rb', line 64

def set_yaw(yaw)
  @engine.player.instance_variable_set(:@yaw, yaw.to_f)
end

#teleport(x, y, z, yaw: nil, pitch: nil) ⇒ Object

—- Player control —-



59
60
61
62
# File 'lib/quake/debug/script.rb', line 59

def teleport(x, y, z, yaw: nil, pitch: nil)
  @output.puts ">> teleport (#{x}, #{y}, #{z}) yaw=#{yaw} pitch=#{pitch}"
  @engine.teleport(x, y, z, yaw: yaw, pitch: pitch)
end

#ticks(count, dt: DEFAULT_DT) ⇒ Object

—- Time —-



103
104
105
# File 'lib/quake/debug/script.rb', line 103

def ticks(count, dt: DEFAULT_DT)
  count.to_i.times { @engine.tick(dt) }
end

#trigger(target_name) ⇒ Object

Trigger a brush entity by name (simulates a button press)



157
158
159
160
# File 'lib/quake/debug/script.rb', line 157

def trigger(target_name)
  @engine.brush_game.send(:fire_targets, target_name)
  @output.puts ">> trigger #{target_name}"
end

#wait_seconds(seconds, dt: DEFAULT_DT) ⇒ Object



107
108
109
# File 'lib/quake/debug/script.rb', line 107

def wait_seconds(seconds, dt: DEFAULT_DT)
  ticks((seconds / dt).round, dt: dt)
end