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
@@ -43,6 +43,10 @@ TEXT
43
43
  options[:dev] = true
44
44
  end
45
45
 
46
+ parser.on('--fullscreen', "Run fullscreen at native resolution") do
47
+ options[:fullscreen] = true
48
+ end
49
+
46
50
  parser.on('--console', 'Console mode (no log file)') do
47
51
  options[:log] = nil # Write to console.
48
52
  end
@@ -75,6 +79,7 @@ TEXT
75
79
  options[:log] = DEFAULT_LOG_FILE_PATH
76
80
  end
77
81
 
82
+ FULLSCREEN = options[:fullscreen]
78
83
  LOG_FILE = options[:log]
79
84
  DEVELOPMENT_MODE = options[:dev]
80
85
 
@@ -1,4 +1,4 @@
1
- require_folder "abilities", %w[area melee move ranged sprint]
1
+ require_folder "abilities", %w[area drop melee move pick_up ranged sprint]
2
2
 
3
3
  module SmashAndGrab::Abilities
4
4
  # Create an ability based on class name.
@@ -2,14 +2,20 @@ module SmashAndGrab::Abilities
2
2
  # @abstract
3
3
  class Ability
4
4
  attr_reader :skill, :owner
5
+ NON_SKILL = 0
5
6
  SKILL_LEVEL_DESCRIPTIONS = %w[Fair Good Excellent Heroic Legendary]
6
7
 
8
+ def use?; true; end
7
9
  def can_be_undone?; true; end
8
10
  def action_cost; @action_cost == :all ? owner.max_action_points : @action_cost; end
9
11
  def type; Inflector.underscore(Inflector.demodulize(self.class.name)).to_sym; end
10
12
 
11
13
  def tip
12
- "#{SKILL_LEVEL_DESCRIPTIONS[skill - 1]} #{type.capitalize} (#{'*' * skill})"
14
+ if skill > NON_SKILL
15
+ "#{SKILL_LEVEL_DESCRIPTIONS[skill - 1]} #{type.capitalize} (#{'*' * skill})"
16
+ else
17
+ type.capitalize
18
+ end
13
19
  end
14
20
 
15
21
  protected
@@ -23,11 +29,15 @@ module SmashAndGrab::Abilities
23
29
  public
24
30
  # Data saved with the character.
25
31
  def to_json(*a)
32
+ to_hash.to_json(*a)
33
+ end
34
+
35
+ def to_hash
26
36
  {
27
37
  type: type,
28
38
  skill: skill,
29
39
  action_cost: @action_cost,
30
- }.to_json(*a)
40
+ }
31
41
  end
32
42
 
33
43
  public
@@ -57,6 +67,8 @@ module SmashAndGrab::Abilities
57
67
  # Ability that has an effect every turn.
58
68
  # @abstract
59
69
  class ContinuousAbility < Ability
70
+ def active?; @active; end
71
+
60
72
  def initialize(owner, data)
61
73
  @active = data[:active]
62
74
  super(owner, data)
@@ -74,9 +86,36 @@ module SmashAndGrab::Abilities
74
86
  # An ability that has an effect, but the player can turn it on and off. e.g. Invisibility.
75
87
  # @abstract
76
88
  class ToggleAbility < ContinuousAbility
89
+ def activate?; owner.action_points >= action_cost and not active?; end
90
+ def deactivate?; owner.movement_points >= movement_bonus and active?; end
91
+
77
92
  def initialize(owner, data)
78
93
  super(owner, { active: false }.merge!(data))
79
94
  end
95
+
96
+ def do(data)
97
+ if active?
98
+ deactivate data
99
+ else
100
+ activate data
101
+ end
102
+ end
103
+
104
+ def undo(data)
105
+ self.do data # Effect is based on current state, not on whether it is done or undone.
106
+ end
107
+
108
+ protected
109
+ def activate(data)
110
+ owner.action_points -= action_cost
111
+ @active = true
112
+ end
113
+
114
+ protected
115
+ def deactivate(data)
116
+ owner.action_points += action_cost
117
+ @active = false
118
+ end
80
119
  end
81
120
 
82
121
  # Ability that can be activated, but only on oneself.
@@ -97,10 +136,13 @@ module SmashAndGrab::Abilities
97
136
  target_position: target_tile.grid_position
98
137
  )
99
138
  end
139
+
140
+ protected
141
+ def target(data); owner.map.object_by_id(data[:target_id]); end
100
142
  end
101
143
 
102
144
  # An ability that requires that the actor be adjacent to the target
103
- # and will move them if necessary. E.g. Melee.
145
+ # and will move them if necessary. E.g. Melee or PickUp.
104
146
  # @abstract
105
147
  class TouchAbility < TargetedAbility
106
148
  end
@@ -0,0 +1,38 @@
1
+ require_relative "ability"
2
+
3
+ # Pick up an object from the ground
4
+ module SmashAndGrab::Abilities
5
+ class Drop < TargetedAbility
6
+ def can_be_undone?; true; end
7
+ def use?; !owner.contents.nil? and owner.tile.adjacent_tiles(owner).any?(&:empty?); end
8
+
9
+ def tip
10
+ "#{super} #{owner.contents ? owner.contents.name : "something" }"
11
+ end
12
+
13
+ def initialize(owner, data)
14
+ super(owner, data.merge(action_cost: 1, skill: NON_SKILL))
15
+ end
16
+
17
+ def action_data
18
+ # TODO: find a way to get a specific position.
19
+ target_tile = owner.tile.adjacent_tiles(owner).find_all(&:empty?).sample
20
+
21
+ super(target_tile).merge(
22
+ target_id: owner.contents.id
23
+ )
24
+ end
25
+
26
+ def do(data)
27
+ super(data)
28
+
29
+ owner.drop owner.map.tile_at_grid(*data[:target_position])
30
+ end
31
+
32
+ def undo(data)
33
+ owner.pick_up target(data)
34
+
35
+ super(data)
36
+ end
37
+ end
38
+ end
@@ -21,23 +21,21 @@ module SmashAndGrab::Abilities
21
21
  )
22
22
  end
23
23
 
24
- def target(data); owner.map.object_by_id(data[:target_id]); end
25
-
26
24
  def random_damage
27
- # 1..skill as damage in a bell-ish curve.
28
- (skill - 1).times.find_all { rand(2) == 1 }.size + 1
25
+ # 0..skill as damage in a bell-ish curve.
26
+ skill.times.find_all { rand(6) + 1 <= 3 }.size # 3 potential hits on each d6.
29
27
  end
30
28
 
31
29
  def do(data)
32
30
  super(data)
33
31
 
34
- owner.melee(target(data), data[:damage])
32
+ owner.make_melee_attack(target(data), data[:damage])
35
33
  end
36
34
 
37
35
  def undo(data)
38
36
  target = target(data)
39
37
  target.tile = owner.map.tile_at_grid(data[:target_position]) unless target.tile
40
- owner.melee(target(data), -data[:damage])
38
+ owner.make_melee_attack(target(data), -data[:damage])
41
39
 
42
40
  super(data)
43
41
  end
@@ -0,0 +1,33 @@
1
+ require_relative "ability"
2
+
3
+ # Pick up an object from the ground
4
+ module SmashAndGrab::Abilities
5
+ class PickUp < TouchAbility
6
+ def can_be_undone?; true; end
7
+
8
+ def tip
9
+ raise "n/a"
10
+ end
11
+
12
+ def initialize(owner, data)
13
+ super(owner, data.merge(action_cost: 1, skill: NON_SKILL))
14
+ end
15
+
16
+ def action_data(object)
17
+ super(object.tile).merge({
18
+ })
19
+ end
20
+
21
+ def do(data)
22
+ super(data)
23
+
24
+ owner.pick_up target(data)
25
+ end
26
+
27
+ def undo(data)
28
+ owner.drop owner.map.tile_at_grid(*data[:target_position])
29
+
30
+ super(data)
31
+ end
32
+ end
33
+ end
@@ -4,49 +4,55 @@ module SmashAndGrab::Abilities
4
4
  class Ranged < TargetedAbility
5
5
  attr_reader :min_range, :max_range
6
6
 
7
- def can_undo?; false; end
7
+ def can_be_undone?; false; end
8
8
 
9
9
  # TODO: Take into account min/max range and LOS.
10
10
  def target_valid?(tile); !!(tile.object.is_a?(Objects::Entity) and tile.object.enemy?(owner)); end
11
11
 
12
12
  def initialize(owner, data)
13
- super(owner, data.merge(action_cost: 1))
13
+ data = {
14
+ action_cost: 1,
15
+ min_range: 2 # Can't fire at adjacent (melee) squares.
16
+ }.merge data
17
+
18
+ @min_range = data[:min_range]
14
19
  @max_range = data[:max_range] || raise(ArgumentError, "no :max_range specified")
15
- @min_range = data[:min_range] || raise(ArgumentError, "no :min_range specified")
20
+
21
+ super(owner, data)
16
22
  end
17
23
 
18
24
  def tip
19
25
  "#{super} attack in ranged combat, at range #{min_range}..#{max_range}"
20
26
  end
21
27
 
22
- def to_json(*args)
28
+ def to_hash
23
29
  super.merge(
24
30
  min_range: @min_range,
25
31
  max_range: @max_range,
26
- ).to_json(*args)
32
+ )
27
33
  end
28
34
 
29
- def action_data(target_tile)
30
- super(target_tile).merge!(
35
+ def action_data(target)
36
+ super(target.tile).merge!(
31
37
  damage: random_damage
32
38
  )
33
39
  end
34
40
 
35
41
  def random_damage
36
- # 1..skill as damage in a bell-ish curve.
37
- (skill - 1).times.find_all { rand(2) == 1 }.size + 1
42
+ # 0..skill as damage in a bell-ish curve.
43
+ skill.times.find_all { rand(6) + 1 <= 2 }.size # 2 potential hits on each d6.
38
44
  end
39
45
 
40
46
  def do(data)
41
47
  super(data)
42
48
 
43
- owner.map.object_by_id(data[:target_id]).health -= data[:damage]
49
+ owner.make_ranged_attack(target(data), data[:damage])
44
50
  end
45
51
 
46
52
  def undo(data)
47
- target = owner.map.object_by_id(data[:target_id])
48
- target.health += data[:damage]
53
+ target = target(data)
49
54
  target.tile = owner.map.tile_at_grid(data[:target_position]) unless target.tile
55
+ owner.make_ranged_attack(target(data), -data[:damage])
50
56
 
51
57
  super(data)
52
58
  end
@@ -1,9 +1,13 @@
1
1
  require_relative "ability"
2
2
 
3
3
  module SmashAndGrab::Abilities
4
- class Sprint < SelfAbility
4
+ class Sprint < ToggleAbility
5
5
  def initialize(owner, data)
6
6
  super(owner, data.merge(action_cost: :all))
7
+
8
+ owner.subscribe :ended_turn do
9
+ @active = false
10
+ end
7
11
  end
8
12
 
9
13
  def movement_bonus
@@ -31,16 +35,16 @@ module SmashAndGrab::Abilities
31
35
  )
32
36
  end
33
37
 
34
- def do(data)
35
- super(data)
36
-
38
+ protected
39
+ def activate(data)
40
+ super
37
41
  owner.movement_points += data[:movement_bonus]
38
42
  end
39
43
 
40
- def undo(data)
44
+ protected
45
+ def deactivate(data)
46
+ super
41
47
  owner.movement_points -= data[:movement_bonus]
42
-
43
- super(data)
44
48
  end
45
49
  end
46
50
  end
@@ -0,0 +1,26 @@
1
+ module Chingu
2
+ class BasicGameObject
3
+ def initialize(options = {})
4
+ @options = options
5
+ @parent = options[:parent]
6
+
7
+ # SMASH_AND_GRAB PATCH: Do not want to store this at all.
8
+ #self.class.instances ||= Array.new
9
+ #self.class.instances << self
10
+
11
+ #
12
+ # A GameObject either belong to a GameState or our mainwindow ($window)
13
+ #
14
+ @parent = $window.current_scope if !@parent && $window
15
+
16
+ # if true, BasicGameObject#update will be called
17
+ @paused = options[:paused] || options[:pause] || false
18
+
19
+ # This will call #setup_trait on the latest trait mixed in
20
+ # which then will pass it on to the next setup_trait() with a super-call.
21
+ setup_trait(options)
22
+
23
+ setup
24
+ end
25
+ end
26
+ end
@@ -3,7 +3,11 @@ class GameWindow < Chingu::Window
3
3
  attr_reader :pixel
4
4
 
5
5
  def initialize
6
- super(800, 600, false)
6
+ if FULLSCREEN
7
+ super(screen_width, screen_height, true)
8
+ else
9
+ super(800, 600, false)
10
+ end
7
11
  end
8
12
 
9
13
  def setup
@@ -1,6 +1,8 @@
1
1
  module SmashAndGrab
2
2
  module Gui
3
3
  class EntityPanel < Fidgit::Horizontal
4
+ TITLE_COLOR = Color.rgb(150, 150, 150)
5
+
4
6
  event :info_toggled
5
7
 
6
8
  def initialize(entity, info_shown, options = {})
@@ -25,8 +27,9 @@ module SmashAndGrab
25
27
  end
26
28
  end
27
29
 
28
- vertical padding: 0, spacing: 4 do
29
- @name = label @entity.name
30
+ vertical padding: 0, spacing: 1.5 do
31
+ @name = label @entity.name, font_height: 20, color: Color::WHITE
32
+ label TITLE_COLOR.colorize("#{entity.title} - #{@entity.faction.colorized_name}"), font_height: 12
30
33
  @sub_panel_container = vertical spacing: 0, padding: 0
31
34
  end
32
35
 
@@ -36,7 +39,11 @@ module SmashAndGrab
36
39
 
37
40
  update_details @entity
38
41
 
39
- @entity.subscribe :changed, method(:update_details)
42
+ @changed_event = @entity.subscribe :changed, method(:update_details)
43
+ end
44
+
45
+ def finalize
46
+ @changed_event.unsubscribe
40
47
  end
41
48
 
42
49
  def switch_sub_panel
@@ -47,9 +54,9 @@ module SmashAndGrab
47
54
  def create_info_sub_panel
48
55
  @info_sub_panel = Fidgit::Vertical.new padding: 0, spacing: 0 do
49
56
  text = nil
50
- scroll = scroll_window width: 350, height: 72 do
57
+ scroll = scroll_window width: 350, height: 68 do
51
58
  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
59
+ width: 330, font_height: 14, editable: false
53
60
  end
54
61
  scroll.background_color = text.background_color
55
62
  end
@@ -70,20 +77,22 @@ module SmashAndGrab
70
77
 
71
78
  label_options = button_options.merge border_thickness: 2, border_color: Color.rgba(255, 255, 255, 100)
72
79
 
73
- [:melee, :ranged, :sprint].each do |ability_name|
80
+ [:melee, :ranged, :sprint, :drop].each do |ability_name|
74
81
  if @entity.has_ability? ability_name
75
82
  ability = @entity.ability ability_name
76
83
  @ability_buttons[ability_name] = button("#{ability_name.to_s[0].upcase}#{ability.skill}",
77
84
  button_options.merge(tip: ability.tip)) do
78
85
 
79
- @entity.use_ability :sprint if ability_name == :sprint
86
+ if ability_name == :sprint or ability_name == :drop
87
+ @entity.use_ability ability_name
88
+ end
80
89
  end
81
90
  else
82
91
  label "", label_options
83
92
  end
84
93
  end
85
94
 
86
- 5.times do |i|
95
+ 4.times do |i|
87
96
  label "", label_options
88
97
  end
89
98
  end
@@ -91,7 +100,9 @@ module SmashAndGrab
91
100
  end
92
101
 
93
102
  def update_details(entity)
94
- @health.text = "HP: #{entity.health} / #{entity.max_health}"
103
+ return unless entity.faction.player
104
+
105
+ @health.text = "HP: #{entity.hp} / #{entity.max_hp}"
95
106
  @movement_points.text = "MP: #{entity.mp} / #{entity.max_mp}"
96
107
  @action_points.text = "AP: #{entity.ap} / #{entity.max_ap}"
97
108
 
@@ -100,7 +111,12 @@ module SmashAndGrab
100
111
  end
101
112
 
102
113
  @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))
114
+ button.enabled = entity.active? && entity.faction.player.human? && entity.use_ability?(ability)
115
+
116
+ # TODO: this should be more sensible (allows un-sprinting).
117
+ if ability == :sprint && entity.active? && entity.faction.player.human? && entity.ability(ability).deactivate?
118
+ button.enabled = true
119
+ end
104
120
  end
105
121
 
106
122
  @portrait.image = entity.image