empi 0.18 → 0.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/empi.rb +22 -75
- data/lib/lib/game_states/build_state.rb +82 -0
- data/lib/lib/game_states/game_state.rb +27 -0
- data/lib/lib/game_states/play_state.rb +135 -0
- data/lib/lib/game_states/quit_state.rb +33 -0
- data/lib/lib/game_states/welcome_state.rb +64 -0
- data/lib/{army.rb → lib/units/army.rb} +6 -3
- data/lib/{ship.rb → lib/units/ship.rb} +6 -3
- data/lib/lib/units/town.rb +106 -0
- data/lib/lib/units/unit.rb +301 -0
- data/lib/lib/units/unitFunction.rb +63 -0
- data/lib/lib/user_interface/cursor.rb +184 -0
- data/lib/lib/user_interface/infopane.rb +49 -0
- data/lib/{map.rb → lib/user_interface/map.rb} +32 -26
- data/lib/{tile.rb → lib/user_interface/tile.rb} +2 -2
- data/lib/docu/Empi v18.png b/data/lib/media/Empi → v18.png +0 -0
- data/lib/save/m02-err.esf +26 -0
- metadata +26 -18
- data/lib/cursor.rb +0 -183
- data/lib/docu/Empi v14.png +0 -0
- data/lib/docu/Empi v18 - printouts.png +0 -0
- data/lib/docu/info.txt +0 -282
- data/lib/infopane.rb +0 -36
- data/lib/town.rb +0 -28
- data/lib/unit.rb +0 -238
- data/lib/unitFunction.rb +0 -47
@@ -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 + '
|
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 + '
|
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
|