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.
- data/{CHANGELOG.txt → CHANGELOG.md} +0 -0
- data/Gemfile.lock +8 -4
- data/LICENSE.txt +20 -0
- data/README.md +30 -14
- data/Rakefile +2 -2
- data/config/lang/objects/entities/en.yml +134 -0
- data/config/lang/objects/static/en.yml +8 -0
- data/config/lang/objects/vehicles/en.yml +11 -0
- data/config/map/entities.yml +42 -38
- data/lib/smash_and_grab.rb +5 -0
- data/lib/smash_and_grab/abilities.rb +1 -1
- data/lib/smash_and_grab/abilities/ability.rb +45 -3
- data/lib/smash_and_grab/abilities/drop.rb +38 -0
- data/lib/smash_and_grab/abilities/melee.rb +4 -6
- data/lib/smash_and_grab/abilities/pick_up.rb +33 -0
- data/lib/smash_and_grab/abilities/ranged.rb +18 -12
- data/lib/smash_and_grab/abilities/sprint.rb +11 -7
- data/lib/smash_and_grab/chingu_ext/basic_game_object.rb +26 -0
- data/lib/smash_and_grab/game_window.rb +5 -1
- data/lib/smash_and_grab/gui/entity_panel.rb +26 -10
- data/lib/smash_and_grab/gui/entity_summary.rb +19 -11
- data/lib/smash_and_grab/gui/game_log.rb +6 -2
- data/lib/smash_and_grab/gui/info_panel.rb +7 -4
- data/lib/smash_and_grab/gui/object_panel.rb +6 -2
- data/lib/smash_and_grab/gui/scenario_panel.rb +4 -0
- data/lib/smash_and_grab/history/action_history.rb +1 -1
- data/lib/smash_and_grab/main.rb +7 -3
- data/lib/smash_and_grab/map/faction.rb +26 -8
- data/lib/smash_and_grab/map/map.rb +21 -12
- data/lib/smash_and_grab/map/tile.rb +29 -2
- data/lib/smash_and_grab/map/wall.rb +2 -2
- data/lib/smash_and_grab/mixins/has_contents.rb +38 -0
- data/lib/smash_and_grab/mixins/line_of_sight.rb +129 -0
- data/lib/smash_and_grab/mixins/pathfinding.rb +145 -0
- data/lib/smash_and_grab/mouse_selection.rb +107 -36
- data/lib/smash_and_grab/objects/entity.rb +311 -260
- data/lib/smash_and_grab/objects/floating_text.rb +0 -1
- data/lib/smash_and_grab/objects/static.rb +10 -3
- data/lib/smash_and_grab/objects/vehicle.rb +7 -2
- data/lib/smash_and_grab/objects/world_object.rb +10 -3
- data/lib/smash_and_grab/path.rb +38 -12
- data/lib/smash_and_grab/players/ai.rb +91 -0
- data/lib/smash_and_grab/players/human.rb +9 -0
- data/lib/smash_and_grab/players/player.rb +16 -65
- data/lib/smash_and_grab/players/remote.rb +9 -0
- data/lib/smash_and_grab/sprite_sheet.rb +9 -0
- data/lib/smash_and_grab/states/edit_level.rb +15 -4
- data/lib/smash_and_grab/states/main_menu.rb +16 -4
- data/lib/smash_and_grab/states/play_level.rb +88 -28
- data/lib/smash_and_grab/states/world.rb +17 -9
- data/lib/smash_and_grab/version.rb +1 -1
- data/lib/smash_and_grab/z_order.rb +6 -5
- data/media/images/path.png +0 -0
- data/media/images/tile_selection.png +0 -0
- data/media/images/tiles_selection.png +0 -0
- data/smash_and_grab.gemspec +2 -2
- data/tasks/create_portraits.rb +39 -0
- data/tasks/outline_images.rb +56 -0
- data/test/smash_and_grab/abilities/drop_test.rb +68 -0
- data/test/smash_and_grab/abilities/melee_test.rb +4 -4
- data/test/smash_and_grab/abilities/pick_up_test.rb +68 -0
- data/test/smash_and_grab/abilities/ranged_test.rb +105 -0
- data/test/smash_and_grab/abilities/sprint_test.rb +55 -25
- data/test/smash_and_grab/map/faction_test.rb +18 -16
- data/test/smash_and_grab/map/map_test.rb +9 -4
- metadata +51 -19
- data/lib/smash_and_grab/fidgit_ext/event.rb +0 -77
data/lib/smash_and_grab.rb
CHANGED
@@ -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
|
|
@@ -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
|
-
|
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
|
-
}
|
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
|
-
#
|
28
|
-
|
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.
|
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.
|
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
|
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
|
-
|
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
|
-
|
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
|
28
|
+
def to_hash
|
23
29
|
super.merge(
|
24
30
|
min_range: @min_range,
|
25
31
|
max_range: @max_range,
|
26
|
-
)
|
32
|
+
)
|
27
33
|
end
|
28
34
|
|
29
|
-
def action_data(
|
30
|
-
super(
|
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
|
-
#
|
37
|
-
|
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.
|
49
|
+
owner.make_ranged_attack(target(data), data[:damage])
|
44
50
|
end
|
45
51
|
|
46
52
|
def undo(data)
|
47
|
-
target =
|
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 <
|
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
|
-
|
35
|
-
|
36
|
-
|
38
|
+
protected
|
39
|
+
def activate(data)
|
40
|
+
super
|
37
41
|
owner.movement_points += data[:movement_bonus]
|
38
42
|
end
|
39
43
|
|
40
|
-
|
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
|
@@ -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:
|
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:
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|