smash_and_grab 0.0.3alpha → 0.0.5alpha

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 (36) hide show
  1. data/Gemfile.lock +1 -1
  2. data/README.md +31 -16
  3. data/config/gui/schema.yml +3 -2
  4. data/config/map/entities.yml +36 -12
  5. data/lib/smash_and_grab/abilities/ability.rb +5 -0
  6. data/lib/smash_and_grab/abilities/melee.rb +11 -5
  7. data/lib/smash_and_grab/abilities/ranged.rb +20 -0
  8. data/lib/smash_and_grab/abilities/sprint.rb +4 -0
  9. data/lib/smash_and_grab/abilities.rb +3 -1
  10. data/lib/smash_and_grab/fidgit_ext/event.rb +77 -0
  11. data/lib/smash_and_grab/gui/editor_selector.rb +54 -73
  12. data/lib/smash_and_grab/gui/entity_panel.rb +110 -0
  13. data/lib/smash_and_grab/gui/entity_summary.rb +9 -8
  14. data/lib/smash_and_grab/gui/game_log.rb +44 -0
  15. data/lib/smash_and_grab/gui/info_panel.rb +39 -95
  16. data/lib/smash_and_grab/gui/object_panel.rb +37 -0
  17. data/lib/smash_and_grab/gui/scenario_panel.rb +21 -0
  18. data/lib/smash_and_grab/history/editor_actions/place_object.rb +1 -1
  19. data/lib/smash_and_grab/main.rb +11 -16
  20. data/lib/smash_and_grab/map/map.rb +1 -0
  21. data/lib/smash_and_grab/map/tile.rb +6 -3
  22. data/lib/smash_and_grab/map/wall.rb +4 -2
  23. data/lib/smash_and_grab/mouse_selection.rb +103 -46
  24. data/lib/smash_and_grab/objects/entity.rb +219 -30
  25. data/lib/smash_and_grab/objects/static.rb +7 -5
  26. data/lib/smash_and_grab/objects/vehicle.rb +7 -5
  27. data/lib/smash_and_grab/objects/world_object.rb +13 -3
  28. data/lib/smash_and_grab/path.rb +13 -7
  29. data/lib/smash_and_grab/players/player.rb +15 -10
  30. data/lib/smash_and_grab/states/edit_level.rb +17 -0
  31. data/lib/smash_and_grab/states/play_level.rb +20 -10
  32. data/lib/smash_and_grab/version.rb +1 -1
  33. data/lib/smash_and_grab.rb +18 -14
  34. data/test/smash_and_grab/abilities/melee_test.rb +37 -39
  35. data/test/teststrap.rb +3 -3
  36. metadata +21 -16
@@ -0,0 +1,110 @@
1
+ module SmashAndGrab
2
+ module Gui
3
+ class EntityPanel < Fidgit::Horizontal
4
+ event :info_toggled
5
+
6
+ def initialize(entity, info_shown, options = {})
7
+ options = {
8
+ padding: 0,
9
+ spacing: 8,
10
+ }.merge! options
11
+
12
+ super options
13
+
14
+ @entity = entity
15
+ @info_shown = info_shown
16
+
17
+ vertical padding: 0 do
18
+ # TODO: Clicking on portrait should center.
19
+ @portrait = image_frame @entity.image, padding: 0, background_color: Color::GRAY
20
+ @info_toggle = toggle_button "Bio", value: @info_shown, tip: "Show/hide biography",
21
+ font_height: 14, align_h: :center do |_, value|
22
+ @info_shown = value
23
+ publish :info_toggled, value
24
+ switch_sub_panel
25
+ end
26
+ end
27
+
28
+ vertical padding: 0, spacing: 4 do
29
+ @name = label @entity.name
30
+ @sub_panel_container = vertical spacing: 0, padding: 0
31
+ end
32
+
33
+ create_details_sub_panel
34
+ create_info_sub_panel
35
+ switch_sub_panel
36
+
37
+ update_details @entity
38
+
39
+ @entity.subscribe :changed, method(:update_details)
40
+ end
41
+
42
+ def switch_sub_panel
43
+ @sub_panel_container.clear
44
+ @sub_panel_container.add @info_shown ? @info_sub_panel : @details_sub_panel
45
+ end
46
+
47
+ def create_info_sub_panel
48
+ @info_sub_panel = Fidgit::Vertical.new padding: 0, spacing: 0 do
49
+ text = nil
50
+ scroll = scroll_window width: 350, height: 72 do
51
+ text = text_area text: "#{@entity.name} once ate a pomegranate, but it took all day and all night... " * 5,
52
+ width: 330, font_height: 14, enabled: false
53
+ end
54
+ scroll.background_color = text.background_color
55
+ end
56
+ end
57
+
58
+ def create_details_sub_panel
59
+ @details_sub_panel = Fidgit::Horizontal.new padding: 0, spacing: 0 do
60
+ vertical padding: 0, spacing: 1, width: 160 do
61
+ @health = label "", font_height: 20
62
+ @movement_points = label "", font_height: 20
63
+ @action_points = label "", font_height: 20
64
+ end
65
+
66
+ grid num_columns: 4, spacing: 4, padding: 0 do
67
+ button_options = { font_height: 20, width: 28, height: 28, padding: 0, padding_left: 8 }
68
+
69
+ @ability_buttons = {}
70
+
71
+ label_options = button_options.merge border_thickness: 2, border_color: Color.rgba(255, 255, 255, 100)
72
+
73
+ [:melee, :ranged, :sprint].each do |ability_name|
74
+ if @entity.has_ability? ability_name
75
+ ability = @entity.ability ability_name
76
+ @ability_buttons[ability_name] = button("#{ability_name.to_s[0].upcase}#{ability.skill}",
77
+ button_options.merge(tip: ability.tip)) do
78
+
79
+ @entity.use_ability :sprint if ability_name == :sprint
80
+ end
81
+ else
82
+ label "", label_options
83
+ end
84
+ end
85
+
86
+ 5.times do |i|
87
+ label "", label_options
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def update_details(entity)
94
+ @health.text = "HP: #{entity.health} / #{entity.max_health}"
95
+ @movement_points.text = "MP: #{entity.mp} / #{entity.max_mp}"
96
+ @action_points.text = "AP: #{entity.ap} / #{entity.max_ap}"
97
+
98
+ if entity.has_ability? :sprint
99
+ @movement_points.text += " + #{entity.ability(:sprint).movement_bonus}"
100
+ end
101
+
102
+ @ability_buttons.each do |ability, button|
103
+ button.enabled = (entity.active? and (entity.has_ability?(ability) and entity.action_points >= entity.ability(ability).action_cost))
104
+ end
105
+
106
+ @portrait.image = entity.image
107
+ end
108
+ end
109
+ end
110
+ end
@@ -22,8 +22,8 @@ class EntitySummary < Fidgit::Vertical
22
22
 
23
23
  vertical padding: 0, spacing: 0 do
24
24
  @health = label "", font_height: 15
25
- @movement_points = label "", font_height: 15
26
25
  @action_points = label "", font_height: 15
26
+ @movement_points = label "", font_height: 15
27
27
  end
28
28
  end
29
29
 
@@ -31,10 +31,11 @@ class EntitySummary < Fidgit::Vertical
31
31
  end
32
32
 
33
33
  public
34
- def update
35
- @health.text = "HP: #{@entity.health}"
36
- @movement_points.text = "MP: #{@entity.mp}"
37
- @action_points.text = "AP: #{@entity.ap}"
34
+ def update_details(entity)
35
+ @health.text = "HP: #{entity.health}"
36
+ @movement_points.text = "MP: #{entity.mp}"
37
+ @action_points.text = "AP: #{entity.ap}"
38
+ @portrait.image = entity.portrait
38
39
  end
39
40
 
40
41
  public
@@ -46,10 +47,10 @@ class EntitySummary < Fidgit::Vertical
46
47
  def entity=(entity)
47
48
  @entity = entity
48
49
 
49
- @name.text = " " + entity.name[0...13]
50
- @portrait.image = entity.portrait
50
+ @name.text = " " + @entity.name[0...13]
51
+ @entity.subscribe :changed, &method(:update_details)
51
52
 
52
- update
53
+ update_details @entity
53
54
 
54
55
  entity
55
56
  end
@@ -0,0 +1,44 @@
1
+ module SmashAndGrab
2
+ module Gui
3
+ class GameLog < Fidgit::Composite
4
+ include Log
5
+
6
+ MAX_ITEMS = 100
7
+
8
+ def initialize(state, options = {})
9
+ options = {
10
+ padding: 0,
11
+ }.merge! options
12
+
13
+ super options
14
+
15
+ @items = []
16
+
17
+ @scroll_window = scroll_window width: 420, height: 72, padding: 0 do
18
+ # 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
+ end
21
+
22
+ @scroll_window.background_color = @text.background_color
23
+
24
+ state.subscribe :game_info do |_, text|
25
+ append text
26
+ log.info { "game_info: #{text}" }
27
+ end
28
+
29
+ state.subscribe :game_heading do |_, text|
30
+ append "<c=AAAADD> { #{text} }</c>"
31
+ log.info { "game_heading: #{text}" }
32
+ end
33
+ end
34
+
35
+ def append(text)
36
+ @items.shift until @items.size < MAX_ITEMS
37
+ @items << text
38
+ @text.text = @items.join "\n"
39
+
40
+ @scroll_window.offset_y = Float::INFINITY # Scroll to bottom.
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,105 +1,49 @@
1
- module SmashAndGrab
2
- module Gui
3
- class InfoPanel < Fidgit::Vertical
4
- def initialize(options = {})
5
- options = {
6
- padding: 4,
7
- background_color: Color::BLACK,
8
- }.merge! options
9
- super options
10
-
11
- @frame = horizontal padding: 4, spacing: 8, background_color: Color.rgb(0, 0, 150), width: 440, height: 112 do
12
- @portrait = image_frame Objects::Entity.sprites[0, 0], padding: 0, background_color: Color::GRAY
1
+ require_relative "entity_panel"
2
+ require_relative "object_panel"
3
+ require_relative "scenario_panel"
13
4
 
14
- vertical padding: 0, spacing: 4 do
15
- @name = label " "
16
-
17
- horizontal padding: 0, spacing: 0 do
18
- vertical padding: 0, spacing: 1, width: 160 do
19
- @health = label "", font_height: 20
20
- @movement_points = label "", font_height: 20
21
- @action_points = label "", font_height: 20
22
- end
5
+ module SmashAndGrab
6
+ module Gui
7
+ # The info panel contains scenario/entity/object panels based what is selected.
8
+ class InfoPanel < Fidgit::Vertical
9
+ attr_reader :object
10
+
11
+ def initialize(state, options = {})
12
+ options = {
13
+ padding: 4,
14
+ background_color: Color::BLACK,
15
+ }.merge! options
16
+ super options
17
+
18
+ @object = nil
19
+ @show_info = false
20
+ @scenario_panel = ScenarioPanel.new state
21
+ @frame = vertical padding: 4, background_color: Color.rgb(0, 0, 150), width: 440, height: 112
22
+
23
+ self.x, self.y = ($window.width - width) / 2, $window.height - height
24
+ end
23
25
 
24
- grid num_columns: 4, spacing: 4, padding: 0 do
25
- button_options = { font_height: 20, width: 28, height: 28, padding: 0, padding_left: 8 }
26
+ def object=(object)
27
+ return if @object == object
28
+ @scenario_panel.parent = nil
26
29
 
27
- @ability_buttons = {}
28
- @ability_buttons[:melee] = button "Me", button_options
29
- @ability_buttons[:ranged] = button "Ra", button_options
30
- @ability_buttons[:sprint] = button "Sp", button_options do
31
- @entity.map.actions.do :ability, @entity.ability(:sprint).action_data
30
+ @frame.clear
31
+ case object
32
+ when Objects::Entity
33
+ panel = EntityPanel.new(object, @show_info, parent: @frame)
34
+ panel.subscribe :info_toggled do |_, shown|
35
+ @show_info = shown
32
36
  end
33
-
34
- @ability_buttons[:a] = button "??", button_options.merge(tip: "???")
35
-
36
- @ability_buttons[:b] = button "P1", button_options.merge(tip: "power1")
37
- @ability_buttons[:c] = button "P2", button_options.merge(tip: "power2")
38
- @ability_buttons[:d] = button "P3", button_options.merge(tip: "power3")
39
- @ability_buttons[:e] = button "P4", button_options.merge(tip: "power4")
40
- end
37
+ when Objects::Static, Objects::Vehicle
38
+ ObjectPanel.new(object, parent: @frame)
39
+ when nil
40
+ @frame.add @scenario_panel
41
+ else
42
+ raise object.inspect
41
43
  end
42
- end
43
- end
44
-
45
- recalc
46
-
47
- self.x, self.y = ($window.width - width) / 2, $window.height - height
48
- end
49
-
50
- public
51
- def update
52
- return unless @entity
53
-
54
- @health.text = "HP: #{@entity.health} / #{@entity.max_health}"
55
- @movement_points.text = "MP: #{@entity.mp} / #{@entity.max_mp}"
56
- @action_points.text = "AP: #{@entity.ap} / #{@entity.max_ap}"
57
-
58
- if @entity.has_ability? :sprint
59
- @movement_points.text += " +#{@entity.ability(:sprint).movement_bonus}"
60
- end
61
-
62
- @ability_buttons.each do |ability, button|
63
- button.enabled = (@entity.has_ability?(ability) and @entity.action_points >= @entity.ability(ability).action_cost)
64
- end
65
- end
66
44
 
67
- public
68
- def entity=(entity)
69
- @entity = entity
70
-
71
- @frame.shown = (not entity.nil?)
72
-
73
- if entity
74
- @portrait.image = @entity.image
75
- @name.text = @entity.name
76
-
77
- if @entity.has_ability? :melee
78
- melee = @entity.ability(:melee)
79
- @ability_buttons[:melee].tip = "Melee[#{melee.skill}] - attack in hand-to-hand combat"
80
- else
81
- @ability_buttons[:melee].tip = "Melee[n/a]"
45
+ @object = object
82
46
  end
83
-
84
- if @entity.has_ability? :ranged
85
- ranged = @entity.ability(:ranged)
86
- @ability_buttons[:ranged].tip = "Ranged[#{ranged.skill}] - attack in ranged combat"
87
- else
88
- @ability_buttons[:ranged].tip = "Ranged[n/a]"
89
- end
90
-
91
- if @entity.has_ability? :sprint
92
- sprint = @entity.ability(:sprint)
93
- @ability_buttons[:sprint].tip = "Sprint[#{sprint.skill}] - gain #{sprint.movement_bonus} movement points"
94
- else
95
- @ability_buttons[:sprint].tip = "Sprint[n/a]"
96
- end
97
-
98
- update
99
47
  end
100
-
101
- entity
102
48
  end
103
49
  end
104
- end
105
- end
@@ -0,0 +1,37 @@
1
+ module SmashAndGrab
2
+ module Gui
3
+ class ObjectPanel < Fidgit::Horizontal
4
+ def initialize(object, options = {})
5
+ options = {
6
+ padding: 0,
7
+ spacing: 8,
8
+ }.merge! options
9
+
10
+ super options
11
+
12
+ @object = object
13
+
14
+ vertical padding: 0 do
15
+ # TODO: Clicking on portrait should center.
16
+ @portrait = image_frame @object.image, padding: 0, background_color: Color::GRAY,
17
+ factor: (@object.is_a?(Objects::Vehicle) ? 0.25 : 1)
18
+
19
+ @object.subscribe :changed do
20
+ @portrait.image = @object.image
21
+ end
22
+ end
23
+
24
+ vertical padding: 0, spacing: 4 do
25
+ @name = label @object.name
26
+
27
+ Fidgit::Vertical.new padding: 0, spacing: 0 do
28
+ scroll_window width: 350, height: 72 do
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
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "game_log"
2
+
3
+ module SmashAndGrab
4
+ module Gui
5
+ class ScenarioPanel < Fidgit::Vertical
6
+ def initialize(state, options = {})
7
+ options = {
8
+ padding: 0,
9
+ spacing: 8,
10
+ }.merge! options
11
+ super options
12
+
13
+ label "Scenario: Bank raid"
14
+
15
+ horizontal padding_h: 4, padding_v: 0 do
16
+ @game_log = GameLog.new(state, parent: self)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -17,7 +17,7 @@ class PlaceObject < EditorAction
17
17
  @new_object = @object_class.new @tile.map,
18
18
  type: @type,
19
19
  tile: @tile.grid_position,
20
- facing: :left
20
+ facing: :right
21
21
  end
22
22
 
23
23
  def undo
@@ -11,13 +11,7 @@ def require_folder(path, files)
11
11
  end
12
12
 
13
13
  module SmashAndGrab
14
- USER_PATH = if ENV['APPDATA']
15
- File.join(ENV['APPDATA'].gsub("\\", "/"), "Smash And Grab")
16
- else
17
- File.expand_path("~/.smash_and_grab")
18
- end
19
-
20
- SAVE_PATH = File.join(USER_PATH, "saves")
14
+ SAVE_PATH = File.join(USER_DATA_PATH, "saves")
21
15
  end
22
16
 
23
17
  require_relative "log"
@@ -26,13 +20,11 @@ SmashAndGrab::Log.log.info { "Smash and Grab loading; please wait.." }
26
20
  t = Time.now
27
21
 
28
22
  begin
29
- require 'rubygems' unless defined? OSX_EXECUTABLE
30
- rescue LoadError
31
- end
32
-
33
-
34
- begin
35
- require 'bundler/setup' unless defined?(OSX_EXECUTABLE) or ENV['OCRA_EXECUTABLE']
23
+ # Running as an executable makes bundler irrelevant; from a gem means someone else decides about whether to use Bundler.
24
+ unless RUNNING_FROM_EXECUTABLE or Gem.loaded_specs.has_key? APP_NAME
25
+ require 'bundler'
26
+ Bundler.setup :default
27
+ end
36
28
 
37
29
  rescue LoadError
38
30
  $stderr.puts "Bundler gem not installed. To install:\n gem install bundler"
@@ -51,7 +43,7 @@ require 'chingu'
51
43
 
52
44
  require 'fidgit'
53
45
  Fidgit::Element.schema.merge_schema! YAML.load(File.read(File.expand_path('config/gui/schema.yml', EXTRACT_PATH)))
54
- require_folder("fidgit_ext", %w[element container cursor])
46
+ require_folder("fidgit_ext", %w[event element container cursor])
55
47
 
56
48
  require 'texplay'
57
49
  require_folder('texplay_ext', %w[color image window])
@@ -76,9 +68,9 @@ require_folder "std_ext", %w[array hash]
76
68
 
77
69
  # Include other files.
78
70
  require_folder("", %w[version sprite_sheet z_order z_order_recorder game_window mouse_selection])
79
- require_folder("gui", %w[minimap editor_selector entity_summary info_panel])
80
71
  require_folder("map", %w[tile wall map])
81
72
  require_folder("objects", %w[static entity vehicle])
73
+ require_folder("gui", %w[minimap editor_selector entity_summary info_panel])
82
74
  require_folder("states", %w[edit_level play_level main_menu])
83
75
  require_folder("players", %w[player])
84
76
  require_folder("history", %w[editor_action_history game_action_history])
@@ -90,5 +82,8 @@ SmashAndGrab::GameWindow.new
90
82
  SmashAndGrab::Log.log.debug { "Window created in #{"%.3f" % (Time.now - t)} s" }
91
83
 
92
84
  unless defined? Ocra or defined? Bacon
85
+ SmashAndGrab::Log.log.info { "Game window opened" }
93
86
  $window.show
87
+
88
+ SmashAndGrab::Log.log.info { "Game window closed" }
94
89
  end
@@ -42,6 +42,7 @@ class Map
42
42
 
43
43
  def add_effect(effect); @effects << effect; end
44
44
  def remove_effect(effect); @effects.delete effect; end
45
+ def busy?; factions.any? {|f| f.entities.any?(&:busy?) }; end
45
46
 
46
47
  # tile_classes: Nested arrays of Tile class names (Tile::Grass is represented as "Grass")
47
48
  def initialize(data)
@@ -25,11 +25,14 @@ class Tile < GameObject
25
25
  def position; [@x, @y]; end
26
26
  def needs_to_be_seen?; (@temp_occlusions > 0) or @object; end # Causes walls to become transparent.
27
27
  def blocks_sight?; @type == 'none' or (@object and @object.blocks_sight?); end
28
+ def zoc?(faction); entities_exerting_zoc(faction).any?; end
28
29
 
29
30
  # Blank white tile, useful for colourising tiles.
30
- def self.blank; @@sprites[0]; end
31
- def self.config; @@config ||= YAML.load_file(File.expand_path("config/map/tiles.yml", EXTRACT_PATH)); end
32
- def self.sprites; @@sprites ||= SpriteSheet.new("floor_tiles.png", WIDTH, HEIGHT, 4); end
31
+ class << self
32
+ def blank; @sprites[0]; end
33
+ 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
35
+ end
33
36
 
34
37
  attr_reader :entities_exerting_zoc
35
38
 
@@ -32,8 +32,10 @@ class Wall < GameObject
32
32
 
33
33
  def blocks_sight?; @blocks_sight; end
34
34
 
35
- def self.config; @@config ||= YAML.load_file(File.expand_path("config/map/walls.yml", EXTRACT_PATH)); end
36
- def self.sprites; @@sprites ||= SpriteSheet.new("walls.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8); end
35
+ class << self
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
38
+ end
37
39
 
38
40
  def initialize(map, data)
39
41
  options = {