empi 0.19 → 0.24

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,17 @@
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
- @armor_left = @armor_max = 3
14
+ @armour_left = @armour_max = 3
12
15
  @moves_max = 5
13
16
  end
14
17
 
@@ -1,14 +1,17 @@
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
- @armor_left = @armor_max = 1
14
+ @armour_left = @armour_max = 1
12
15
  @moves_max = 2
13
16
  @cargo_max = 3
14
17
  end
@@ -0,0 +1,108 @@
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
+ @armour_left = @armour_max = 1
21
+ @moves_max = 0
22
+ @cargo_max = 10
23
+
24
+ @starting_project = Army unless @faction == 0 # used once at the game start
25
+ @default_project = Army # used after capture
26
+ @project = nil
27
+ @parts_built = 0
28
+ @parts_needed = 0
29
+
30
+ set_function!(FUNCBUILD, @faction) unless @faction == 0
31
+ end
32
+
33
+ def can_build?
34
+ true
35
+ end
36
+
37
+ def can_be_captured?
38
+ true
39
+ end
40
+
41
+ # Tell the state of current build project
42
+ def build_info
43
+ "#{@parts_built}/#{@parts_needed}"
44
+ end
45
+
46
+ # Process capture targeted at this town and reset build process
47
+ def capture!(by_whom)
48
+ super
49
+
50
+ # Reset build process
51
+ # 1) remove old project so that it is not disclosed
52
+ # 2) set default project so that there is always some set (remove old parts)
53
+ # 3) offer change of the project to the new owner
54
+ @function.func = FUNCNONE
55
+ @project = nil
56
+ set_project!(@default_project)
57
+ set_function!(FUNCBUILD, @faction)
58
+ end
59
+
60
+ # Set desired function and possibly also project
61
+ def set_function!(func, commanding_faction)
62
+ super
63
+
64
+ if @faction != 0 and commanding_faction == @faction and func == FUNCBUILD
65
+ # Set starting project once or ask player about next project
66
+ if @starting_project
67
+ set_project!(@starting_project)
68
+ @starting_project = nil
69
+ else
70
+ GameState.switch!(BuildState.instance)
71
+ BuildState.instance.unit = self
72
+ end
73
+ end
74
+ end
75
+
76
+ # Set desired project
77
+ def set_project!(desired_project)
78
+ unless price_list.key?(desired_project)
79
+ abort("town.set_project!(): Unknown project (#{desired_project})")
80
+ end
81
+ @parts_needed = price_list[desired_project]
82
+
83
+ # Compare new setting with the old one
84
+ if desired_project == @project
85
+ puts PROMPT + to_s + ": project has already been set to #{@project.name} (#{build_info} done)"
86
+ else
87
+ previous_project = @project
88
+ @project = desired_project
89
+ lost_parts = @parts_built
90
+ @parts_built = 0
91
+
92
+ new_project_set_text = PROMPT + to_s + ": project set to #{@project.name} (#{build_info} done)"
93
+ if previous_project and lost_parts > 0 # parts were lost but not due to capture
94
+ puts new_project_set_text + ", losing #{lost_parts} " +
95
+ "part#{ 's' unless lost_parts == 1 } of #{previous_project.name}"
96
+ else
97
+ puts new_project_set_text
98
+ end
99
+ end
100
+ end
101
+
102
+ # Load all prices
103
+ def price_list
104
+ prices = Hash[
105
+ [Army, Ship].collect { |ii| [ii, ii.price] } # TODO all subclasses of Unit which can_be_built?
106
+ ]
107
+ end
108
+ end
@@ -1,10 +1,13 @@
1
1
  require_relative './unitFunction'
2
2
 
3
- PARTS_TO_BE_BUILT = 0
4
-
5
3
  # Both capturable and movable game pieces
6
4
  class Unit
7
- attr_accessor :x, :y, :faction, :function, :cargo
5
+ class << self
6
+ attr_reader :name, :map_symbol, :price, :value
7
+ end
8
+
9
+ attr_reader :armour_left
10
+ attr_accessor :x, :y, :faction, :function, :cargo, :cargo_max
8
11
 
9
12
  def initialize(x, y, faction, map, infopane)
10
13
  @x = x
@@ -13,10 +16,8 @@ class Unit
13
16
  @map = map
14
17
  @infopane = infopane
15
18
 
16
- @name = 'unit'
17
- @value = 1
18
- @armor_max = 1
19
- @armor_left = @armor_max
19
+ @armour_max = 1
20
+ @armour_left = @armour_max
20
21
  @moves_max = 1
21
22
  @moves_left = @moves_max
22
23
  @cargo = [] # transported units
@@ -72,16 +73,18 @@ class Unit
72
73
  end
73
74
 
74
75
  # Add <value> to the capturing faction
75
- @infopane.add_score(by_whom - 1, @value)
76
+ @infopane.add_score(by_whom - 1, self.class.value)
76
77
  @faction = by_whom
77
78
  end
78
79
 
79
80
  # Add <value> to the other faction and remove links to given unit
80
81
  def destroy!
82
+ @armour_left = 0 # for non-attack damage
83
+
81
84
  # If you are transporting somebody, destroy them first
82
85
  @cargo.each { |uu| uu.destroy! }
83
86
 
84
- @infopane.add_score(3 - @faction - 1, @value) # TODO more factions?
87
+ @infopane.add_score(3 - @faction - 1, self.class.value) # TODO more factions?
85
88
 
86
89
  if is_transported?
87
90
  coords_unit = @map.get_unit(@x, @y)
@@ -101,10 +104,11 @@ class Unit
101
104
  oldcoords_unit = @map.get_unit(old_x, old_y)
102
105
  newcoords_unit = @map.get_unit(@x, @y)
103
106
 
104
- # If there is nobody or is there friendly unit with some cargo space
107
+ # If there is nobody or is there friendly unit with some cargo space (and bigger cargo space)
105
108
  if !newcoords_unit || \
106
109
  (newcoords_unit.faction == @faction && \
107
110
  newcoords_unit.can_transport? && \
111
+ @cargo_max < newcoords_unit.cargo_max && \
108
112
  !newcoords_unit.is_full?)
109
113
 
110
114
  # Leave old coordinates
@@ -115,35 +119,39 @@ class Unit
115
119
  end
116
120
 
117
121
  # Get to new coordinates
118
- if newcoords_unit == nil
122
+ if !newcoords_unit
119
123
  @map.set_unit(@x, @y, self)
120
124
  @cargo.each { |uu|
121
125
  uu.x = @x
122
126
  uu.y = @y
123
127
  }
124
128
 
125
- else # if you are going to be transported
129
+ else # if you are going to be transported
126
130
  newcoords_unit.cargo.insert(-1, self) # -1 = to the end
127
131
  puts PROMPT + to_s + ' got loaded into '+ newcoords_unit.to_s
128
132
  @moves_left = 1 unless newcoords_unit.can_build? # use all your left moves unless you are getting loaded into a town
129
133
  end
130
134
 
131
135
  else # if there already is somebody that can't transport you (enemy or full friend)
132
- # Stay on your original tile
133
- @x = old_x
134
- @y = old_y
135
-
136
- # If it was a friend unit
137
- if newcoords_unit.faction == @faction
138
- if !newcoords_unit.can_transport?
139
- puts PROMPT + newcoords_unit.to_s + ' can\'t transport other units'
140
- else # newcoords_unit.can_transport?; has to be full then
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
141
148
  puts PROMPT + newcoords_unit.to_s + ' is already full'
142
149
  end
143
- else
144
- # Enemy!
145
- newcoords_unit.engage!(self)
146
150
  end
151
+ else
152
+ # Enemy!
153
+ newcoords_unit.engage!(self)
154
+ end
147
155
  end
148
156
  @moves_left -= 1
149
157
 
@@ -155,12 +163,17 @@ class Unit
155
163
  end
156
164
  end
157
165
 
158
-
159
166
  def draw
160
167
  @image.draw(@x * TILESIZE, (@y + 1) * TILESIZE, ZUNIT,
161
168
  scale_x = 1, scale_y = 1, color = COLOUR[@faction])
162
169
  end
163
170
 
171
+ def is_waiting_for_commands?
172
+ @faction == @infopane.faction and
173
+ function == FUNCNONE and
174
+ can_move?
175
+ end
176
+
164
177
  def can_move?
165
178
  (can_fly? || can_sail? || can_ride?) && @moves_left > 0
166
179
  end
@@ -182,7 +195,7 @@ class Unit
182
195
  end
183
196
 
184
197
  def can_be_built?
185
- PARTS_TO_BE_BUILT > 0
198
+ self.class.price > 0
186
199
  end
187
200
 
188
201
  def can_transport?
@@ -227,19 +240,31 @@ class Unit
227
240
  end
228
241
 
229
242
  # Set desired function
230
- def set_function!(func)
231
- unless @faction == 0 # neutral towns dom't need functions
232
- if func == FUNCBUILD and !can_build?
233
- puts PROMPT + to_s + ": this unit can't build other units"
234
- return
235
- end
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
236
249
 
237
- if @function.func == func
238
- puts PROMPT + to_s + ": function is already set to #{@function.func}"
239
- else
240
- @function.func = func
241
- puts PROMPT + to_s + ": function set to #{@function.func}"
242
- end
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}"
243
268
  end
244
269
  end
245
270
 
@@ -250,12 +275,12 @@ class Unit
250
275
 
251
276
  # Set short info string: type, faction, coordinates
252
277
  def to_s
253
- "#{@name} (FAC#{@faction} #{@x}-#{@y})"
278
+ "#{self.class.name} (FAC#{@faction} #{@x}-#{@y})"
254
279
  end
255
280
 
256
- # Set long info string: short info string, armor, moves, function, cargo
281
+ # Set long info string: short info string, armour, moves, function, cargo
257
282
  def info
258
- ret = to_s + ": armor #{@armor_left}/#{@armor_max}"
283
+ ret = to_s + ": armour #{@armour_left}/#{@armour_max}"
259
284
 
260
285
  if @moves_max > 0
261
286
  ret = ret + ", moves #{@moves_left}/#{@moves_max}"
@@ -12,26 +12,31 @@ class UnitFunction
12
12
  when FUNCNONE
13
13
  "no function"
14
14
  when FUNCBUILD
15
- "building #{@unit.project} (#{@unit.build_info})"
15
+ unless @unit.project
16
+ abort("unitFunction.info(): No project set (" + @unit.to_s + ")")
17
+ end
18
+ "building #{@unit.project.name} (#{@unit.build_info})"
16
19
  when FUNCSENTRY
17
20
  "sentrying"
18
21
  end
19
22
  end
20
23
 
21
24
  def func!(map, infopane)
22
- ret = " didn't actually do anything (ERROR)"
23
-
24
25
  case @func
25
26
  # Build given unit
26
27
  when FUNCBUILD
28
+ unless @unit.project
29
+ abort("unitFunction.func!(): No project set (" + @unit.to_s + ")")
30
+ end
31
+
27
32
  @unit.parts_built += 1
28
33
 
29
34
  unless @unit.parts_built >= @unit.parts_needed # just == should be enough
30
- ret = " built next part of #{@unit.project} (#{@unit.build_info} done)"
35
+ ret = " built next part of #{@unit.project.name} (#{@unit.build_info} done)"
31
36
  else
32
- ret = " completed building of #{@unit.project}"
37
+ ret = " completed building of #{@unit.project.name}"
33
38
  @unit.parts_built = 0
34
- Army.new(@unit.x, @unit.y, @unit.faction, map, infopane)
39
+ @unit.project.new(@unit.x, @unit.y, @unit.faction, map, infopane)
35
40
  end
36
41
 
37
42
  # Wake when enemies are nearby
@@ -49,6 +54,10 @@ class UnitFunction
49
54
  end
50
55
  end
51
56
 
57
+ unless ret
58
+ abort("unitFunction.func!(): Functionable unit didn't function (" + @unit.to_s + ")")
59
+ end
60
+
52
61
  ret
53
62
  end
54
63
  end
@@ -0,0 +1,190 @@
1
+ class Cursor
2
+ attr_accessor :freeroam, :info_stopped
3
+
4
+ def initialize(x, y, map, infopane)
5
+ dir_path = File.dirname(__FILE__)
6
+
7
+ @x = x
8
+ @y = y
9
+ @map = map
10
+ @infopane = infopane
11
+
12
+ @image = Gosu::Image.new(dir_path + '/../../media/cursor.png')
13
+ @freeroam = false
14
+ @info_stopped = false
15
+
16
+ # Give to Infopane link to yourself (for freeroam marker)
17
+ if !@infopane
18
+ abort("Cursor.initialize!(): Infopane is not set")
19
+ end
20
+ @infopane.cursor = self
21
+ end
22
+
23
+ def update(button)
24
+ case button
25
+ # Cardinal directions
26
+ when Gosu::KbLeft, Gosu::KbA, Gosu::KB_NUMPAD_4 then
27
+ move!(-1, 0) unless @x <= 0
28
+ when Gosu::KbRight, Gosu::KbD, Gosu::KB_NUMPAD_6 then
29
+ move!(1, 0) unless @x >= MAPX
30
+ when Gosu::KbUp, Gosu::KbW, Gosu::KB_NUMPAD_8 then
31
+ move!(0, -1) unless @y <= 0
32
+ when Gosu::KbDown, Gosu::KbX, Gosu::KB_NUMPAD_2 then
33
+ move!(0, 1) unless @y >= MAPY
34
+
35
+ # Intercardinal directions
36
+ when Gosu::KbQ, Gosu::KB_NUMPAD_7 then
37
+ move!(-1, -1) unless @x <= 0 || @y <= 0
38
+ when Gosu::KbE, Gosu::KB_NUMPAD_9 then
39
+ move!(1, -1) unless @x >= MAPX || @y <= 0
40
+ when Gosu::KbZ, Gosu::KB_NUMPAD_1 then
41
+ move!(-1, 1) unless @x <= 0 || @y >= MAPY
42
+ when Gosu::KbC, Gosu::KB_NUMPAD_3 then
43
+ move!(1, 1) unless @x >= MAPX || @y >= MAPY
44
+
45
+ # Functions
46
+ when Gosu::KbS then
47
+ set_function_to_unit(FUNCSENTRY)
48
+ when Gosu::KbB then
49
+ set_function_to_unit(FUNCBUILD)
50
+ when Gosu::KbN then
51
+ set_function_to_unit(FUNCNONE)
52
+
53
+ # The rest
54
+ when Gosu::KbJ, Gosu::KB_NUMPAD_0 then
55
+ switch_freeroam!
56
+ when Gosu::KbReturn, Gosu::KB_NUMPAD_5 then
57
+ info
58
+ end
59
+
60
+ # If in locked mode, stay at current/jump to next movable unit
61
+ to_next_unit! unless @freeroam
62
+ end
63
+
64
+ def draw
65
+ @image.draw(@x * TILESIZE, (@y + 1) * TILESIZE, ZCURSOR)
66
+ end
67
+
68
+ # Move to coordinates of unit the cursor is locked to
69
+ def warp_to_locked!()
70
+ @x = @locked_to.x
71
+ @y = @locked_to.y
72
+ @local_unit = @locked_to
73
+ end
74
+
75
+ # Move by given change of coordinates
76
+ def move!(xx, yy)
77
+ if freeroam
78
+ @x += xx
79
+ @y += yy
80
+ @local_unit = @map.get_unit(@x, @y)
81
+ # TODO show some basic tile info in infopane
82
+ else
83
+ check_unit
84
+
85
+ # Move the unit first
86
+ @locked_to.x += xx
87
+ @locked_to.y += yy
88
+ @locked_to.check_movement(@x, @y) # cursor coordinates work like old_x, old_y
89
+
90
+ # Is the unit still alive?
91
+ if @locked_to.armour_left > 0
92
+ warp_to_locked! # whether it moved or not
93
+ else
94
+ # It got destroyed so clear last links then so that (object of)
95
+ # given unit can be truly destroyed
96
+ @local_unit = nil
97
+ @locked_to = nil
98
+ end
99
+ end
100
+ end
101
+
102
+ # Tries to set function <func> to local unit
103
+ def set_function_to_unit(func)
104
+ check_unit
105
+
106
+ if @local_unit
107
+ @local_unit.set_function!(func, @infopane.faction)
108
+
109
+ # Update infopane with the new (possibly changed) state
110
+ # (visible only in freeroam mode as in locked one the infopane is
111
+ # overwritten as cursor either jumps away or switches to freeroam mode)
112
+ @infopane.text = @local_unit.info
113
+ else
114
+ puts "no unit to set that function to (at #{@x}-#{@y})"
115
+ end
116
+ end
117
+
118
+ # Reset to locked mode
119
+ def reset!
120
+ @freeroam = true
121
+ switch_freeroam!
122
+ @local_unit = nil
123
+ @locked_to = nil
124
+ to_next_unit!
125
+ end
126
+
127
+ # Find next unit which is still waiting for commands and lock to it
128
+ # (local -> last locked to -> next waiting) or switch (back) to freeroaming
129
+ def to_next_unit!
130
+ if @local_unit and @local_unit.is_waiting_for_commands?
131
+ # Lock to such unit (though it may have already been locked)
132
+ @locked_to = @local_unit
133
+ else
134
+ unless @locked_to and @locked_to.is_waiting_for_commands?
135
+ waiting = @map.all_units.select { |uu| uu.is_waiting_for_commands? }
136
+
137
+ # Are there still some units of active faction waiting for commands?
138
+ if waiting.size <= 0 # == would be enough
139
+ puts 'all movable units without functions moved'
140
+ switch_freeroam!
141
+ return
142
+ end
143
+
144
+ @locked_to = waiting[0] # newly selected one
145
+ end
146
+ end
147
+
148
+ warp_to_locked! # stay at old or go to new
149
+ info unless @info_stopped # due to switching out of the play game state
150
+ end
151
+
152
+ # When cursor is in locked mode there needs to be a local unit it is locked to
153
+ # When cursor is in freeroam mode the local unit should still be loaded
154
+ def check_unit
155
+ if !@freeroam and !@local_unit
156
+ abort("cursor.set_function_to_unit(): Cursor is in locked mode " \
157
+ "but there is no unit it is locked to (at #{@x}-#{@y})")
158
+ end
159
+ end
160
+
161
+ # Switch between being attached to unit and being able to freeroam
162
+ def switch_freeroam!
163
+ if freeroam
164
+ @infopane.text = 'freeroam disabled'
165
+ puts 'freeroam disabled'
166
+ @freeroam = false
167
+ else
168
+ @infopane.text = 'freeroam enabled'
169
+ puts 'freeroam enabled'
170
+ @freeroam = true
171
+ end
172
+ end
173
+
174
+ # Find some info about units on the current tile
175
+ def info
176
+ check_unit
177
+
178
+ if @local_unit
179
+ @infopane.text = @local_unit.info
180
+ puts @local_unit.info
181
+
182
+ if @local_unit.is_transporting?
183
+ @local_unit.cargo.each { |uu| puts '- cargo: ' + uu.info }
184
+ end
185
+ else
186
+ @infopane.text = ''
187
+ puts "no unit to show info of (at #{@x}-#{@y})"
188
+ end
189
+ end
190
+ end