smash_and_grab 0.0.3alpha → 0.0.5alpha

Sign up to get free protection for your applications and to get access to all the features.
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 = {