smash_and_grab 0.0.5alpha → 0.0.6alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/{CHANGELOG.txt → CHANGELOG.md} +0 -0
  2. data/Gemfile.lock +8 -4
  3. data/LICENSE.txt +20 -0
  4. data/README.md +30 -14
  5. data/Rakefile +2 -2
  6. data/config/lang/objects/entities/en.yml +134 -0
  7. data/config/lang/objects/static/en.yml +8 -0
  8. data/config/lang/objects/vehicles/en.yml +11 -0
  9. data/config/map/entities.yml +42 -38
  10. data/lib/smash_and_grab.rb +5 -0
  11. data/lib/smash_and_grab/abilities.rb +1 -1
  12. data/lib/smash_and_grab/abilities/ability.rb +45 -3
  13. data/lib/smash_and_grab/abilities/drop.rb +38 -0
  14. data/lib/smash_and_grab/abilities/melee.rb +4 -6
  15. data/lib/smash_and_grab/abilities/pick_up.rb +33 -0
  16. data/lib/smash_and_grab/abilities/ranged.rb +18 -12
  17. data/lib/smash_and_grab/abilities/sprint.rb +11 -7
  18. data/lib/smash_and_grab/chingu_ext/basic_game_object.rb +26 -0
  19. data/lib/smash_and_grab/game_window.rb +5 -1
  20. data/lib/smash_and_grab/gui/entity_panel.rb +26 -10
  21. data/lib/smash_and_grab/gui/entity_summary.rb +19 -11
  22. data/lib/smash_and_grab/gui/game_log.rb +6 -2
  23. data/lib/smash_and_grab/gui/info_panel.rb +7 -4
  24. data/lib/smash_and_grab/gui/object_panel.rb +6 -2
  25. data/lib/smash_and_grab/gui/scenario_panel.rb +4 -0
  26. data/lib/smash_and_grab/history/action_history.rb +1 -1
  27. data/lib/smash_and_grab/main.rb +7 -3
  28. data/lib/smash_and_grab/map/faction.rb +26 -8
  29. data/lib/smash_and_grab/map/map.rb +21 -12
  30. data/lib/smash_and_grab/map/tile.rb +29 -2
  31. data/lib/smash_and_grab/map/wall.rb +2 -2
  32. data/lib/smash_and_grab/mixins/has_contents.rb +38 -0
  33. data/lib/smash_and_grab/mixins/line_of_sight.rb +129 -0
  34. data/lib/smash_and_grab/mixins/pathfinding.rb +145 -0
  35. data/lib/smash_and_grab/mouse_selection.rb +107 -36
  36. data/lib/smash_and_grab/objects/entity.rb +311 -260
  37. data/lib/smash_and_grab/objects/floating_text.rb +0 -1
  38. data/lib/smash_and_grab/objects/static.rb +10 -3
  39. data/lib/smash_and_grab/objects/vehicle.rb +7 -2
  40. data/lib/smash_and_grab/objects/world_object.rb +10 -3
  41. data/lib/smash_and_grab/path.rb +38 -12
  42. data/lib/smash_and_grab/players/ai.rb +91 -0
  43. data/lib/smash_and_grab/players/human.rb +9 -0
  44. data/lib/smash_and_grab/players/player.rb +16 -65
  45. data/lib/smash_and_grab/players/remote.rb +9 -0
  46. data/lib/smash_and_grab/sprite_sheet.rb +9 -0
  47. data/lib/smash_and_grab/states/edit_level.rb +15 -4
  48. data/lib/smash_and_grab/states/main_menu.rb +16 -4
  49. data/lib/smash_and_grab/states/play_level.rb +88 -28
  50. data/lib/smash_and_grab/states/world.rb +17 -9
  51. data/lib/smash_and_grab/version.rb +1 -1
  52. data/lib/smash_and_grab/z_order.rb +6 -5
  53. data/media/images/path.png +0 -0
  54. data/media/images/tile_selection.png +0 -0
  55. data/media/images/tiles_selection.png +0 -0
  56. data/smash_and_grab.gemspec +2 -2
  57. data/tasks/create_portraits.rb +39 -0
  58. data/tasks/outline_images.rb +56 -0
  59. data/test/smash_and_grab/abilities/drop_test.rb +68 -0
  60. data/test/smash_and_grab/abilities/melee_test.rb +4 -4
  61. data/test/smash_and_grab/abilities/pick_up_test.rb +68 -0
  62. data/test/smash_and_grab/abilities/ranged_test.rb +105 -0
  63. data/test/smash_and_grab/abilities/sprint_test.rb +55 -25
  64. data/test/smash_and_grab/map/faction_test.rb +18 -16
  65. data/test/smash_and_grab/map/map_test.rb +9 -4
  66. metadata +51 -19
  67. data/lib/smash_and_grab/fidgit_ext/event.rb +0 -77
@@ -16,26 +16,34 @@ class EntitySummary < Fidgit::Vertical
16
16
 
17
17
  @name = label "", font_height: 15
18
18
 
19
- horizontal padding: 0, spacing: 4 do
20
- @portrait = image_frame nil, padding: 0, background_color: Color::GRAY,
19
+ @box = horizontal padding: 0, spacing: 4 do
20
+ @portrait = image_frame nil, background_color: Color::GRAY,
21
21
  border_thickness: 1, border_color: Color::BLACK
22
22
 
23
- vertical padding: 0, spacing: 0 do
24
- @health = label "", font_height: 15
25
- @action_points = label "", font_height: 15
26
- @movement_points = label "", font_height: 15
27
- end
23
+ # Just to get a position to place the stats box.
24
+ @stats_position = vertical padding: 0
28
25
  end
29
26
 
30
- self.entity = entity
27
+ self.entity = entity
28
+ end
29
+
30
+ def draw
31
+ super
32
+ @entity.draw_stat_bars x: @stats_position.x, y: @stats_position.y + 1, zorder: z, factor_x: 6, factor_y: 6
33
+
34
+ if @entity.contents
35
+ # TODO: Measure the sprite properly.
36
+ @entity.contents.image.draw x + width - 10, y - 12, z
37
+ end
31
38
  end
32
39
 
33
40
  public
34
41
  def update_details(entity)
35
- @health.text = "HP: #{entity.health}"
36
- @movement_points.text = "MP: #{entity.mp}"
37
- @action_points.text = "AP: #{entity.ap}"
42
+ self.tip = "HP: #{entity.hp} / #{entity.max_hp}; MP: #{entity.mp} / #{entity.max_mp}; AP: #{entity.ap} / #{entity.max_ap}"
43
+
38
44
  @portrait.image = entity.portrait
45
+ @name.color = entity.alive? ? Color::WHITE : Color::GRAY
46
+ @portrait.enabled = entity.alive?
39
47
  end
40
48
 
41
49
  public
@@ -4,6 +4,7 @@ module SmashAndGrab
4
4
  include Log
5
5
 
6
6
  MAX_ITEMS = 100
7
+ HEADING_COLOR = Color.rgb(200, 200, 230)
7
8
 
8
9
  def initialize(state, options = {})
9
10
  options = {
@@ -16,18 +17,21 @@ module SmashAndGrab
16
17
 
17
18
  @scroll_window = scroll_window width: 420, height: 72, padding: 0 do
18
19
  # TODO: Text-area "editable(?)" seem broken. They don't prevent editing while also allowing copy/pasting.
19
- @text = text_area width: 400, font_height: 14, enabled: false
20
+ @text = text_area width: 400, font_height: 14, editable: false
20
21
  end
21
22
 
22
23
  @scroll_window.background_color = @text.background_color
23
24
 
24
25
  state.subscribe :game_info do |_, text|
25
26
  append text
27
+ text = text.gsub /<[^>]*>/, ''
26
28
  log.info { "game_info: #{text}" }
27
29
  end
28
30
 
29
31
  state.subscribe :game_heading do |_, text|
30
- append "<c=AAAADD> { #{text} }</c>"
32
+
33
+ append HEADING_COLOR.colorize("{ #{text} }")
34
+ text = text.gsub /<[^>]*>/, ''
31
35
  log.info { "game_heading: #{text}" }
32
36
  end
33
37
  end
@@ -15,17 +15,20 @@ module SmashAndGrab
15
15
  }.merge! options
16
16
  super options
17
17
 
18
- @object = nil
19
18
  @show_info = false
20
- @scenario_panel = ScenarioPanel.new state
19
+
21
20
  @frame = vertical padding: 4, background_color: Color.rgb(0, 0, 150), width: 440, height: 112
22
21
 
22
+ @scenario_panel = ScenarioPanel.new state
23
+ self.object = nil
24
+
23
25
  self.x, self.y = ($window.width - width) / 2, $window.height - height
24
26
  end
25
27
 
26
28
  def object=(object)
27
- return if @object == object
28
- @scenario_panel.parent = nil
29
+ return if defined? @object and object == @object
30
+
31
+ @frame.each(&:finalize)
29
32
 
30
33
  @frame.clear
31
34
  case object
@@ -16,7 +16,7 @@ module SmashAndGrab
16
16
  @portrait = image_frame @object.image, padding: 0, background_color: Color::GRAY,
17
17
  factor: (@object.is_a?(Objects::Vehicle) ? 0.25 : 1)
18
18
 
19
- @object.subscribe :changed do
19
+ @changed_event = @object.subscribe :changed do
20
20
  @portrait.image = @object.image
21
21
  end
22
22
  end
@@ -27,11 +27,15 @@ module SmashAndGrab
27
27
  Fidgit::Vertical.new padding: 0, spacing: 0 do
28
28
  scroll_window width: 350, height: 72 do
29
29
  text_area text: "#{@object.name} once was a pomegranate, but it got better... " * 10,
30
- background_color: Color::NONE, width: 330, font_height: 14
30
+ background_color: Color::NONE, width: 330, font_height: 14, editable: false
31
31
  end
32
32
  end
33
33
  end
34
34
  end
35
+
36
+ def finalize
37
+ @changed_event.unsubscribe
38
+ end
35
39
  end
36
40
  end
37
41
  end
@@ -16,6 +16,10 @@ module SmashAndGrab
16
16
  @game_log = GameLog.new(state, parent: self)
17
17
  end
18
18
  end
19
+
20
+ def finalize
21
+ # Don't need to clean up.
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -6,7 +6,7 @@ class ActionHistory < Fidgit::History
6
6
 
7
7
  def_delegators :@actions, :empty?
8
8
 
9
- def completed_turns; @actions.count {|a| a.is_a? GameAction::EndTurn }; end
9
+ def completed_turns; @actions.count {|a| a.is_a? GameActions::EndTurn }; end
10
10
 
11
11
  # Perform a History::Action, adding it to the history.
12
12
  # If there are currently any actions that have been undone, they will be permanently lost and cannot be redone.
@@ -39,11 +39,15 @@ require 'syck' # Required for unknown reason, when ocraed!
39
39
  require 'gosu'
40
40
  require_folder('gosu_ext', %w[font])
41
41
 
42
+ require 'r18n-desktop'
43
+ R18n.from_env File.join(EXTRACT_PATH, 'config/lang'), Gosu.language
44
+
42
45
  require 'chingu'
46
+ require_folder("chingu_ext", %w[basic_game_object])
43
47
 
44
48
  require 'fidgit'
45
49
  Fidgit::Element.schema.merge_schema! YAML.load(File.read(File.expand_path('config/gui/schema.yml', EXTRACT_PATH)))
46
- require_folder("fidgit_ext", %w[event element container cursor])
50
+ require_folder("fidgit_ext", %w[element container cursor])
47
51
 
48
52
  require 'texplay'
49
53
  require_folder('texplay_ext', %w[color image window])
@@ -72,7 +76,7 @@ require_folder("map", %w[tile wall map])
72
76
  require_folder("objects", %w[static entity vehicle])
73
77
  require_folder("gui", %w[minimap editor_selector entity_summary info_panel])
74
78
  require_folder("states", %w[edit_level play_level main_menu])
75
- require_folder("players", %w[player])
79
+ require_folder("players", %w[ai human remote])
76
80
  require_folder("history", %w[editor_action_history game_action_history])
77
81
 
78
82
  SmashAndGrab::Log.log.debug { "Scripts loaded in #{"%.3f" % (Time.now - t)} s" }
@@ -80,7 +84,7 @@ SmashAndGrab::Log.log.debug { "Scripts loaded in #{"%.3f" % (Time.now - t)} s" }
80
84
  t = Time.now
81
85
  SmashAndGrab::GameWindow.new
82
86
  SmashAndGrab::Log.log.debug { "Window created in #{"%.3f" % (Time.now - t)} s" }
83
-
87
+ SmashAndGrab:: FONT_NAME = "UnmaskedBB.ttf"
84
88
  unless defined? Ocra or defined? Bacon
85
89
  SmashAndGrab::Log.log.info { "Game window opened" }
86
90
  $window.show
@@ -1,6 +1,6 @@
1
1
  module SmashAndGrab
2
2
  module Factions
3
- class Faction
3
+ class Faction < BasicGameObject
4
4
  include Fidgit::Event
5
5
  extend Forwardable
6
6
  include Log
@@ -15,18 +15,26 @@ module Factions
15
15
 
16
16
  def friend?(faction); faction.is_a? self.class; end
17
17
  def enemy?(faction); not friend?(faction); end
18
- def to_s; Inflector.demodulize self.class.name; end
19
- def minimap_color; self.class::COLOR; end
18
+ def to_s; name; end
19
+ def minimap_color; self.class::MINIMAP_COLOR; end
20
20
  def active?; @active; end
21
21
  def inactive?; !@active; end
22
+ def colorized_name; self.class::TEXT_COLOR.colorize name; end
23
+ def name; Inflector.demodulize self.class.name; end
22
24
 
23
- def initialize(map)
24
- @map = map
25
+ def initialize
26
+ super
27
+
28
+ @map = nil
25
29
  @entities = []
26
30
  @active = false
27
31
  @player = nil
28
32
  end
29
33
 
34
+ def map=(map)
35
+ @map = map
36
+ end
37
+
30
38
  def <<(entity)
31
39
  @entities << entity
32
40
  end
@@ -37,15 +45,22 @@ module Factions
37
45
 
38
46
  # Start of first turn of the game.
39
47
  def start_game
48
+ raise unless @map
40
49
  start_turn
41
50
  end
42
51
 
43
52
  # Restart from a loaded position.
44
53
  def resume_game
54
+ raise unless @map
45
55
  @active = true
46
56
  end
47
57
 
48
58
  def start_turn
59
+ parent.publish :game_heading, "=== Turn #{map.turn + 1} ==="
60
+ parent.publish :game_info, ""
61
+ parent.publish :game_heading, self.class::TEXT_COLOR.colorize("#{name}' turn (#{Inflector.demodulize player.class})")
62
+ parent.publish :game_info, ""
63
+
49
64
  log.info "#{self} started turn #{@map.turn + 1}"
50
65
  @active = true
51
66
  @entities.each(&:start_turn)
@@ -65,18 +80,21 @@ module Factions
65
80
 
66
81
  # Super-villains
67
82
  class Baddies < Faction
68
- COLOR = Color.rgb(255, 50, 50)
83
+ TEXT_COLOR = Color.rgb(255, 0, 0)
84
+ MINIMAP_COLOR = Color.rgb(255, 50, 50)
69
85
  end
70
86
 
71
87
  # Superheroes, police, g-men, etc.
72
88
  class Goodies < Faction
73
- COLOR = Color.rgb(100, 100, 255)
89
+ TEXT_COLOR = Color.rgb(100, 100, 255)
90
+ MINIMAP_COLOR = Color.rgb(100, 100, 255)
74
91
  def friend?(faction); (faction.is_a? Bystanders) or super; end
75
92
  end
76
93
 
77
94
  # Everyone else (NPCS).
78
95
  class Bystanders < Faction
79
- COLOR = Color.rgb(255, 255, 0)
96
+ TEXT_COLOR = Color.rgb(200, 200, 0)
97
+ MINIMAP_COLOR = Color.rgb(255, 255, 0)
80
98
 
81
99
  def friend?(faction); (faction.is_a? Goodies) or super; end
82
100
  end
@@ -36,7 +36,7 @@ class Map
36
36
  event :wall_type_changed # The actual type itself changed.
37
37
 
38
38
  attr_reader :grid_width, :grid_height, :actions
39
- attr_reader :goodies, :baddies, :bystanders, :active_faction, :turn, :factions
39
+ attr_reader :active_faction, :turn, :factions, :world_objects
40
40
 
41
41
  def to_rect; Rect.new(0, 0, @grid_width * Tile::WIDTH, @grid_height * Tile::HEIGHT); end
42
42
 
@@ -45,9 +45,17 @@ class Map
45
45
  def busy?; factions.any? {|f| f.entities.any?(&:busy?) }; end
46
46
 
47
47
  # tile_classes: Nested arrays of Tile class names (Tile::Grass is represented as "Grass")
48
- def initialize(data)
48
+ def initialize(data, factions, options = {})
49
+ options = {
50
+ start: true,
51
+ }.merge! options
52
+
49
53
  t = Time.now
50
54
 
55
+ raise unless factions.inspect unless factions.size >= 3
56
+ @factions = factions
57
+ @factions.each {|f| f.map = self }
58
+
51
59
  @effects = []
52
60
  @world_objects = []
53
61
  @drawable_objects = []
@@ -79,12 +87,6 @@ class Map
79
87
  Wall.new self, wall_data
80
88
  end
81
89
 
82
- @goodies = Factions::Goodies.new self
83
- @baddies = Factions::Baddies.new self
84
- @bystanders = Factions::Bystanders.new self
85
-
86
- @factions = [@baddies, @goodies, @bystanders] # And order of play.
87
-
88
90
  data[:objects].each do |object_data|
89
91
  case object_data[:class]
90
92
  when Objects::Entity::CLASS
@@ -109,10 +111,12 @@ class Map
109
111
 
110
112
  record
111
113
 
112
- if @actions.empty?
113
- start_game
114
- else
115
- resume_game
114
+ if options[:start]
115
+ if @actions.empty?
116
+ start_game
117
+ else
118
+ resume_game
119
+ end
116
120
  end
117
121
 
118
122
  # Ensure that if any tiles are changed, that the map is redrawn.
@@ -135,6 +139,11 @@ class Map
135
139
  end
136
140
 
137
141
  def start_game
142
+ # Ensure that everyone is at max health when scenario helps (not more or less).
143
+ @factions.each do |faction|
144
+ faction.entities.each {|e| e.start_game }
145
+ end
146
+
138
147
  @active_faction.start_game
139
148
  end
140
149
 
@@ -4,6 +4,18 @@ class Tile < GameObject
4
4
 
5
5
  WIDTH, HEIGHT = 32, 16
6
6
 
7
+ # X
8
+ # / \
9
+ # X T X <- this tile
10
+ # \ / <- covered by any wall here
11
+ # X
12
+ # / \ <- covered by wall height 1+ here
13
+ # X X
14
+ # \ / <- covered by wall height 1+ here
15
+ # X
16
+ # X
17
+ # / \ <- covered by wall height 2+ here
18
+ #
7
19
  # x, y, direction to cause occlusion.
8
20
  WALL_OCCLUSION_POSITIONS = [
9
21
  [[ 0, 0], :left],
@@ -31,7 +43,7 @@ class Tile < GameObject
31
43
  class << self
32
44
  def blank; @sprites[0]; end
33
45
  def config; @config ||= YAML.load_file(File.expand_path("config/map/tiles.yml", EXTRACT_PATH)); end
34
- def sprites; @sprites ||= SpriteSheet.new("floor_tiles.png", WIDTH, HEIGHT, 4); end
46
+ def sprites; @sprites ||= SpriteSheet["floor_tiles.png", WIDTH, HEIGHT, 4]; end
35
47
  end
36
48
 
37
49
  attr_reader :entities_exerting_zoc
@@ -52,6 +64,14 @@ class Tile < GameObject
52
64
  @entities_exerting_zoc = Set.new
53
65
  end
54
66
 
67
+ def overwatched?(faction_targeted)
68
+ return false unless empty?
69
+
70
+ map.factions.any? do |faction|
71
+ faction.enemy?(faction_targeted) and faction.entities.any? {|e| e.overwatch? self }
72
+ end
73
+ end
74
+
55
75
  def entities_exerting_zoc(faction)
56
76
  @entities_exerting_zoc.select {|e| e.action? and e.faction.enemy? faction }
57
77
  end
@@ -165,16 +185,23 @@ class Tile < GameObject
165
185
  @walls[direction] = wall
166
186
  end
167
187
 
188
+ # Returns wall in this direction.
168
189
  def wall(direction)
169
190
  @walls[direction]
170
191
  end
171
192
 
193
+ # @return [Wall, nil] wall between this and other tile.
194
+ def wall_to(tile)
195
+ @walls[direction_to(tile)]
196
+ end
197
+
198
+ # @return [Symbol, nil] Direction to another tile
172
199
  def direction_to(tile)
173
200
  @walls.each_pair do |direction, wall|
174
201
  return direction if wall.destination(self) == tile
175
202
  end
176
203
 
177
- return nil
204
+ nil
178
205
  end
179
206
 
180
207
  def to_json(*a)
@@ -34,7 +34,7 @@ class Wall < GameObject
34
34
 
35
35
  class << self
36
36
  def config; @config ||= YAML.load_file(File.expand_path("config/map/walls.yml", EXTRACT_PATH)); end
37
- def sprites; @sprites ||= SpriteSheet.new("walls.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8); end
37
+ def sprites; @sprites ||= SpriteSheet["walls.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8]; end
38
38
  end
39
39
 
40
40
  def initialize(map, data)
@@ -124,7 +124,7 @@ class Wall < GameObject
124
124
  end
125
125
 
126
126
  def destination(from)
127
- blocks_movement? ? nil : @destinations[from]
127
+ @destinations[from]
128
128
  end
129
129
 
130
130
  def draw
@@ -0,0 +1,38 @@
1
+ module SmashAndGrab
2
+ module Mixins
3
+ module HasContents
4
+ attr_reader :contents
5
+
6
+ def setup_contents
7
+ # @tmp_contents_id was just a holder until we could do this; can't get it to work unless
8
+ # all objects have been loaded.
9
+ @contents = @tmp_contents_id ? map.object_by_id(@tmp_contents_id) : nil
10
+ log.debug { "#{self} started carrying #{@contents}"} if @contents
11
+ @tmp_contents_id = nil
12
+ end
13
+
14
+ def pick_up?(object)
15
+ @contents.nil? and object.pick_up?(self)
16
+ end
17
+
18
+ def pick_up(object)
19
+ raise if @contents
20
+ object.tile = nil
21
+ @contents = object
22
+ parent.publish :game_info, "#{colorized_name} picked up #{object.colorized_name}"
23
+ publish :changed
24
+ nil
25
+ end
26
+
27
+ def drop(tile)
28
+ raise unless @contents
29
+ object = @contents
30
+ @contents = nil
31
+ parent.publish :game_info, "#{colorized_name} dropped #{object.colorized_name}"
32
+ object.tile = tile
33
+ publish :changed
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end