smash_and_grab 0.0.5alpha → 0.0.6alpha

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