ssc.nob 0.1.0-java

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.
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