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