cyberarm_engine 0.25.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -8
  3. data/.rubocop.yml +7 -7
  4. data/.travis.yml +5 -5
  5. data/Gemfile +6 -6
  6. data/Gemfile.lock +37 -25
  7. data/LICENSE.txt +21 -21
  8. data/README.md +74 -74
  9. data/Rakefile +10 -10
  10. data/assets/shaders/fragment/g_buffer.glsl +30 -30
  11. data/assets/shaders/fragment/lighting.glsl +115 -115
  12. data/assets/shaders/include/light_struct.glsl +11 -11
  13. data/assets/shaders/include/material_struct.glsl +16 -16
  14. data/assets/shaders/vertex/g_buffer.glsl +28 -28
  15. data/assets/shaders/vertex/lighting.glsl +24 -24
  16. data/assets/textures/ui/circle.png +0 -0
  17. data/assets/textures/ui/circle_small.png +0 -0
  18. data/assets/textures/ui/rounded.png +0 -0
  19. data/assets/textures/ui/rounded_bottom.png +0 -0
  20. data/assets/textures/ui/rounded_left.png +0 -0
  21. data/assets/textures/ui/rounded_left_bottom.png +0 -0
  22. data/assets/textures/ui/rounded_left_top.png +0 -0
  23. data/assets/textures/ui/rounded_right.png +0 -0
  24. data/assets/textures/ui/rounded_right_bottom.png +0 -0
  25. data/assets/textures/ui/rounded_right_top.png +0 -0
  26. data/assets/textures/ui/rounded_small.png +0 -0
  27. data/assets/textures/ui/rounded_tiny.png +0 -0
  28. data/assets/textures/ui/rounded_top.png +0 -0
  29. data/assets/textures/ui/square.png +0 -0
  30. data/bin/console +14 -14
  31. data/bin/setup +8 -8
  32. data/cyberarm_engine.gemspec +35 -36
  33. data/lib/cyberarm_engine/animator.rb +219 -219
  34. data/lib/cyberarm_engine/background.rb +156 -158
  35. data/lib/cyberarm_engine/background_image.rb +93 -93
  36. data/lib/cyberarm_engine/background_nine_slice.rb +182 -142
  37. data/lib/cyberarm_engine/bounding_box.rb +150 -150
  38. data/lib/cyberarm_engine/builtin/intro_state.rb +130 -130
  39. data/lib/cyberarm_engine/cache/download_manager.rb +123 -123
  40. data/lib/cyberarm_engine/cache.rb +4 -4
  41. data/lib/cyberarm_engine/common.rb +133 -131
  42. data/lib/cyberarm_engine/config_file.rb +46 -46
  43. data/lib/cyberarm_engine/console/command.rb +157 -157
  44. data/lib/cyberarm_engine/console/commands/help_command.rb +43 -43
  45. data/lib/cyberarm_engine/console/subcommand.rb +99 -99
  46. data/lib/cyberarm_engine/console.rb +248 -248
  47. data/lib/cyberarm_engine/event_bus.rb +39 -0
  48. data/lib/cyberarm_engine/game_object.rb +244 -244
  49. data/lib/cyberarm_engine/game_state.rb +134 -124
  50. data/lib/cyberarm_engine/gosu_ext/draw_arc.rb +111 -111
  51. data/lib/cyberarm_engine/gosu_ext/draw_circle.rb +30 -30
  52. data/lib/cyberarm_engine/gosu_ext/draw_path.rb +17 -17
  53. data/lib/cyberarm_engine/model/material.rb +21 -21
  54. data/lib/cyberarm_engine/model/mesh.rb +131 -131
  55. data/lib/cyberarm_engine/model/parser.rb +74 -74
  56. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -138
  57. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -154
  58. data/lib/cyberarm_engine/model.rb +216 -216
  59. data/lib/cyberarm_engine/model_cache.rb +31 -31
  60. data/lib/cyberarm_engine/notification.rb +82 -82
  61. data/lib/cyberarm_engine/notification_manager.rb +241 -241
  62. data/lib/cyberarm_engine/opengl/light.rb +52 -52
  63. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -46
  64. data/lib/cyberarm_engine/opengl/perspective_camera.rb +41 -41
  65. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -249
  66. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +167 -167
  67. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +307 -307
  68. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +33 -33
  69. data/lib/cyberarm_engine/opengl/shader.rb +408 -408
  70. data/lib/cyberarm_engine/opengl/texture.rb +69 -69
  71. data/lib/cyberarm_engine/opengl.rb +53 -53
  72. data/lib/cyberarm_engine/pathfinding/graph.rb +64 -0
  73. data/lib/cyberarm_engine/pathfinding/graph_search.rb +6 -0
  74. data/lib/cyberarm_engine/pathfinding/graph_searches/astar_search.rb +6 -0
  75. data/lib/cyberarm_engine/pathfinding/graph_searches/breadth_first_search.rb +27 -0
  76. data/lib/cyberarm_engine/pathfinding/graph_searches/dijkstra_search.rb +6 -0
  77. data/lib/cyberarm_engine/pathfinding/graphs/grid.rb +52 -0
  78. data/lib/cyberarm_engine/ray.rb +56 -56
  79. data/lib/cyberarm_engine/result.rb +19 -0
  80. data/lib/cyberarm_engine/stats.rb +200 -200
  81. data/lib/cyberarm_engine/text.rb +288 -260
  82. data/lib/cyberarm_engine/timer.rb +27 -23
  83. data/lib/cyberarm_engine/transform.rb +296 -296
  84. data/lib/cyberarm_engine/trees/aabb_node.rb +126 -126
  85. data/lib/cyberarm_engine/trees/aabb_tree.rb +55 -55
  86. data/lib/cyberarm_engine/trees/aabb_tree_debug.rb +29 -29
  87. data/lib/cyberarm_engine/ui/border_canvas.rb +102 -102
  88. data/lib/cyberarm_engine/ui/dialog.rb +24 -0
  89. data/lib/cyberarm_engine/ui/dsl.rb +170 -142
  90. data/lib/cyberarm_engine/ui/element.rb +664 -662
  91. data/lib/cyberarm_engine/ui/elements/button.rb +99 -100
  92. data/lib/cyberarm_engine/ui/elements/check_box.rb +58 -54
  93. data/lib/cyberarm_engine/ui/elements/container.rb +413 -407
  94. data/lib/cyberarm_engine/ui/elements/edit_box.rb +200 -179
  95. data/lib/cyberarm_engine/ui/elements/edit_line.rb +299 -297
  96. data/lib/cyberarm_engine/ui/elements/flow.rb +15 -15
  97. data/lib/cyberarm_engine/ui/elements/image.rb +78 -72
  98. data/lib/cyberarm_engine/ui/elements/list_box.rb +79 -79
  99. data/lib/cyberarm_engine/ui/elements/menu.rb +27 -27
  100. data/lib/cyberarm_engine/ui/elements/menu_item.rb +6 -6
  101. data/lib/cyberarm_engine/ui/elements/progress.rb +95 -93
  102. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -6
  103. data/lib/cyberarm_engine/ui/elements/slider.rb +111 -107
  104. data/lib/cyberarm_engine/ui/elements/stack.rb +11 -11
  105. data/lib/cyberarm_engine/ui/elements/text_block.rb +236 -222
  106. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +71 -67
  107. data/lib/cyberarm_engine/ui/elements/widget.rb +45 -0
  108. data/lib/cyberarm_engine/ui/event.rb +54 -54
  109. data/lib/cyberarm_engine/ui/gui_state.rb +402 -326
  110. data/lib/cyberarm_engine/ui/page.rb +39 -0
  111. data/lib/cyberarm_engine/ui/style.rb +94 -61
  112. data/lib/cyberarm_engine/ui/theme.rb +237 -225
  113. data/lib/cyberarm_engine/vector.rb +312 -312
  114. data/lib/cyberarm_engine/version.rb +4 -4
  115. data/lib/cyberarm_engine/window.rb +234 -195
  116. data/lib/cyberarm_engine.rb +88 -76
  117. data/mrbgem.rake +29 -29
  118. metadata +30 -5
@@ -1,131 +1,133 @@
1
- module CyberarmEngine
2
- module Common
3
- ImageBlob = Data.define(:to_blob, :columns, :rows)
4
-
5
- def push_state(klass, options = {})
6
- window.push_state(klass, options)
7
- end
8
-
9
- def current_state
10
- window.current_state
11
- end
12
-
13
- def previous_state(state = nil)
14
- raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
15
-
16
- i = window.states.index(state || self)
17
- window.states[i - 1] unless (i - 1).negative?
18
- end
19
-
20
- def pop_state
21
- window.pop_state
22
- end
23
-
24
- def shift_state
25
- window.shift_state
26
- end
27
-
28
- def show_cursor
29
- window.show_cursor
30
- end
31
-
32
- def show_cursor=(boolean)
33
- window.show_cursor = boolean
34
- end
35
-
36
- def find_element_by_tag(container, tag, list = [])
37
- return unless container
38
-
39
- container.children.each do |child|
40
- list << child if child.style.tag == tag
41
-
42
- find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
43
- end
44
-
45
- list.first
46
- end
47
-
48
- def draw_rect(x, y, width, height, color, z = 0, mode = :default)
49
- Gosu.draw_rect(x, y, width, height, color, z, mode)
50
- end
51
-
52
- def fill(color, z = 0)
53
- draw_rect(0, 0, window.width, window.height, color, z)
54
- end
55
-
56
- def lighten(color, amount = 25)
57
- if color.respond_to?(:alpha)
58
- Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
59
- else
60
- Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
61
- end
62
- end
63
-
64
- def darken(color, amount = 25)
65
- if color.respond_to?(:alpha)
66
- Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
67
- else
68
- Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
69
- end
70
- end
71
-
72
- def opacity(color, ratio = 1.0)
73
- alpha = 255 * ratio
74
-
75
- Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
76
- end
77
-
78
- def get_asset(path, hash, klass, retro = false, tileable = false)
79
- asset = nil
80
- hash.detect do |_asset, instance|
81
- if _asset == path
82
- asset = instance
83
- true
84
- end
85
- end
86
-
87
- unless asset
88
- instance = nil
89
- instance = if klass == Gosu::Image
90
- path_or_blob = path.is_a?(String) ? path : ImageBlob.new(path.to_blob, path.width, path.height)
91
- klass.new(path_or_blob, retro: retro, tileable: tileable)
92
- else
93
- klass.new(path)
94
- end
95
-
96
- hash[path] = instance
97
- asset = instance
98
- end
99
-
100
- asset
101
- end
102
-
103
- def get_image(path, retro: false, tileable: false)
104
- get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
105
- end
106
-
107
- def get_sample(path)
108
- get_asset(path, Window::SAMPLES, Gosu::Sample)
109
- end
110
-
111
- def get_song(path)
112
- get_asset(path, Window::SONGS, Gosu::Song)
113
- end
114
-
115
- def window
116
- CyberarmEngine::Window.instance
117
- end
118
-
119
- def control_down?
120
- Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
121
- end
122
-
123
- def shift_down?
124
- Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
125
- end
126
-
127
- def alt_down?
128
- Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
129
- end
130
- end
131
- end
1
+ module CyberarmEngine
2
+ GUI_DEBUG = false
3
+
4
+ module Common
5
+ ImageBlob = Data.define(:to_blob, :columns, :rows)
6
+
7
+ def push_state(klass, options = {})
8
+ window.push_state(klass, options)
9
+ end
10
+
11
+ def current_state
12
+ window.current_state
13
+ end
14
+
15
+ def previous_state(state = nil)
16
+ raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
17
+
18
+ i = window.states.index(state || self)
19
+ window.states[i - 1] unless (i - 1).negative?
20
+ end
21
+
22
+ def pop_state
23
+ window.pop_state
24
+ end
25
+
26
+ def shift_state
27
+ window.shift_state
28
+ end
29
+
30
+ def show_cursor
31
+ window.show_cursor
32
+ end
33
+
34
+ def show_cursor=(boolean)
35
+ window.show_cursor = boolean
36
+ end
37
+
38
+ def find_element_by_tag(container, tag, list = [])
39
+ return unless container
40
+
41
+ container.children.each do |child|
42
+ list << child if child.style.tag == tag
43
+
44
+ find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
45
+ end
46
+
47
+ list.first
48
+ end
49
+
50
+ def draw_rect(x, y, width, height, color, z = 0, mode = :default)
51
+ Gosu.draw_rect(x, y, width, height, color, z, mode)
52
+ end
53
+
54
+ def fill(color, z = 0)
55
+ draw_rect(0, 0, window.width, window.height, color, z)
56
+ end
57
+
58
+ def lighten(color, amount = 25)
59
+ if color.respond_to?(:alpha)
60
+ Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
61
+ else
62
+ Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
63
+ end
64
+ end
65
+
66
+ def darken(color, amount = 25)
67
+ if color.respond_to?(:alpha)
68
+ Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
69
+ else
70
+ Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
71
+ end
72
+ end
73
+
74
+ def opacity(color, ratio = 1.0)
75
+ alpha = 255 * ratio
76
+
77
+ Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
78
+ end
79
+
80
+ def get_asset(path, hash, klass, retro = false, tileable = false)
81
+ asset = nil
82
+ hash.detect do |_asset, instance|
83
+ if _asset == path
84
+ asset = instance
85
+ true
86
+ end
87
+ end
88
+
89
+ unless asset
90
+ instance = nil
91
+ instance = if klass == Gosu::Image
92
+ path_or_blob = path.is_a?(String) ? path : ImageBlob.new(path.to_blob, path.width, path.height)
93
+ klass.new(path_or_blob, retro: retro, tileable: tileable)
94
+ else
95
+ klass.new(path)
96
+ end
97
+
98
+ hash[path] = instance
99
+ asset = instance
100
+ end
101
+
102
+ asset
103
+ end
104
+
105
+ def get_image(path, retro: false, tileable: false)
106
+ get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
107
+ end
108
+
109
+ def get_sample(path)
110
+ get_asset(path, Window::SAMPLES, Gosu::Sample)
111
+ end
112
+
113
+ def get_song(path)
114
+ get_asset(path, Window::SONGS, Gosu::Song)
115
+ end
116
+
117
+ def window
118
+ CyberarmEngine::Window.instance
119
+ end
120
+
121
+ def control_down?
122
+ Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
123
+ end
124
+
125
+ def shift_down?
126
+ Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
127
+ end
128
+
129
+ def alt_down?
130
+ Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
131
+ end
132
+ end
133
+ end
@@ -1,46 +1,46 @@
1
- module CyberarmEngine
2
- class ConfigFile
3
- def initialize(file:)
4
- @file = file
5
-
6
- if File.exist?(@file)
7
- deserialize
8
- else
9
- @data = {}
10
- end
11
- end
12
-
13
- def []=(*keys, value)
14
- last_key = keys.last
15
-
16
- if keys.size == 1
17
- hash = @data
18
- else
19
- keys.pop
20
- hash = @data[keys.shift] ||= {}
21
-
22
- keys.each do |key|
23
- hash = hash[key] ||= {}
24
- end
25
- end
26
-
27
- hash[last_key] = value
28
- end
29
-
30
- def get(*keys)
31
- @data.dig(*keys)
32
- end
33
-
34
- def serialize
35
- JSON.dump(@data)
36
- end
37
-
38
- def deserialize
39
- @data = JSON.parse(File.read(@file), symbolize_names: true)
40
- end
41
-
42
- def save!
43
- File.open(@file, "w") { |f| f.write(serialize) }
44
- end
45
- end
46
- end
1
+ module CyberarmEngine
2
+ class ConfigFile
3
+ def initialize(file:)
4
+ @file = file
5
+
6
+ if File.exist?(@file)
7
+ deserialize
8
+ else
9
+ @data = {}
10
+ end
11
+ end
12
+
13
+ def []=(*keys, value)
14
+ last_key = keys.last
15
+
16
+ if keys.size == 1
17
+ hash = @data
18
+ else
19
+ keys.pop
20
+ hash = @data[keys.shift] ||= {}
21
+
22
+ keys.each do |key|
23
+ hash = hash[key] ||= {}
24
+ end
25
+ end
26
+
27
+ hash[last_key] = value
28
+ end
29
+
30
+ def get(*keys)
31
+ @data.dig(*keys)
32
+ end
33
+
34
+ def serialize
35
+ JSON.dump(@data)
36
+ end
37
+
38
+ def deserialize
39
+ @data = JSON.parse(File.read(@file), symbolize_names: true)
40
+ end
41
+
42
+ def save!
43
+ File.open(@file, "w") { |f| f.write(serialize) }
44
+ end
45
+ end
46
+ end
@@ -1,158 +1,158 @@
1
- # frozen_string_literal: true
2
-
3
- module CyberarmEngine
4
- class Console
5
- module Style
6
- def self.error(string)
7
- "<c=ff5555>#{string}</c>"
8
- end
9
-
10
- def self.warn(string)
11
- "<c=ff7700>#{string}</c>"
12
- end
13
-
14
- def self.notice(string)
15
- "<c=55ff55>#{string}</c>"
16
- end
17
-
18
- def self.highlight(string, color = "5555ff")
19
- "<c=#{color}>#{string}</c>"
20
- end
21
- end
22
-
23
- class Command
24
- def self.inherited(subclass)
25
- @list ||= []
26
- @commands ||= []
27
- @list << subclass
28
- end
29
-
30
- def self.setup
31
- @list ||= []
32
- @commands = []
33
- @list.each do |subclass|
34
- cmd = subclass.new
35
- if @commands.detect { |c| c.command == cmd.command }
36
- raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
37
- end
38
-
39
- @commands << cmd
40
- end
41
- end
42
-
43
- def self.use(command, arguments, console)
44
- found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
45
-
46
- if found_command
47
- found_command.handle(arguments, console)
48
- else
49
- console.stdin("Command #{Style.error(command)} not found.")
50
- end
51
- end
52
-
53
- def self.find(command)
54
- @commands.detect { |cmd| cmd.command == command.to_sym }
55
- end
56
-
57
- def self.list_commands
58
- @commands
59
- end
60
-
61
- def initialize
62
- @store = {}
63
- @subcommands = []
64
-
65
- setup
66
- end
67
-
68
- def setup
69
- end
70
-
71
- def subcommand(command, type)
72
- if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
73
- raise "Subcommand '#{command}' for '#{self.command}' already exists!"
74
- end
75
-
76
- @subcommands << SubCommand.new(self, command, type)
77
- end
78
-
79
- def get(key)
80
- @store[key]
81
- end
82
-
83
- def set(key, value)
84
- @store[key] = value
85
- end
86
-
87
- def group
88
- raise NotImplementedError
89
- end
90
-
91
- def command
92
- raise NotImplementedError
93
- end
94
-
95
- def handle(arguments, console)
96
- raise NotImplementedError
97
- end
98
-
99
- def autocomplete(console)
100
- split = console.text_input.text.split(" ")
101
-
102
- if @subcommands.size.positive?
103
- if !console.text_input.text.end_with?(" ") && split.size == 2
104
- list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
105
-
106
- if list.size == 1
107
- console.text_input.text = "#{split.first} #{list.first} "
108
- else
109
- return unless list.size.positive?
110
-
111
- console.stdin(list.map { |cmd| Console::Style.highlight(cmd) }.join(", ").to_s)
112
- end
113
-
114
- # List available options on subcommand
115
- elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
116
- subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
117
-
118
- if subcommand
119
- if split.size == 2
120
- console.stdin("Available options: #{subcommand.values.map { |value| Console::Style.highlight(value) }.join(',')}")
121
- else
122
- list = console.abbrev_search(subcommand.values, split.last)
123
- if list.size == 1
124
- console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
125
- elsif list.size.positive?
126
- console.stdin("Available options: #{list.map { |value| Console::Style.highlight(value) }.join(',')}")
127
- end
128
- end
129
- end
130
-
131
- # List available subcommands if command was entered and has only a space after it
132
- elsif console.text_input.text.end_with?(" ") && split.size == 1
133
- console.stdin("Available subcommands: #{@subcommands.map { |cmd| Console::Style.highlight(cmd.command) }.join(', ')}")
134
- end
135
- end
136
- end
137
-
138
- def handle_subcommand(arguments, console)
139
- if arguments.size.zero?
140
- console.stdin(usage)
141
- return
142
- end
143
- subcommand = arguments.delete_at(0)
144
-
145
- found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
146
- if found_command
147
- found_command.handle(arguments, console)
148
- else
149
- console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
150
- end
151
- end
152
-
153
- def usage
154
- raise NotImplementedError
155
- end
156
- end
157
- end
1
+ # frozen_string_literal: true
2
+
3
+ module CyberarmEngine
4
+ class Console
5
+ module Style
6
+ def self.error(string)
7
+ "<c=ff5555>#{string}</c>"
8
+ end
9
+
10
+ def self.warn(string)
11
+ "<c=ff7700>#{string}</c>"
12
+ end
13
+
14
+ def self.notice(string)
15
+ "<c=55ff55>#{string}</c>"
16
+ end
17
+
18
+ def self.highlight(string, color = "5555ff")
19
+ "<c=#{color}>#{string}</c>"
20
+ end
21
+ end
22
+
23
+ class Command
24
+ def self.inherited(subclass)
25
+ @list ||= []
26
+ @commands ||= []
27
+ @list << subclass
28
+ end
29
+
30
+ def self.setup
31
+ @list ||= []
32
+ @commands = []
33
+ @list.each do |subclass|
34
+ cmd = subclass.new
35
+ if @commands.detect { |c| c.command == cmd.command }
36
+ raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
37
+ end
38
+
39
+ @commands << cmd
40
+ end
41
+ end
42
+
43
+ def self.use(command, arguments, console)
44
+ found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
45
+
46
+ if found_command
47
+ found_command.handle(arguments, console)
48
+ else
49
+ console.stdin("Command #{Style.error(command)} not found.")
50
+ end
51
+ end
52
+
53
+ def self.find(command)
54
+ @commands.detect { |cmd| cmd.command == command.to_sym }
55
+ end
56
+
57
+ def self.list_commands
58
+ @commands
59
+ end
60
+
61
+ def initialize
62
+ @store = {}
63
+ @subcommands = []
64
+
65
+ setup
66
+ end
67
+
68
+ def setup
69
+ end
70
+
71
+ def subcommand(command, type)
72
+ if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
73
+ raise "Subcommand '#{command}' for '#{self.command}' already exists!"
74
+ end
75
+
76
+ @subcommands << SubCommand.new(self, command, type)
77
+ end
78
+
79
+ def get(key)
80
+ @store[key]
81
+ end
82
+
83
+ def set(key, value)
84
+ @store[key] = value
85
+ end
86
+
87
+ def group
88
+ raise NotImplementedError
89
+ end
90
+
91
+ def command
92
+ raise NotImplementedError
93
+ end
94
+
95
+ def handle(arguments, console)
96
+ raise NotImplementedError
97
+ end
98
+
99
+ def autocomplete(console)
100
+ split = console.text_input.text.split(" ")
101
+
102
+ if @subcommands.size.positive?
103
+ if !console.text_input.text.end_with?(" ") && split.size == 2
104
+ list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
105
+
106
+ if list.size == 1
107
+ console.text_input.text = "#{split.first} #{list.first} "
108
+ else
109
+ return unless list.size.positive?
110
+
111
+ console.stdin(list.map { |cmd| Console::Style.highlight(cmd) }.join(", ").to_s)
112
+ end
113
+
114
+ # List available options on subcommand
115
+ elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
116
+ subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
117
+
118
+ if subcommand
119
+ if split.size == 2
120
+ console.stdin("Available options: #{subcommand.values.map { |value| Console::Style.highlight(value) }.join(',')}")
121
+ else
122
+ list = console.abbrev_search(subcommand.values, split.last)
123
+ if list.size == 1
124
+ console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
125
+ elsif list.size.positive?
126
+ console.stdin("Available options: #{list.map { |value| Console::Style.highlight(value) }.join(',')}")
127
+ end
128
+ end
129
+ end
130
+
131
+ # List available subcommands if command was entered and has only a space after it
132
+ elsif console.text_input.text.end_with?(" ") && split.size == 1
133
+ console.stdin("Available subcommands: #{@subcommands.map { |cmd| Console::Style.highlight(cmd.command) }.join(', ')}")
134
+ end
135
+ end
136
+ end
137
+
138
+ def handle_subcommand(arguments, console)
139
+ if arguments.size.zero?
140
+ console.stdin(usage)
141
+ return
142
+ end
143
+ subcommand = arguments.delete_at(0)
144
+
145
+ found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
146
+ if found_command
147
+ found_command.handle(arguments, console)
148
+ else
149
+ console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
150
+ end
151
+ end
152
+
153
+ def usage
154
+ raise NotImplementedError
155
+ end
156
+ end
157
+ end
158
158
  end