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