grueserve 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. data/bin/grueserve +6 -0
  2. data/bin/grueserve_path2lines +59 -0
  3. data/example/attributes.yaml +28 -0
  4. data/example/map.gif +0 -0
  5. data/example/sprites/black_soldier_dead.gif +0 -0
  6. data/example/sprites/black_soldier_dodging_0PI1_1.gif +0 -0
  7. data/example/sprites/black_soldier_dodging_0PI1_2.gif +0 -0
  8. data/example/sprites/black_soldier_dodging_0PI1_3.gif +0 -0
  9. data/example/sprites/black_soldier_dodging_1PI1_1.gif +0 -0
  10. data/example/sprites/black_soldier_dodging_1PI1_2.gif +0 -0
  11. data/example/sprites/black_soldier_dodging_1PI1_3.gif +0 -0
  12. data/example/sprites/black_soldier_dodging_1PI2_1.gif +0 -0
  13. data/example/sprites/black_soldier_dodging_1PI2_2.gif +0 -0
  14. data/example/sprites/black_soldier_dodging_1PI2_3.gif +0 -0
  15. data/example/sprites/black_soldier_dodging_3PI2_1.gif +0 -0
  16. data/example/sprites/black_soldier_dodging_3PI2_2.gif +0 -0
  17. data/example/sprites/black_soldier_dodging_3PI2_3.gif +0 -0
  18. data/example/sprites/black_soldier_standing_0PI1_1.gif +0 -0
  19. data/example/sprites/black_soldier_standing_0PI1_2.gif +0 -0
  20. data/example/sprites/black_soldier_standing_0PI1_3.gif +0 -0
  21. data/example/sprites/black_soldier_standing_1PI1_1.gif +0 -0
  22. data/example/sprites/black_soldier_standing_1PI1_2.gif +0 -0
  23. data/example/sprites/black_soldier_standing_1PI1_3.gif +0 -0
  24. data/example/sprites/black_soldier_standing_1PI2_1.gif +0 -0
  25. data/example/sprites/black_soldier_standing_1PI2_2.gif +0 -0
  26. data/example/sprites/black_soldier_standing_1PI2_3.gif +0 -0
  27. data/example/sprites/black_soldier_standing_3PI2_1.gif +0 -0
  28. data/example/sprites/black_soldier_standing_3PI2_2.gif +0 -0
  29. data/example/sprites/black_soldier_standing_3PI2_3.gif +0 -0
  30. data/example/sprites/brown_soldier_dead.gif +0 -0
  31. data/example/sprites/brown_soldier_dodging_0PI1_1.gif +0 -0
  32. data/example/sprites/brown_soldier_dodging_0PI1_2.gif +0 -0
  33. data/example/sprites/brown_soldier_dodging_0PI1_3.gif +0 -0
  34. data/example/sprites/brown_soldier_dodging_1PI1_1.gif +0 -0
  35. data/example/sprites/brown_soldier_dodging_1PI1_2.gif +0 -0
  36. data/example/sprites/brown_soldier_dodging_1PI1_3.gif +0 -0
  37. data/example/sprites/brown_soldier_dodging_1PI2_1.gif +0 -0
  38. data/example/sprites/brown_soldier_dodging_1PI2_2.gif +0 -0
  39. data/example/sprites/brown_soldier_dodging_1PI2_3.gif +0 -0
  40. data/example/sprites/brown_soldier_dodging_3PI2_1.gif +0 -0
  41. data/example/sprites/brown_soldier_dodging_3PI2_2.gif +0 -0
  42. data/example/sprites/brown_soldier_dodging_3PI2_3.gif +0 -0
  43. data/example/sprites/brown_soldier_standing_0PI1_1.gif +0 -0
  44. data/example/sprites/brown_soldier_standing_0PI1_2.gif +0 -0
  45. data/example/sprites/brown_soldier_standing_0PI1_3.gif +0 -0
  46. data/example/sprites/brown_soldier_standing_1PI1_1.gif +0 -0
  47. data/example/sprites/brown_soldier_standing_1PI1_2.gif +0 -0
  48. data/example/sprites/brown_soldier_standing_1PI1_3.gif +0 -0
  49. data/example/sprites/brown_soldier_standing_1PI2_1.gif +0 -0
  50. data/example/sprites/brown_soldier_standing_1PI2_2.gif +0 -0
  51. data/example/sprites/brown_soldier_standing_1PI2_3.gif +0 -0
  52. data/example/sprites/brown_soldier_standing_3PI2_1.gif +0 -0
  53. data/example/sprites/brown_soldier_standing_3PI2_2.gif +0 -0
  54. data/example/sprites/brown_soldier_standing_3PI2_3.gif +0 -0
  55. data/example/sprites/explosion_1.gif +0 -0
  56. data/example/sprites/explosion_10.gif +0 -0
  57. data/example/sprites/explosion_11.gif +0 -0
  58. data/example/sprites/explosion_12.gif +0 -0
  59. data/example/sprites/explosion_13.gif +0 -0
  60. data/example/sprites/explosion_14.gif +0 -0
  61. data/example/sprites/explosion_15.gif +0 -0
  62. data/example/sprites/explosion_16.gif +0 -0
  63. data/example/sprites/explosion_17.gif +0 -0
  64. data/example/sprites/explosion_18.gif +0 -0
  65. data/example/sprites/explosion_19.gif +0 -0
  66. data/example/sprites/explosion_2.gif +0 -0
  67. data/example/sprites/explosion_20.gif +0 -0
  68. data/example/sprites/explosion_21.gif +0 -0
  69. data/example/sprites/explosion_22.gif +0 -0
  70. data/example/sprites/explosion_23.gif +0 -0
  71. data/example/sprites/explosion_24.gif +0 -0
  72. data/example/sprites/explosion_25.gif +0 -0
  73. data/example/sprites/explosion_26.gif +0 -0
  74. data/example/sprites/explosion_27.gif +0 -0
  75. data/example/sprites/explosion_3.gif +0 -0
  76. data/example/sprites/explosion_4.gif +0 -0
  77. data/example/sprites/explosion_5.gif +0 -0
  78. data/example/sprites/explosion_6.gif +0 -0
  79. data/example/sprites/explosion_7.gif +0 -0
  80. data/example/sprites/explosion_8.gif +0 -0
  81. data/example/sprites/explosion_9.gif +0 -0
  82. data/example/sprites/green_soldier_dead.gif +0 -0
  83. data/example/sprites/green_soldier_dodging_0PI1_1.gif +0 -0
  84. data/example/sprites/green_soldier_dodging_0PI1_2.gif +0 -0
  85. data/example/sprites/green_soldier_dodging_0PI1_3.gif +0 -0
  86. data/example/sprites/green_soldier_dodging_1PI1_1.gif +0 -0
  87. data/example/sprites/green_soldier_dodging_1PI1_2.gif +0 -0
  88. data/example/sprites/green_soldier_dodging_1PI1_3.gif +0 -0
  89. data/example/sprites/green_soldier_dodging_1PI2_1.gif +0 -0
  90. data/example/sprites/green_soldier_dodging_1PI2_2.gif +0 -0
  91. data/example/sprites/green_soldier_dodging_1PI2_3.gif +0 -0
  92. data/example/sprites/green_soldier_dodging_3PI2_1.gif +0 -0
  93. data/example/sprites/green_soldier_dodging_3PI2_2.gif +0 -0
  94. data/example/sprites/green_soldier_dodging_3PI2_3.gif +0 -0
  95. data/example/sprites/green_soldier_standing_0PI1_1.gif +0 -0
  96. data/example/sprites/green_soldier_standing_0PI1_2.gif +0 -0
  97. data/example/sprites/green_soldier_standing_0PI1_3.gif +0 -0
  98. data/example/sprites/green_soldier_standing_1PI1_1.gif +0 -0
  99. data/example/sprites/green_soldier_standing_1PI1_2.gif +0 -0
  100. data/example/sprites/green_soldier_standing_1PI1_3.gif +0 -0
  101. data/example/sprites/green_soldier_standing_1PI2_1.gif +0 -0
  102. data/example/sprites/green_soldier_standing_1PI2_2.gif +0 -0
  103. data/example/sprites/green_soldier_standing_1PI2_3.gif +0 -0
  104. data/example/sprites/green_soldier_standing_3PI2_1.gif +0 -0
  105. data/example/sprites/green_soldier_standing_3PI2_2.gif +0 -0
  106. data/example/sprites/green_soldier_standing_3PI2_3.gif +0 -0
  107. data/ext/extconf.rb +93 -0
  108. data/ext/grueserve_ext.c +2083 -0
  109. data/lib/grueserve.rb +28 -0
  110. data/lib/grueserve/application.rb +72 -0
  111. data/lib/grueserve/client.rb +339 -0
  112. data/lib/grueserve/debuggable.rb +114 -0
  113. data/lib/grueserve/map.rb +178 -0
  114. data/lib/grueserve/rated.rb +44 -0
  115. data/lib/grueserve/server.rb +201 -0
  116. data/lib/grueserve/soldier.rb +181 -0
  117. metadata +179 -0
@@ -0,0 +1,178 @@
1
+ # grueserve - a game server in c and ruby for the gruesome game
2
+ # Copyright (C) 2008 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'rubygems'
19
+ require 'pathname'
20
+ require 'yaml'
21
+ require 'geo'
22
+
23
+ module Grueserve
24
+
25
+ class Map
26
+
27
+ def self.blocks?(distance, profile)
28
+ return true if profile >= 1
29
+ return false if profile <= 0
30
+ return rand < (profile - (1.0 / ((distance / 100) + (1.0 / profile))))
31
+ end
32
+
33
+ include Debuggable
34
+
35
+ attr_reader :teams, :application
36
+
37
+ def initialize(path, application = nil)
38
+ @path = path
39
+ @application = application
40
+ @attributes = YAML.load(@path.join("attributes.yaml").read)
41
+ init_lines("movement_obstacles")
42
+ init_lines("visibility_obstacles")
43
+ init_lines("bullet_obstacles")
44
+ init_lines("hearing_obstacles")
45
+ init_lines("fortifications")
46
+ init_areas("speed_areas")
47
+ init_areas("protection_areas")
48
+ init_areas("hidden_areas")
49
+ @hearing_distance_cutoff = (@attributes["hearing_distance_cutoff"] || 100).to_f
50
+ @viewing_distance_limit = (@attributes["viewing_distance_limit"] || 1000).to_f
51
+ @teams = @attributes["teams"].split
52
+ @start_points = @teams.inject({}) do |sum, team|
53
+ sum.merge(team => @attributes["#{team}_start_points"].split.collect do |point_string|
54
+ coords = point_string.split(/,/)
55
+ Geo::Point.new(coords.first.to_f, coords.last.to_f)
56
+ end)
57
+ end
58
+ end
59
+
60
+ def size_of_team(team)
61
+ members = 0
62
+ each_client do |client|
63
+ members += 1 if client.team == team
64
+ end
65
+ return members
66
+ end
67
+
68
+ def smallest_team
69
+ return @teams.sort do |team1,team2|
70
+ size_of_team(team1) <=> size_of_team(team2)
71
+ end.first
72
+ end
73
+
74
+ def send_binaries(client)
75
+ Pathname.glob(@path.join("sprites").join("**").join("*")).each do |sprite_frame|
76
+ client.send_bin(sprite_frame.basename, sprite_frame.read)
77
+ end
78
+ client.send_bin("map", img.read)
79
+ end
80
+
81
+ def protection_factor(location)
82
+ rval = 1.0
83
+ if area = protection_areas.first_container(location)
84
+ rval *= self[area] / 1000.0
85
+ end
86
+ return rval
87
+ end
88
+
89
+ def start_point(team, not_point = nil)
90
+ points_to_choose_from = @start_points[team].clone
91
+ points_to_choose_from.delete(not_point) if not_point && points_to_choose_from.size > 1
92
+ the_point = points_to_choose_from[rand(points_to_choose_from.size)]
93
+ return Geo::Point.new(the_point.x, the_point.y)
94
+ end
95
+
96
+ def img
97
+ @path.join(@attributes["map"])
98
+ end
99
+
100
+ def bullet_intersection(line)
101
+ definite_stop = bullet_obstacles.closest_intersection(line)
102
+ line.p2 = definite_stop.first if definite_stop
103
+ fortifications.intersections(line).sort do |a,b|
104
+ a.first.to(line.p1).abs <=> b.first.to(line.p1).abs
105
+ end.each do |possible_stop|
106
+ if self.class.blocks?(line.p1.to(possible_stop.first).abs, self[possible_stop.last] / 1000.0)
107
+ return possible_stop.first
108
+ end
109
+ end
110
+ return line.p2
111
+ end
112
+
113
+ def noise_damp(line)
114
+ obstacles_damp = hearing_obstacles.intersections(line).inject(1.0) do |sum, obstacle|
115
+ sum * self[obstacle.last] / 1000.0
116
+ end
117
+ (1.0 / ([(line.abs / @hearing_distance_cutoff),1].max ** 2)) * obstacles_damp
118
+ end
119
+
120
+ private
121
+
122
+ def init_areas(name)
123
+ triangle_set = self.send(name.to_sym)
124
+ if stringData = @attributes[name]
125
+ stringData.split(/:/).each do |segment|
126
+ pointString, valueString = segment.split(/=/)
127
+ value = @attributes["#{name}_default_value"].to_i
128
+ value = valueString.to_i if valueString
129
+ points = pointString.split
130
+ middle = point_from_string(points.shift)
131
+ (points.size - 1).times do |i|
132
+ p1 = points[i]
133
+ p1 = points.first if p1 == "loop"
134
+ p1 = point_from_string(p1)
135
+ p2 = points[i + 1]
136
+ p2 = points.first if p2 == "loop"
137
+ p2 = point_from_string(p2)
138
+ area = Geo::Triangle.new(middle, p1, p2)
139
+ log_debug("adding area #{area.inspect} with value #{value} to @#{name}")
140
+ triangle_set << area
141
+ self[area] = value
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def init_lines(name)
148
+ line_set = self.send(name.to_sym)
149
+ if stringData = @attributes[name]
150
+ stringData.split(/:/).each do |segment|
151
+ pointString, valueString = segment.split(/=/)
152
+ value = @attributes["#{name}_default_value"].to_i
153
+ value = valueString.to_i if valueString
154
+ points = segment.split
155
+ (points.size - 1).times do |i|
156
+ p1 = points[i]
157
+ p1 = points.first if p1 == "loop"
158
+ p1 = point_from_string(p1)
159
+ p2 = points[i + 1]
160
+ p2 = points.first if p2 == "loop"
161
+ p2 = point_from_string(p2)
162
+ o = Geo::Line.new(p1, p2)
163
+ log_debug("adding obstacle #{o.inspect} with value #{value} to @#{name}")
164
+ line_set << o
165
+ self[o] = value
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ def point_from_string(s)
172
+ x,y = s.split(/,/)
173
+ return Geo::Point.new(x.to_i,y.to_i)
174
+ end
175
+
176
+ end
177
+
178
+ end
@@ -0,0 +1,44 @@
1
+ # grueserve - a game server in c and ruby for the gruesome game
2
+ # Copyright (C) 2008 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+
19
+ module Grueserve
20
+
21
+ module Rated
22
+
23
+ class Rate
24
+ def initialize(n)
25
+ @n = n
26
+ @times = {}
27
+ end
28
+ def times_a_second(&block)
29
+ time = (@times[:second] ||= Time.now.to_f)
30
+ if Time.now.to_f - time > 1.0 / @n
31
+ @times[:second] = Time.now.to_f
32
+ yield
33
+ end
34
+ end
35
+ end
36
+
37
+ def max_rate_of(n)
38
+ @rates ||= {}
39
+ @rates[n] ||= Rate.new(n)
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,201 @@
1
+ # grueserve - a game server in c and ruby for the gruesome game
2
+ # Copyright (C) 2008 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'rubygems'
19
+ require 'socket'
20
+ require 'pp'
21
+ require 'monitor'
22
+ require 'xml/libxml'
23
+
24
+ module Grueserve
25
+
26
+ class Server
27
+
28
+ module ClientSocket
29
+
30
+ class PolicyFileRequest < RuntimeError
31
+ end
32
+
33
+ include MonitorMixin
34
+ include Debuggable
35
+
36
+ attr_accessor :buffer, :bufferSize, :client, :server
37
+
38
+ def init_client_socket(client, server)
39
+ mon_initialize
40
+ @client = client
41
+ @server = server
42
+ @buffer = ""
43
+ @bufferSize = 0
44
+ end
45
+
46
+ def readTo(char)
47
+ rval = ""
48
+ while (next_char = self.read_nonblock(1)) != char
49
+ rval += next_char
50
+ if rval == "<policy-file-request/>\0"
51
+ log_debug("Received policy file request #{rval.inspect}")
52
+ raise PolicyFileRequest.new
53
+ end
54
+ end
55
+ return rval
56
+ end
57
+
58
+ def handle_input
59
+ begin
60
+ @bufferSize = readTo(":").to_i if @bufferSize == 0
61
+ log_trace("Expecting package of size #{@bufferSize}")
62
+ gotten = self.read_nonblock(@bufferSize - @buffer.size)
63
+ log_trace("Received buffer #{gotten.inspect}")
64
+ @buffer += gotten
65
+ if @buffer.size == @bufferSize
66
+ log_debug("Received complete buffer from #{self}: #{@buffer.inspect}")
67
+ doc = XML::Parser.string(@buffer).parse
68
+ client.handle_input(doc)
69
+ @buffer = ""
70
+ @bufferSize = 0
71
+ end
72
+ rescue Exception => e
73
+ server.kill_socket(self)
74
+ raise e
75
+ end
76
+ end
77
+
78
+ end
79
+
80
+ include Debuggable
81
+
82
+ def initialize(options = {})
83
+ @port = options.delete(:port) || 25999
84
+ raise "#{self.class}.new requires an :application argument." unless options.include?(:application)
85
+ @application = options.delete(:application)
86
+ @frequency = options.delete(:frequency) || 100
87
+ raise "Unknown options to #{self.class}: #{options.inspect}" unless options.empty?
88
+ @sockets = []
89
+ @sockets_to_close = Set.new
90
+ end
91
+
92
+ def start!
93
+ start_updating!
94
+ start_listening!
95
+ start_serving!
96
+ end
97
+
98
+ def start_updating!
99
+ @updater ||= nil
100
+ @updater.kill if @updater.respond_to?(:kill)
101
+ @updater = Thread.new do
102
+ counter = 0
103
+ timer = Time.now
104
+ sleep_sum = 0
105
+ loop do
106
+ begin
107
+ start_time = Time.now
108
+ @application.map.update!
109
+ if (diff = (1.0 / @frequency) - ((stop_time = Time.now).to_f - start_time.to_f)) > 0
110
+ sleep_sum += diff
111
+ sleep(diff)
112
+ end
113
+ counter += 1
114
+ if Time.now - timer > 10
115
+ avg_frequency = counter / (Time.now.to_f - timer.to_f)
116
+ avg_sleep = sleep_sum / (Time.now.to_f - timer.to_f)
117
+ log_info("Avg frequency: #{avg_frequency}, sleeping #{(avg_sleep * 100).to_i}% of the time")
118
+ report
119
+ reset
120
+ timer = Time.now
121
+ counter = 0
122
+ sleep_sum = 0
123
+ end
124
+ rescue Exception => e
125
+ log_warn(e)
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def kill_socket(socket)
132
+ socket.client.shutdown
133
+ @sockets.delete(socket)
134
+ @sockets_to_close << socket
135
+ end
136
+
137
+ def start_listening!
138
+ @listener ||= nil
139
+ @listener.kill if @listener.respond_to?(:kill)
140
+ @listener = Thread.new do
141
+ loop do
142
+ begin
143
+ to_close = @sockets_to_close.to_a
144
+ while (socket = to_close.shift)
145
+ socket.close unless socket.closed?
146
+ end
147
+ read_ready, write_read, errors = select(@sockets, nil, @sockets, 1)
148
+ unless errors.nil?
149
+ errors.each do |socket|
150
+ log_warn("Going to drop #{socket} from #{@sockets.inspect} since it got an error")
151
+ kill_socket(socket)
152
+ end
153
+ end
154
+ unless read_ready.nil?
155
+ read_ready.each do |socket|
156
+ if socket.eof?
157
+ log_info("Going to drop #{socket} from #{@sockets.inspect} since it is closed")
158
+ kill_socket(socket)
159
+ else
160
+ begin
161
+ socket.handle_input
162
+ rescue ClientSocket::PolicyFileRequest => e
163
+ log_info("Got policy file request from #{socket.inspect}")
164
+ socket.client.handle_policy_request
165
+ end
166
+ end
167
+ end
168
+ end
169
+ rescue Exception => e
170
+ log_warn(e)
171
+ end
172
+ end
173
+ end
174
+ log_debug("Started listener")
175
+ end
176
+
177
+ def start_serving!
178
+ log_debug("Starting serving")
179
+ @server ||= nil
180
+ @server.close if @server.respond_to?(:close)
181
+ @server = TCPServer.new(@port)
182
+ loop do
183
+ begin
184
+ socket = @server.accept_nonblock
185
+ socket.extend(ClientSocket)
186
+ socket.init_client_socket(Client.new(:socket => socket, :application => @application), self)
187
+ log_info("Received #{socket}, adding it to #{@sockets.inspect}")
188
+ @sockets << socket
189
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR => e
190
+ log_trace("Got TCPServer error: #{e}")
191
+ IO.select([@server])
192
+ retry
193
+ rescue Exception => e
194
+ log_warn(e)
195
+ end
196
+ end
197
+ end
198
+
199
+ end
200
+
201
+ end
@@ -0,0 +1,181 @@
1
+ # grueserve - a game server in c and ruby for the gruesome game
2
+ # Copyright (C) 2008 Martin Kihlgren <zond at troja dot ath dot cx>
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ module Grueserve
19
+
20
+ module Soldier
21
+
22
+ class Aim
23
+ attr_accessor :point, :precision
24
+ def initialize(point, precision)
25
+ @point = point
26
+ @precision = precision
27
+ end
28
+ end
29
+
30
+ RELOAD = "RELOAD"
31
+
32
+ def self.extend_object(o)
33
+ super
34
+ o.init_soldier
35
+ end
36
+
37
+ def init_soldier
38
+ self.behaviour = "soldier"
39
+ @reloading = false
40
+ @aim = nil
41
+ subscribe("LBUTTON", :handle_lbutton)
42
+ subscribe("RELOAD", :handle_reload)
43
+ end
44
+
45
+ def refill
46
+ super
47
+ self.ammo = max_ammo
48
+ end
49
+
50
+ def ammo
51
+ @ammo
52
+ end
53
+
54
+ def ammo=(x)
55
+ @ammo = x
56
+ self.extra_status = " Ammo: #{@ammo}"
57
+ end
58
+
59
+ def max_ammo
60
+ 32
61
+ end
62
+
63
+ def base_grenade_precision_factor
64
+ 2
65
+ end
66
+
67
+ def grenade_precision_delta
68
+ 0.1
69
+ end
70
+
71
+ def grenade_max_precision_factor
72
+ 5
73
+ end
74
+
75
+ def reload_time
76
+ 3
77
+ end
78
+
79
+ def throwing_speed
80
+ 100
81
+ end
82
+
83
+ def max_throwing_distance
84
+ 200
85
+ end
86
+
87
+ def reload
88
+ if !@reloading && @aim.nil
89
+ @reloading = true
90
+ broadcast_noise("reload", body.origo)
91
+ thread_with_rescue do
92
+ sleep(reload_time)
93
+ self.ammo = max_ammo
94
+ @reloading = false
95
+ end
96
+ end
97
+ end
98
+
99
+ def throw_grenade
100
+ this_fuzz_factor = @aim.point.abs / @aim.precision
101
+ loc = body.origo + Geo::Point.new(@aim.point.x + (this_fuzz_factor * fuzz),
102
+ @aim.point.y + (this_fuzz_factor * fuzz))
103
+ send_throw(body.origo.to(loc), throwing_speed)
104
+ @aim = nil
105
+ end
106
+
107
+ def handle_lbutton(command)
108
+ unless @reloading
109
+ if command.nil?
110
+ unless @aim.nil?
111
+ throw_grenade
112
+ end
113
+ else
114
+ if current_command.include?("SHIFT")
115
+ max_rate_of(10).times_a_second do
116
+ if @aim.nil?
117
+ new_point = Geo::Point.new(command["x"].to_i, command["y"].to_i) - body.origo
118
+ new_point.abs = max_throwing_distance if new_point.abs > max_throwing_distance
119
+ @aim = Aim.new(new_point, base_grenade_precision_factor)
120
+ else
121
+ @aim.precision += grenade_precision_delta unless @aim.precision > grenade_max_precision_factor
122
+ end
123
+ aim_point = body.origo + @aim.point
124
+ send_aim(aim_point.x, aim_point.y, @aim.point.abs / @aim.precision)
125
+ end
126
+ else
127
+ if @aim.nil?
128
+ if ammo > 0
129
+ max_rate_of(8).times_a_second do
130
+ shot_line = body.origo.to(command["x"].to_i, command["y"].to_i)
131
+ this_fuzz_factor = shoot_fuzz_factor
132
+ angle = fuzz * this_fuzz_factor + shot_line.angle
133
+ send_aim(command["x"], command["y"], Math.tan(this_fuzz_factor) * shot_line.abs)
134
+ fire_bullet(angle)
135
+ self.ammo -= 1
136
+ end
137
+ else
138
+ max_rate_of(1).times_a_second do
139
+ broadcast_noise("dry_shot", body.origo)
140
+ end
141
+ end
142
+ else
143
+ throw_grenade
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ def handle_reload(command)
151
+ unless command.nil?
152
+ reload
153
+ end
154
+ end
155
+
156
+ def damage
157
+ 2
158
+ end
159
+
160
+ def shoot_fuzz_factor
161
+ rval = 1.0
162
+ if moving?
163
+ if state == :dodging
164
+ rval *= 4
165
+ else
166
+ rval *= 2
167
+ end
168
+ else
169
+ rval *= 0.5 if state == :dodging
170
+ end
171
+ return rval / precision
172
+ end
173
+
174
+ def precision
175
+ 30.0
176
+ end
177
+
178
+ end
179
+
180
+
181
+ end