empi 0.18 → 0.23

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.
@@ -1,13 +1,16 @@
1
1
  require_relative './unit'
2
2
 
3
3
  class Army < Unit
4
+ @name = 'army'
5
+ @map_symbol = 'A'
6
+ @price = 3
7
+ @value = 5
8
+
4
9
  def initialize(x, y, faction, map, infopane)
5
10
  super
6
11
  dir_path = File.dirname(__FILE__)
7
- @image = Gosu::Image.new(dir_path + '/media/army.png')
12
+ @image = Gosu::Image.new(dir_path + '/../../media/army.png')
8
13
 
9
- @name = 'army'
10
- @value = 5
11
14
  @armor_left = @armor_max = 3
12
15
  @moves_max = 5
13
16
  end
@@ -1,13 +1,16 @@
1
1
  require_relative './unit'
2
2
 
3
3
  class Ship < Unit
4
+ @name = 'ship'
5
+ @map_symbol = 'S'
6
+ @price = 5
7
+ @value = 10
8
+
4
9
  def initialize(x, y, faction, map, infopane)
5
10
  super
6
11
  dir_path = File.dirname(__FILE__)
7
- @image = Gosu::Image.new(dir_path + '/media/ship.png')
12
+ @image = Gosu::Image.new(dir_path + '/../../media/ship.png')
8
13
 
9
- @name = 'ship'
10
- @value = 10
11
14
  @armor_left = @armor_max = 1
12
15
  @moves_max = 2
13
16
  @cargo_max = 3
@@ -0,0 +1,106 @@
1
+ require_relative './army'
2
+ require_relative './ship'
3
+ require_relative './unit'
4
+
5
+ require_relative './../game_states/game_state'
6
+ require_relative './../game_states/build_state'
7
+
8
+ class Town < Unit
9
+ @name = 'town'
10
+ @map_symbol = 'T'
11
+ @value = 20
12
+
13
+ attr_accessor :project, :parts_built, :parts_needed
14
+
15
+ def initialize(x, y, faction, map, infopane)
16
+ super
17
+ dir_path = File.dirname(__FILE__)
18
+ @image = Gosu::Image.new(dir_path + '/../../media/town.png')
19
+
20
+ @armor_left = @armor_max = 1
21
+ @moves_max = 0
22
+ @cargo_max = 10
23
+
24
+ @starting_project = Army unless @faction == 0
25
+ @project = nil
26
+ @parts_built = 0
27
+ @parts_needed = 0
28
+
29
+ set_function!(FUNCBUILD, @faction) unless @faction == 0
30
+ end
31
+
32
+ def can_build?
33
+ true
34
+ end
35
+
36
+ def can_be_captured?
37
+ true
38
+ end
39
+
40
+ # Tell the state of current build project
41
+ def build_info
42
+ "#{@parts_built}/#{@parts_needed}"
43
+ end
44
+
45
+ # Process capture targeted at this town and reset build process
46
+ def capture!(by_whom)
47
+ super
48
+
49
+ # Reset build process
50
+ @function.func = FUNCNONE
51
+ @project = nil
52
+ @parts_built = 0
53
+
54
+ set_function!(FUNCBUILD, @faction)
55
+ end
56
+
57
+ # Set desired function and possibly also project
58
+ def set_function!(func, commanding_faction)
59
+ super
60
+
61
+ if @faction != 0 and commanding_faction == @faction and func == FUNCBUILD
62
+ # Set starting project once or ask player about next project
63
+ if @starting_project
64
+ set_project!(@starting_project)
65
+ @starting_project = nil
66
+ else
67
+ GameState.switch!(BuildState.instance)
68
+ BuildState.instance.unit = self
69
+ end
70
+ end
71
+ end
72
+
73
+ # Set desired project
74
+ def set_project!(desired_project)
75
+ unless price_list.key?(desired_project)
76
+ abort("town.set_project!(): Unknown project (#{desired_project})")
77
+ end
78
+ @parts_needed = price_list[desired_project]
79
+
80
+ # Compare new setting with the old one
81
+ if desired_project == @project
82
+ puts PROMPT + to_s + ": project has already been set to #{@project.name} (#{build_info} done)"
83
+ else
84
+ previous_project = @project
85
+ @project = desired_project
86
+ lost_parts = @parts_built
87
+ @parts_built = 0
88
+
89
+ new_project_set_text = PROMPT + to_s + ": project set to #{@project.name} (#{build_info} done)"
90
+ unless lost_parts > 0
91
+ puts new_project_set_text
92
+ else
93
+ puts new_project_set_text + ", losing #{lost_parts} " +
94
+ "part#{ 's' unless lost_parts == 1 } of #{previous_project.name}"
95
+
96
+ end
97
+ end
98
+ end
99
+
100
+ # Load all prices
101
+ def price_list
102
+ prices = Hash[
103
+ [Army, Ship].collect { |ii| [ii, ii.price] } # TODO all subclasses of Unit which can_be_built?
104
+ ]
105
+ end
106
+ end
@@ -0,0 +1,301 @@
1
+ require_relative './unitFunction'
2
+
3
+ # Both capturable and movable game pieces
4
+ class Unit
5
+ class << self
6
+ attr_reader :name, :map_symbol, :price, :value
7
+ end
8
+
9
+ attr_reader :armor_left
10
+ attr_accessor :x, :y, :faction, :function, :cargo, :cargo_max
11
+
12
+ def initialize(x, y, faction, map, infopane)
13
+ @x = x
14
+ @y = y
15
+ @faction = faction
16
+ @map = map
17
+ @infopane = infopane
18
+
19
+ @armor_max = 1
20
+ @armor_left = @armor_max
21
+ @moves_max = 1
22
+ @moves_left = @moves_max
23
+ @cargo = [] # transported units
24
+ @cargo_max = 0
25
+ @function = UnitFunction.new(FUNCNONE, self)
26
+
27
+ # Store yourself
28
+ coords_unit = @map.get_unit(@x, @y)
29
+ if !coords_unit
30
+ @map.set_unit(@x, @y, self)
31
+ else # some town has just built you
32
+ coords_unit.cargo.insert(-1, self) # -1 = to the end
33
+ end
34
+ end
35
+
36
+ # Process engagement targeted at this unit
37
+ def engage!(by_whom)
38
+ if by_whom == @faction
39
+ abort("unit.engage!(): Engaging unit is of the same faction as this one (#{@faction})")
40
+ end
41
+
42
+ # Can it be captured and is it without defenders?
43
+ if can_be_captured?
44
+ if is_transporting?
45
+ cargo[0].attack!(by_whom)
46
+ else # then capture it if you can
47
+ if by_whom.can_capture?
48
+ puts PROMPT + by_whom.to_s + ' is capturing ' + to_s
49
+ capture!(by_whom.faction)
50
+ else
51
+ puts PROMPT + by_whom.to_s + ' can\'t capture other units'
52
+ end
53
+ end
54
+ else
55
+ attack!(by_whom) # uncapturable transporters can't get help from their cargo
56
+ end
57
+ end
58
+
59
+ # Process attack targeted at this unit
60
+ def attack!(by_whom)
61
+ puts PROMPT + by_whom.to_s + ' is attacking ' + to_s
62
+ destroy! # TODO proper combat
63
+ end
64
+
65
+ # Process capture targeted at this unit
66
+ def capture!(by_whom)
67
+ unless can_be_captured?
68
+ abort("unit.capture!(): This unit can\'t be captured")
69
+ end
70
+
71
+ if by_whom == @faction
72
+ abort("unit.capture!(): This unit already belongs to faction #{@faction}")
73
+ end
74
+
75
+ # Add <value> to the capturing faction
76
+ @infopane.add_score(by_whom - 1, self.class.value)
77
+ @faction = by_whom
78
+ end
79
+
80
+ # Add <value> to the other faction and remove links to given unit
81
+ def destroy!
82
+ @armor_left = 0 # for non-attack damage
83
+
84
+ # If you are transporting somebody, destroy them first
85
+ @cargo.each { |uu| uu.destroy! }
86
+
87
+ @infopane.add_score(3 - @faction - 1, self.class.value) # TODO more factions?
88
+
89
+ if is_transported?
90
+ coords_unit = @map.get_unit(@x, @y)
91
+ coords_unit.cargo -= [self]
92
+ else
93
+ @map.set_unit(@x, @y, nil)
94
+ end
95
+ end
96
+
97
+ # Process move of unit and take care of its (un)loading or start of engagement
98
+ def check_movement(old_x, old_y)
99
+ if @x != old_x || @y != old_y # only if it moved
100
+ if @moves_left <= 0
101
+ abort("unit.check_movement(): Moving unit does not have enough move points (#{@moves_left} moves)")
102
+ end
103
+
104
+ oldcoords_unit = @map.get_unit(old_x, old_y)
105
+ newcoords_unit = @map.get_unit(@x, @y)
106
+
107
+ # If there is nobody or is there friendly unit with some cargo space (and bigger cargo space)
108
+ if !newcoords_unit || \
109
+ (newcoords_unit.faction == @faction && \
110
+ newcoords_unit.can_transport? && \
111
+ @cargo_max < newcoords_unit.cargo_max && \
112
+ !newcoords_unit.is_full?)
113
+
114
+ # Leave old coordinates
115
+ if oldcoords_unit == self
116
+ @map.set_unit(old_x, old_y, nil)
117
+ else # if you have been transported
118
+ oldcoords_unit.cargo.delete(self)
119
+ end
120
+
121
+ # Get to new coordinates
122
+ if !newcoords_unit
123
+ @map.set_unit(@x, @y, self)
124
+ @cargo.each { |uu|
125
+ uu.x = @x
126
+ uu.y = @y
127
+ }
128
+
129
+ else # if you are going to be transported
130
+ newcoords_unit.cargo.insert(-1, self) # -1 = to the end
131
+ puts PROMPT + to_s + ' got loaded into '+ newcoords_unit.to_s
132
+ @moves_left = 1 unless newcoords_unit.can_build? # use all your left moves unless you are getting loaded into a town
133
+ end
134
+
135
+ else # if there already is somebody that can't transport you (enemy or full friend)
136
+ # Stay on your original tile
137
+ @x = old_x
138
+ @y = old_y
139
+
140
+ # If it was a friend unit
141
+ if newcoords_unit.faction == @faction
142
+ if !newcoords_unit.can_transport?
143
+ puts PROMPT + newcoords_unit.to_s + ' can\'t transport other units'
144
+ else
145
+ if @cargo_max >= newcoords_unit.cargo_max
146
+ puts PROMPT + "#{self.class.name} can\'t fit in #{newcoords_unit.class.name}"
147
+ else # thus newcoords_unit.is_full? is true
148
+ puts PROMPT + newcoords_unit.to_s + ' is already full'
149
+ end
150
+ end
151
+ else
152
+ # Enemy!
153
+ newcoords_unit.engage!(self)
154
+ end
155
+ end
156
+ @moves_left -= 1
157
+
158
+ # Check if you are on an invalid type of terrain (unless transported)
159
+ unless is_terrain_suitable?
160
+ puts PROMPT + to_s + " found itself in a bad place"
161
+ destroy!
162
+ end
163
+ end
164
+ end
165
+
166
+ def draw
167
+ @image.draw(@x * TILESIZE, (@y + 1) * TILESIZE, ZUNIT,
168
+ scale_x = 1, scale_y = 1, color = COLOUR[@faction])
169
+ end
170
+
171
+ def is_waiting_for_commands?
172
+ @faction == @infopane.faction and
173
+ function == FUNCNONE and
174
+ can_move?
175
+ end
176
+
177
+ def can_move?
178
+ (can_fly? || can_sail? || can_ride?) && @moves_left > 0
179
+ end
180
+
181
+ def can_fly?
182
+ false
183
+ end
184
+
185
+ def can_sail?
186
+ false
187
+ end
188
+
189
+ def can_ride?
190
+ false
191
+ end
192
+
193
+ def can_build?
194
+ false
195
+ end
196
+
197
+ def can_be_built?
198
+ self.class.price > 0
199
+ end
200
+
201
+ def can_transport?
202
+ @cargo_max > 0
203
+ end
204
+
205
+ def is_full?
206
+ @cargo.size >= @cargo_max # just == should be enough
207
+ end
208
+
209
+ def is_transporting?
210
+ @cargo.size > 0
211
+ end
212
+
213
+ def is_transported?
214
+ @map.get_unit(@x, @y) != self
215
+ end
216
+
217
+ def can_capture?
218
+ false
219
+ end
220
+
221
+ def can_be_captured?
222
+ false
223
+ end
224
+
225
+ # Checks if unit is on the right type of terrain (not for transported units)
226
+ def is_terrain_suitable?
227
+ unless is_transported?
228
+ case @map.tile(@x, @y).terrain
229
+ when TILE_SEA
230
+ return can_fly? || can_sail?
231
+ when TILE_GROUND
232
+ return can_fly? || can_ride?
233
+ end
234
+ end
235
+ true
236
+ end
237
+
238
+ def function
239
+ @function.func
240
+ end
241
+
242
+ # Set desired function
243
+ def set_function!(func, commanding_faction)
244
+ # Check your neutrality
245
+ if @faction == 0
246
+ puts PROMPT + to_s + ": neutral units can't have functions set"
247
+ return
248
+ end
249
+
250
+ # Check origin of command
251
+ if commanding_faction != @faction
252
+ puts PROMPT + to_s + ": this unit does not take commands from other factions"
253
+ return
254
+ end
255
+
256
+ # Check your abilities
257
+ if func == FUNCBUILD and !can_build?
258
+ puts PROMPT + to_s + ": this unit can't build other units"
259
+ return
260
+ end
261
+
262
+ # Check current function and set the new one
263
+ if @function.func == func
264
+ puts PROMPT + to_s + ": function is already set to #{@function.func}"
265
+ else
266
+ @function.func = func
267
+ puts PROMPT + to_s + ": function set to #{@function.func}"
268
+ end
269
+ end
270
+
271
+ def function!
272
+ ret = @function.func!(@map, @infopane)
273
+ puts to_s + ret
274
+ end
275
+
276
+ # Set short info string: type, faction, coordinates
277
+ def to_s
278
+ "#{self.class.name} (FAC#{@faction} #{@x}-#{@y})"
279
+ end
280
+
281
+ # Set long info string: short info string, armor, moves, function, cargo
282
+ def info
283
+ ret = to_s + ": armor #{@armor_left}/#{@armor_max}"
284
+
285
+ if @moves_max > 0
286
+ ret = ret + ", moves #{@moves_left}/#{@moves_max}"
287
+ end
288
+
289
+ ret = ret + ", #{@function.info}"
290
+
291
+ if @cargo_max > 0
292
+ ret = ret + ", cargo #{@cargo.size}/#{@cargo_max}"
293
+ end
294
+
295
+ ret
296
+ end
297
+
298
+ def reset_moves!
299
+ @moves_left = @moves_max
300
+ end
301
+ end
@@ -0,0 +1,63 @@
1
+ class UnitFunction
2
+ attr_accessor :func
3
+
4
+ def initialize(function = FUNCNONE, unit)
5
+ @func = function
6
+ @unit = unit
7
+ end
8
+
9
+ # Set function part of long info string of unit
10
+ def info
11
+ case @func
12
+ when FUNCNONE
13
+ "no function"
14
+ when FUNCBUILD
15
+ unless @unit.project
16
+ abort("unitFunction.info(): No project set (" + @unit.to_s + ")")
17
+ end
18
+ "building #{@unit.project.name} (#{@unit.build_info})"
19
+ when FUNCSENTRY
20
+ "sentrying"
21
+ end
22
+ end
23
+
24
+ def func!(map, infopane)
25
+ case @func
26
+ # Build given unit
27
+ when FUNCBUILD
28
+ unless @unit.project
29
+ abort("unitFunction.func!(): No project set (" + @unit.to_s + ")")
30
+ end
31
+
32
+ @unit.parts_built += 1
33
+
34
+ unless @unit.parts_built >= @unit.parts_needed # just == should be enough
35
+ ret = " built next part of #{@unit.project.name} (#{@unit.build_info} done)"
36
+ else
37
+ ret = " completed building of #{@unit.project.name}"
38
+ @unit.parts_built = 0
39
+ @unit.project.new(@unit.x, @unit.y, @unit.faction, map, infopane)
40
+ end
41
+
42
+ # Wake when enemies are nearby
43
+ when FUNCSENTRY
44
+ units_around = map.all_units.select { |uu|
45
+ (uu.x - @unit.x).abs <= 1 &&
46
+ (uu.y - @unit.y).abs <= 1 &&
47
+ uu.faction != @unit.faction
48
+ }
49
+ if units_around.size > 0
50
+ ret = " woke up"
51
+ @func = FUNCNONE
52
+ else
53
+ ret = " was on sentry duty"
54
+ end
55
+ end
56
+
57
+ unless ret
58
+ abort("unitFunction.func!(): Functionable unit didn't function (" + @unit.to_s + ")")
59
+ end
60
+
61
+ ret
62
+ end
63
+ end