ssc.nob 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # SSC.Nob
2
+
3
+ *SSC.Nob* is a simple bot I wrote for the game [Subspace Continuum](https://store.steampowered.com/app/352700/Subspace_Continuum/), just for fun!
4
+
5
+ *SSC* stands for Subspace Continuum. *Nob* stands for Noble One Bot. Noble One (or Nob) is meant to be a gender-neutral version of King of the Hill.
6
+
7
+ Subspace Continuum links:
8
+ - [Windows / Linux (with wine)](http://subspace-continuum.com/trackdownload.php?type=win)
9
+ - [macOS](http://subspace-continuum.com/trackdownload.php?type=mac)
10
+ - [UDP Game Protocol [wiki]](http://wiki.minegoboom.com/index.php/UDP_Game_Protocol)
11
+
12
+ It's **not** a server bot and can be run & used by any user in any zone & arena.
13
+
14
+ It's currently **not** a [UDP packet](https://www.twcore.org/SubspaceProtocol/) bot. It simply reads a log file for input and uses Java's Robot class for output. For SSC.Nob2 or something, I'd like to read/write packets, but will probably never make it.
15
+
16
+ ## Running
17
+
18
+ JRuby is required. [ruby-install](https://github.com/postmodern/ruby-install) & [chruby](https://github.com/postmodern/chruby) make this easy:
19
+
20
+ ```
21
+ $ ruby-install jruby
22
+ $ chruby jruby
23
+ $ gem install bundler
24
+ ```
25
+
26
+ It's pretty janky at the moment, but works.
27
+
28
+ Run the app with JRuby:
29
+
30
+ ```
31
+ $ bundler install
32
+ $ ruby ./lib/ssc.nob.rb
33
+ ```
34
+
35
+ **Note:** that's *bundler* with an *r*, not *bundle* (which doesn't work with JRuby).
36
+
37
+ You'll be asked a couple of questions. Then input the `run` command.
38
+
39
+ Current Subspace configuration:
40
+ - In *Key Defs*, set the *Msg* key to *TAB*.
41
+ - Once logged in...
42
+ - do `?log nob.log`
43
+ - do `?kill` until kill messages are set to display in the chat
44
+ - do `?namelen=24`
45
+
46
+ Now to run *Nob*, private message yourself `!nob.start`. Private message yourself `!nob.stop` to stop it.
47
+
48
+ [![asciinema demo](https://asciinema.org/a/325424.png)](https://asciinema.org/a/325424)
49
+
50
+ ## License
51
+
52
+ [GNU GPL v3+](LICENSE.txt)
53
+
54
+ > SSC.Nob (<https://github.com/esotericpig/ssc.nob>)
55
+ > Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
56
+ >
57
+ > SSC.Nob is free software: you can redistribute it and/or modify
58
+ > it under the terms of the GNU General Public License as published by
59
+ > the Free Software Foundation, either version 3 of the License, or
60
+ > (at your option) any later version.
61
+ >
62
+ > SSC.Nob is distributed in the hope that it will be useful,
63
+ > but WITHOUT ANY WARRANTY; without even the implied warranty of
64
+ > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
65
+ > GNU General Public License for more details.
66
+ >
67
+ > You should have received a copy of the GNU General Public License
68
+ > along with SSC.Nob. If not, see <https://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ #--
5
+ # This file is part of SSC.Nob.
6
+ # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
7
+ #
8
+ # SSC.Nob is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # SSC.Nob is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with SSC.Nob. If not, see <https://www.gnu.org/licenses/>.
20
+ #++
21
+
22
+
23
+ require 'bundler/gem_tasks'
24
+
25
+ require 'rake/clean'
26
+ require 'rake/testtask'
27
+
28
+ require 'ssc.nob/version'
29
+
30
+
31
+ CLEAN.exclude('.git/','stock/')
32
+ CLOBBER.include('doc/')
33
+
34
+ task default: [:test]
35
+
36
+ Rake::TestTask.new() do |task|
37
+ task.libs = ['lib','test']
38
+ task.pattern = File.join('test','**','*_test.rb')
39
+ task.description += ": '#{task.pattern}'"
40
+ task.verbose = false
41
+ task.warning = true
42
+ end
data/bin/ssc.nob ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # This file is part of SSC.Nob.
7
+ # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
8
+ #
9
+ # SSC.Nob is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # SSC.Nob is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with SSC.Nob. If not, see <https://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+
24
+ require 'ssc.nob'
25
+
26
+ SSCNob.run()
data/lib/ssc.nob.rb ADDED
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # This file is part of SSC.Nob.
7
+ # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
8
+ #
9
+ # SSC.Nob is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # SSC.Nob is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with SSC.Nob. If not, see <https://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+
24
+ TESTING = ($0 == __FILE__)
25
+
26
+ if TESTING
27
+ require 'rubygems'
28
+ require 'bundler/setup'
29
+ end
30
+
31
+ require 'attr_bool'
32
+ require 'time'
33
+
34
+ require 'ssc.nob/config'
35
+ require 'ssc.nob/error'
36
+ require 'ssc.nob/ssc_bot'
37
+ require 'ssc.nob/ssc_chat_log'
38
+ require 'ssc.nob/userface'
39
+ require 'ssc.nob/util'
40
+ require 'ssc.nob/version'
41
+
42
+ require 'ssc.nob/ssc_chat_log/message'
43
+ require 'ssc.nob/ssc_chat_log/message_parser'
44
+
45
+
46
+ # TODO: run options
47
+ # run:
48
+ # - Specify time limit (in seconds?).
49
+ # - Specify money donation for winner.
50
+ # - Specify who starts as Nob.
51
+ # - Specify if testing, so do chat channel messages instead of pub.
52
+
53
+ ###
54
+ # @author Jonathan Bradley Whited (@esotericpig)
55
+ # @since 0.1.0
56
+ ###
57
+ module SSCNob
58
+ def self.run()
59
+ nober = SSCNober.new()
60
+
61
+ nober.run()
62
+ end
63
+
64
+ def self.uface()
65
+ return Userface.instance
66
+ end
67
+
68
+ class SSCNober
69
+ include Uface
70
+
71
+ attr_reader :bot
72
+ attr_reader :chat_log
73
+ attr_reader :config
74
+ attr_reader :nob
75
+ attr_reader :nob_time
76
+ attr_reader :nobing
77
+ attr_reader :players
78
+ attr_accessor? :testing
79
+ attr_reader :thread
80
+
81
+ def initialize(testing: false)
82
+ super()
83
+
84
+ @bot = SSCBot.new()
85
+ @chat_log = nil
86
+ @config = Config.new()
87
+ @nob = nil
88
+ @nob_time = Time.now()
89
+ @nobing = false
90
+ @players = {}
91
+ @testing = testing
92
+ @thread = nil
93
+ end
94
+
95
+ def run()
96
+ begin
97
+ puts
98
+ @config.user_init!()
99
+ puts
100
+ rescue UserError => e
101
+ puts
102
+ puts uface.error(e)
103
+
104
+ return
105
+ end
106
+
107
+ @chat_log = SSCChatLog.new(@config)
108
+
109
+ @chat_log.add_listener(&method(:kill_message))
110
+ @chat_log.add_listener(&method(:private_message))
111
+ @chat_log.add_listener(&method(:pub_message))
112
+ @chat_log.add_listener(&method(:q_namelen_message))
113
+
114
+ puts <<~EOH
115
+ #{uface.title('COMMANDS')}
116
+ #{uface.cmd('run')} runs Nob
117
+ #{uface.cmd('stop')} stops Nob
118
+ #{uface.cmd('exit, quit')} goodbye, user
119
+ EOH
120
+ puts
121
+
122
+ while true
123
+ cmd = uface.ask(uface.gt())
124
+ cmd = Util.strip(cmd.to_s()).downcase()
125
+
126
+ case cmd
127
+ when 'exit','quit'
128
+ @chat_log.stop()
129
+
130
+ puts
131
+ uface.types("You don't #{uface.love('love')} me? #{uface.love('<3')}")
132
+ puts
133
+
134
+ return
135
+ when 'run'
136
+ if @chat_log.running?()
137
+ puts
138
+ puts uface.error('stop the current Nob first, user.')
139
+ puts
140
+ else
141
+ @chat_log.run()
142
+ end
143
+ when 'stop'
144
+ @chat_log.stop()
145
+ else
146
+ puts
147
+ puts uface.error("that's an invalid command, user.")
148
+ puts
149
+ end
150
+ end
151
+ end
152
+
153
+ def kill_message(chat_log,msg)
154
+ return unless @nobing
155
+ return unless msg.kill?()
156
+
157
+ if msg[:killed] == @nob
158
+ old_nob = @players[@nob]
159
+ old_nob[:time] += (Time.now() - @nob_time)
160
+
161
+ killer = msg[:killer]
162
+ new_nob = @players[killer]
163
+
164
+ if new_nob.nil?()
165
+ new_nob = {nobs: 0,time: 0,username: killer}
166
+ @players[killer] = new_nob
167
+ end
168
+
169
+ new_nob[:nobs] += 1
170
+
171
+ @nob = killer
172
+ @nob_time = Time.now()
173
+
174
+ @bot.pub_message("Nob} {#{killer}} is now the Nob!")
175
+ end
176
+ end
177
+
178
+ def private_message(chat_log,msg)
179
+ return unless msg.private?()
180
+
181
+ if msg[:username] == @config.username
182
+ case msg[:message]
183
+ when '!nob.start'
184
+ return if @nobing # Already nobing
185
+
186
+ @nob = @config.username
187
+
188
+ @players = {
189
+ @nob => {nobs: 1,time: 0,username: @nob}
190
+ }
191
+
192
+ if !@thread.nil?()
193
+ @thread.kill() if @thread.alive?()
194
+
195
+ @thread = nil
196
+ end
197
+
198
+ @bot.pub_message('Nob} Nob bot loaded (Noble One) for 5 min!')
199
+ @bot.pub_message("Nob} Kill {#{@nob}} to become the Nob!")
200
+
201
+ @nobing = true
202
+ @nob_time = Time.now()
203
+
204
+ @thread = Thread.new() do
205
+ sleep(5 * 60)
206
+
207
+ @nobing = false
208
+
209
+ nobler = @players[@nob]
210
+ nobler[:time] += (Time.now() - @nob_time)
211
+
212
+ tops = @players.values.sort() do |p1,p2|
213
+ p2[:time] <=> p1[:time]
214
+ end
215
+ tops = tops[0..2]
216
+
217
+ @bot.pub_message('Nob} Nob bot ended!')
218
+ @bot.pub_message(sprintf('Nob} | %-24s | # of Nobs | Time','Nobler'))
219
+ @bot.pub_message("Nob} | #{'-' * 24} | #{'-' * 9} | #{'-' * 8}")
220
+
221
+ tops.each_with_index() do |top,i|
222
+ msg = sprintf("Nob} ##{i + 1} | %-24s | %-9s | %s secs",
223
+ top[:username],top[:nobs],top[:time].round(2))
224
+
225
+ @bot.pub_message(msg)
226
+ end
227
+
228
+ top = tops[0]
229
+
230
+ @bot.pub_message("Nob} {#{top[:username]}} is the top Nob! Congrats!")
231
+ end
232
+ when '!nob.stop'
233
+ @nobing = false
234
+ @players = {}
235
+
236
+ if !@thread.nil?()
237
+ @thread.kill() if @thread.alive?()
238
+
239
+ @thread = nil
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ def pub_message(chat_log,msg)
246
+ return unless @nobing
247
+ return unless msg.pub?()
248
+
249
+ if msg[:username] == @config.username
250
+ @bot.prevent_flooding()
251
+ end
252
+ end
253
+
254
+ def q_namelen_message(chat_log,msg)
255
+ return unless msg.q_namelen?()
256
+
257
+ puts
258
+ puts "Using namelen{#{msg[:namelen]}}."
259
+ print uface.gt()
260
+ end
261
+ end
262
+ end
263
+
264
+ if TESTING
265
+ SSCNob.run()
266
+ end
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ #--
6
+ # This file is part of SSC.Nob.
7
+ # Copyright (c) 2020 Jonathan Bradley Whited (@esotericpig)
8
+ #
9
+ # SSC.Nob is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # SSC.Nob is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with SSC.Nob. If not, see <https://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+
24
+ require 'psych'
25
+
26
+ require 'ssc.nob/error'
27
+ require 'ssc.nob/userface'
28
+ require 'ssc.nob/util'
29
+
30
+
31
+ module SSCNob
32
+ ###
33
+ # @author Jonathan Bradley Whited (@esotericpig)
34
+ # @since 0.1.0
35
+ ###
36
+ class Config
37
+ include Uface
38
+
39
+ attr_reader :file
40
+ attr_accessor :ssc_dir
41
+ attr_accessor :username
42
+
43
+ def initialize(file='ssc.nob.yml')
44
+ super()
45
+
46
+ @file = File.expand_path(file)
47
+ @ssc_dir = nil
48
+ @username = nil
49
+ end
50
+
51
+ def build_ssc_log_dir()
52
+ return File.join(@ssc_dir,'logs')
53
+ end
54
+
55
+ def check_ssc_dir()
56
+ @ssc_dir = Util.strip(@ssc_dir)
57
+
58
+ if @ssc_dir.nil?() || @ssc_dir.empty?()
59
+ raise UserError,"that's a blank folder name, user."
60
+ end
61
+
62
+ @ssc_dir = File.expand_path(@ssc_dir)
63
+
64
+ if !Dir.exist?(@ssc_dir)
65
+ raise UserError,"that folder{#{@ssc_dir}} doesn't exist, user."
66
+ end
67
+ if !File.directory?(@ssc_dir)
68
+ raise UserError,"that's a file{#{@ssc_dir}}, not a folder, user."
69
+ end
70
+
71
+ ssc_log_dir = build_ssc_log_dir()
72
+
73
+ if !Dir.exist?(ssc_log_dir) || !File.directory?(ssc_log_dir)
74
+ raise UserError,"why's there no 'logs' folder{#{ssc_log_dir}}, user?"
75
+ end
76
+ end
77
+
78
+ def check_username()
79
+ raise UserError,"that's a blank username, user." if Util.blank?(@username)
80
+ end
81
+
82
+ def load_file!(mode: 'rt:BOM|UTF-8',**kargs)
83
+ data = File.read(@file,mode: mode,**kargs)
84
+
85
+ yaml = Psych.safe_load(data,
86
+ aliases: false,
87
+ filename: @file,
88
+ permitted_classes: [Symbol],
89
+ symbolize_names: true,
90
+ **kargs,
91
+ )
92
+
93
+ @ssc_dir = Util.strip(yaml[:ssc_dir])
94
+ @ssc_dir = File.expand_path(@ssc_dir) if !@ssc_dir.nil?() && !@ssc_dir.empty?()
95
+ @username = yaml[:username]
96
+ end
97
+
98
+ def save_file(mode: 'wt',**kargs)
99
+ File.open(@file,mode: mode,**kargs) do |fout|
100
+ fout.write(to_s())
101
+ end
102
+ end
103
+
104
+ def user_init!()
105
+ if File.exist?(@file)
106
+ load_file!()
107
+ end
108
+
109
+ if valid?()
110
+ uface.type("Welcome back, ")
111
+ puts "#{uface.user(@username)}."
112
+ uface.type("Here's a hot cup of coffee: ")
113
+ puts uface.coffee
114
+ else
115
+ uface.types('Welcome, new user.')
116
+ puts
117
+
118
+ @username = uface.ask("What's your #{uface.user('username')}? ")
119
+ check_username()
120
+
121
+ @ssc_dir = uface.ask("Where's your #{uface.ssc} folder? ")
122
+ check_ssc_dir()
123
+
124
+ puts
125
+ puts uface.gt(@file)
126
+ if uface.agree('Save this configuration (y/n)? ')
127
+ save_file()
128
+ end
129
+ end
130
+ end
131
+
132
+ def valid?()
133
+ begin
134
+ check_ssc_dir()
135
+ check_username()
136
+ rescue UserError
137
+ return false
138
+ end
139
+
140
+ return true
141
+ end
142
+
143
+ def to_s()
144
+ yaml = {
145
+ 'username' => @username,
146
+ 'ssc_dir' => @ssc_dir,
147
+ }
148
+
149
+ return Psych.dump(yaml,header: true)
150
+ end
151
+ end
152
+ end