essytas 0.0.1

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 (174) hide show
  1. data/LICENSE +19 -0
  2. data/README +3 -0
  3. data/Rakefile +44 -0
  4. data/bin/essytas +2 -0
  5. data/lib/animation.rb +74 -0
  6. data/lib/config.rb +63 -0
  7. data/lib/core.rb +242 -0
  8. data/lib/cursor.rb +43 -0
  9. data/lib/def/animations/explosion1.anim +103 -0
  10. data/lib/def/characters.def +132 -0
  11. data/lib/def/de/dialogues/test.dlg +4 -0
  12. data/lib/def/de/enemies.trans +6 -0
  13. data/lib/def/de/items.trans +84 -0
  14. data/lib/def/de/skills.trans +48 -0
  15. data/lib/def/de/spells.trans +31 -0
  16. data/lib/def/en/dialogues/test.dlg +4 -0
  17. data/lib/def/en/enemies.trans +6 -0
  18. data/lib/def/en/items.trans +78 -0
  19. data/lib/def/en/skills.trans +48 -0
  20. data/lib/def/en/spells.trans +31 -0
  21. data/lib/def/enemies.def +8 -0
  22. data/lib/def/enemies/wolf.bhv +11 -0
  23. data/lib/def/init.def +5 -0
  24. data/lib/def/items.def +153 -0
  25. data/lib/def/loot.def +2 -0
  26. data/lib/def/particles.def +66 -0
  27. data/lib/def/recipies.def +18 -0
  28. data/lib/def/skills.def +40 -0
  29. data/lib/def/spells.def +42 -0
  30. data/lib/def/spells.rb +16 -0
  31. data/lib/def/weapons.def +9 -0
  32. data/lib/ext/astar/AMap.rb +146 -0
  33. data/lib/ext/astar/node.rb +72 -0
  34. data/lib/ext/astar/priority_queue.rb +44 -0
  35. data/lib/ext/shader.rb +116 -0
  36. data/lib/game/alchemy/recipe.rb +26 -0
  37. data/lib/game/character.rb +102 -0
  38. data/lib/game/combat/bar.rb +119 -0
  39. data/lib/game/combat/battle.rb +345 -0
  40. data/lib/game/combat/control.rb +18 -0
  41. data/lib/game/combat/gui.rb +190 -0
  42. data/lib/game/combat/gui/select_item.rb +11 -0
  43. data/lib/game/combat/gui/select_spell.rb +38 -0
  44. data/lib/game/constitution.rb +48 -0
  45. data/lib/game/equipment.rb +34 -0
  46. data/lib/game/inventory.rb +37 -0
  47. data/lib/game/item.rb +54 -0
  48. data/lib/game/magic.rb +33 -0
  49. data/lib/game/map/events.rb +29 -0
  50. data/lib/game/map/fog.rb +41 -0
  51. data/lib/game/map/map.rb +247 -0
  52. data/lib/game/map/map_animation.rb +26 -0
  53. data/lib/game/map/map_loader.rb +177 -0
  54. data/lib/game/map/map_object.rb +208 -0
  55. data/lib/game/map/map_particle.rb +27 -0
  56. data/lib/game/map/player.rb +78 -0
  57. data/lib/game/map/tile.rb +35 -0
  58. data/lib/game/mind.rb +24 -0
  59. data/lib/game/npc/behaviour.rb +45 -0
  60. data/lib/game/npc/bubble.rb +28 -0
  61. data/lib/game/npc/goal.rb +93 -0
  62. data/lib/game/npc/npc.rb +95 -0
  63. data/lib/game/npc/task.rb +73 -0
  64. data/lib/game/osd/magic.rb +24 -0
  65. data/lib/game/party.rb +42 -0
  66. data/lib/game/skills.rb +64 -0
  67. data/lib/game/spell.rb +35 -0
  68. data/lib/game_window.rb +95 -0
  69. data/lib/glsl/contrast.frag +12 -0
  70. data/lib/glsl/fade.frag +11 -0
  71. data/lib/glsl/mezzotint.frag +20 -0
  72. data/lib/glsl/noise.frag +20 -0
  73. data/lib/glsl/pixelate.frag +42 -0
  74. data/lib/glsl/radialblur.frag +28 -0
  75. data/lib/glsl/sepia.frag +15 -0
  76. data/lib/glsl/shockwave.frag +24 -0
  77. data/lib/glsl/tv_screen.frag +17 -0
  78. data/lib/graphics/animations/credits.txt +1 -0
  79. data/lib/graphics/animations/explosion2.png +0 -0
  80. data/lib/graphics/backgrounds/white_ties_grass.png +0 -0
  81. data/lib/graphics/chars/ejera.png +0 -0
  82. data/lib/graphics/chars/salyjea.png +0 -0
  83. data/lib/graphics/chars/tharat.png +0 -0
  84. data/lib/graphics/combat/tharat.png +0 -0
  85. data/lib/graphics/cursors/normal.png +0 -0
  86. data/lib/graphics/fog/clouds1.png +0 -0
  87. data/lib/graphics/gui/bar_center.png +0 -0
  88. data/lib/graphics/gui/bar_left.png +0 -0
  89. data/lib/graphics/gui/bar_right.png +0 -0
  90. data/lib/graphics/gui/button_background.png +0 -0
  91. data/lib/graphics/gui/button_close.png +0 -0
  92. data/lib/graphics/gui/button_close_hi.png +0 -0
  93. data/lib/graphics/gui/button_highlight.png +0 -0
  94. data/lib/graphics/gui/charequip_background.png +0 -0
  95. data/lib/graphics/gui/charselect_background.png +0 -0
  96. data/lib/graphics/gui/container_background.png +0 -0
  97. data/lib/graphics/gui/drop_item.png +0 -0
  98. data/lib/graphics/gui/equip_Etarae.png +0 -0
  99. data/lib/graphics/gui/equip_Mensch.png +0 -0
  100. data/lib/graphics/gui/equipslot_background.png +0 -0
  101. data/lib/graphics/gui/iteminfo_background.png +0 -0
  102. data/lib/graphics/gui/msg_background.png +0 -0
  103. data/lib/graphics/gui/scrollbar_background.png +0 -0
  104. data/lib/graphics/gui/scroller.png +0 -0
  105. data/lib/graphics/gui/tile_32.png +0 -0
  106. data/lib/graphics/gui/tile_32_highlight.png +0 -0
  107. data/lib/graphics/icons/items/arrow.png +0 -0
  108. data/lib/graphics/icons/items/bandage.png +0 -0
  109. data/lib/graphics/icons/items/boots_leather.png +0 -0
  110. data/lib/graphics/icons/items/bow.png +0 -0
  111. data/lib/graphics/icons/items/credits.txt +8 -0
  112. data/lib/graphics/icons/items/harness_leather.png +0 -0
  113. data/lib/graphics/icons/items/knife.png +0 -0
  114. data/lib/graphics/icons/items/shirt_linen.png +0 -0
  115. data/lib/graphics/items/none.png +0 -0
  116. data/lib/graphics/menu/ingame_background.png +0 -0
  117. data/lib/graphics/menu/ingame_background.xcf +0 -0
  118. data/lib/graphics/menu/start_background.png +0 -0
  119. data/lib/graphics/missing.png +0 -0
  120. data/lib/graphics/osd/magic_bg.png +0 -0
  121. data/lib/graphics/particles/leaf.png +0 -0
  122. data/lib/graphics/particles/smoke.png +0 -0
  123. data/lib/graphics/pixel.png +0 -0
  124. data/lib/graphics/tiles/test.png +0 -0
  125. data/lib/gui/base.rb +278 -0
  126. data/lib/gui/button.rb +93 -0
  127. data/lib/gui/char_equip.rb +118 -0
  128. data/lib/gui/char_selector.rb +54 -0
  129. data/lib/gui/context_menu.rb +115 -0
  130. data/lib/gui/draggable.rb +18 -0
  131. data/lib/gui/grid.rb +118 -0
  132. data/lib/gui/image.rb +17 -0
  133. data/lib/gui/inventory.rb +42 -0
  134. data/lib/gui/item_info.rb +33 -0
  135. data/lib/gui/slider.rb +10 -0
  136. data/lib/gui/textfield.rb +57 -0
  137. data/lib/layer.rb +64 -0
  138. data/lib/load.rb +31 -0
  139. data/lib/main.rb +18 -0
  140. data/lib/maps/def/test.bhv +26 -0
  141. data/lib/maps/def/test.rb +14 -0
  142. data/lib/maps/test.tmx +133 -0
  143. data/lib/maps/test2.tmx +94 -0
  144. data/lib/maps/test3.tmx +29 -0
  145. data/lib/maps/test3_lower.tmx +25 -0
  146. data/lib/maps/test3_upper.tmx +25 -0
  147. data/lib/maps/test_left.tmx +25 -0
  148. data/lib/maps/test_upper.tmx +25 -0
  149. data/lib/music/Butterfly Tea - A New Hope 2K11.mp3 +0 -0
  150. data/lib/music/Greendjohn - Rebirth.mp3 +0 -0
  151. data/lib/music/credits.txt +8 -0
  152. data/lib/parse.rb +605 -0
  153. data/lib/parse_tmx.rb +114 -0
  154. data/lib/particles.rb +127 -0
  155. data/lib/sample.rb +29 -0
  156. data/lib/save.rb +44 -0
  157. data/lib/song.rb +41 -0
  158. data/lib/sounds/click1.wav +0 -0
  159. data/lib/sounds/credits.txt +2 -0
  160. data/lib/sounds/error1.wav +0 -0
  161. data/lib/sprite.rb +35 -0
  162. data/lib/states/menus.rb +17 -0
  163. data/lib/states/menus/alchemy.rb +53 -0
  164. data/lib/states/menus/equip.rb +79 -0
  165. data/lib/states/menus/ingame.rb +34 -0
  166. data/lib/states/menus/magic.rb +31 -0
  167. data/lib/states/menus/options.rb +54 -0
  168. data/lib/states/menus/start.rb +38 -0
  169. data/lib/states/states.rb +154 -0
  170. data/lib/tileset.rb +42 -0
  171. data/lib/tools/world.rb +103 -0
  172. data/lib/tools/worldmap_editor.rb +221 -0
  173. data/lib/translate.rb +368 -0
  174. metadata +255 -0
@@ -0,0 +1,93 @@
1
+
2
+ # FIXME policy = :recalc doesnt work
3
+
4
+ module Core::Game
5
+
6
+ class Goal
7
+ def initialize
8
+ @state = :before
9
+ end
10
+ def setup
11
+ @state = :progress
12
+ end
13
+ def state
14
+ return @state
15
+ end
16
+ def restart
17
+ @state = :before
18
+ end
19
+ def recalc
20
+ restart
21
+ end
22
+ end
23
+
24
+ class MotionGoal < Goal
25
+ attr_reader :dx, :dy, :x, :y
26
+ attr_accessor :state
27
+ def initialize(dx, dy, x, y)
28
+ super()
29
+ @dx, @dy = dx, dy
30
+ @x, @y = x, y
31
+ end
32
+ def recalc
33
+ @state = :recalc
34
+ end
35
+ end
36
+
37
+ class CompositeGoal < Array
38
+ attr_reader :state
39
+ def initialize(policy=:abort)
40
+ super()
41
+ @current = 0
42
+ @policy = policy
43
+ @state = :progress
44
+ end
45
+ def reset
46
+ self.clear
47
+ @current = 0
48
+ @state = :reset
49
+ end
50
+ def start
51
+ @state = :progress
52
+ end
53
+ def current
54
+ return self[@current]
55
+ end
56
+ def update
57
+ if @state == :progress
58
+ awesome_print(self) if !current
59
+ case current.state
60
+ when :before
61
+ if current.class != MotionGoal
62
+ current.setup
63
+ end
64
+ when :progress
65
+ return
66
+ when :failed
67
+ handle_failed
68
+ when :finished
69
+ advance
70
+ end
71
+ end
72
+ end
73
+ def advance
74
+ @current += 1
75
+ if @current == self.size
76
+ @state = :finished
77
+ @current = 0
78
+ end
79
+ end
80
+ private
81
+ def handle_failed
82
+ case @policy
83
+ when :abort
84
+ @state = :failed
85
+ when :retry
86
+ current.restart
87
+ when :recalc
88
+ current.recalc
89
+ end
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,95 @@
1
+
2
+ require_relative "goal.rb"
3
+ require_relative "bubble.rb"
4
+
5
+ # TODO move goal logic to mapobject
6
+
7
+ module Core::Game
8
+ # Interactive map object
9
+ class MapNPC < MapObject
10
+ attr_accessor :behaviour
11
+ attr_reader :goal, :gamename, :children
12
+ def initialize(x, y, props)
13
+ super(x, y, props)
14
+ @x, @y = x, y
15
+ @behaviour = nil
16
+ @goal = CompositeGoal.new(:retry)
17
+ @speed = 2
18
+ @name = "npc-#{props[:file].downcase}-#{rand(1000)}"
19
+ @gamename = props[:name] ? props[:name] : @name
20
+ @children = []
21
+ @bubble = nil
22
+ end
23
+
24
+ def setup
25
+ super
26
+ @behaviour.on_init if @behaviour
27
+ end
28
+
29
+ # Generates a path from the current position to the given screen coordinates by using A*
30
+ def find_path_to(x, y)
31
+ #puts("from #{@x/32}|#{@y/32} to #{x}|#{y}")
32
+ curr = Core.window.state.map.current.astar(AStar::Node.new(@x/32, @y/32), AStar::Node.new(x, y))
33
+ @goal.reset
34
+ while curr and curr.parent
35
+ @goal.push(MotionGoal.new((curr.x - curr.parent.x) * 32, (curr.y - curr.parent.y) * 32, x, y))
36
+ curr = curr.parent
37
+ end
38
+ @goal.reverse!
39
+ @goal.start
40
+ return @goal
41
+ end
42
+
43
+ def update
44
+ @children.each { |child|
45
+ if child.follow
46
+ child.x = @x + child.xoff
47
+ child.y = @y + child.yoff
48
+ end
49
+ child.update
50
+ }
51
+ @behaviour.on_update if @behaviour
52
+ moved = update_move(Core.window.state.map.current)
53
+ @goal.update if @goal.size > 0
54
+ if @goal.current.class == MotionGoal
55
+ if @goal.current.state == :recalc
56
+ @dx = @dy = 0
57
+ find_path_to(@goal.last.x, @goal.last.y)
58
+ @goal.update
59
+ end
60
+ if @dx == 0 and @dy == 0
61
+ if @goal.current.state == :before
62
+ @dx, @dy = @goal.current.dx, @goal.current.dy
63
+ @goal.current.state = :progress
64
+ elsif @goal.current.state == :progress
65
+ if moved
66
+ @goal.advance
67
+ else
68
+ @goal.current.state = :failed
69
+ end
70
+ end
71
+ end
72
+ end
73
+ if @bubble
74
+ @bubble.update
75
+ @bubble = nil if @bubble.remove?
76
+ end
77
+ end
78
+
79
+ def draw(x, y)
80
+ super
81
+ @bubble.draw(@x+x, @y+y) if @bubble
82
+ @children.each { |child| child.draw(x, y) }
83
+ end
84
+
85
+ # Displays a speech bubble with the given text
86
+ def talk(text)
87
+ @bubble = Bubble.new(text)
88
+ end
89
+
90
+ def destroy_goal
91
+ @goal.reset
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,73 @@
1
+
2
+ module Core::Game::NPC
3
+
4
+ class Task
5
+ # action: proc to execute
6
+ # wait: stall later tasks until this one finished
7
+ # remove_after: delete task when finished
8
+ def initialize(parameters)
9
+ @array = parameters
10
+ @finished = false
11
+ @current = 0
12
+ end
13
+
14
+ def execute(npc, other=nil)
15
+ if !@array.empty? and !@finished
16
+ send(@array[@current].first, @array[@current], npc, other)
17
+ end
18
+ end
19
+
20
+ def empty?
21
+ return @array.empty?
22
+ end
23
+
24
+ def finished?
25
+ return @finished
26
+ end
27
+
28
+ def reset
29
+ @finished = false
30
+ end
31
+
32
+ def next_subtask(npc, other)
33
+ if @array[@current].include?(:remove)
34
+ @array.delete_at(@current)
35
+ else
36
+ @current += 1
37
+ end
38
+ if @current >= @array.size
39
+ @current = 0
40
+ return
41
+ end
42
+ if !@array[@current-1].include?(:wait)
43
+ execute(npc, other)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def particles(parms, npc, other)
50
+ npc.children.push(Core::Game::MapParticle.new(parms[1].x, parms[1].y, parms[2]))
51
+ npc.children.last.xoff = npc.children.last.yoff = 16
52
+ if parms.include?(:follow)
53
+ npc.children.last.follow = true
54
+ end
55
+ next_subtask(npc, other)
56
+ end
57
+
58
+ def path_to(parms, npc, other)
59
+ if npc.goal.state != :progress
60
+ next_subtask(npc, other)
61
+ npc.find_path_to(parms[1].x, parms[1].y)
62
+ npc.goal.start
63
+ end
64
+ end
65
+
66
+ def msg(parms, npc, other)
67
+ Core.window.state.map.message(parms[1])
68
+ next_subtask(npc, other)
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Core::Game
3
+ module OSD
4
+
5
+ class Magic < Core::GUI::Window
6
+ def initialize(x, y, caster=Core::Game.party.player, target=nil)
7
+ if x + 640 > 1024
8
+ x = 386
9
+ elsif x < 0
10
+ x = 0
11
+ end
12
+ if y + 480 > 768
13
+ y = 264
14
+ elsif y < 0
15
+ y = 0
16
+ end
17
+ super(x, y, 640, 480, Core::Trans.menu(:magic), true, "osd/magic_bg", true)
18
+ @caster, @target = caster, target
19
+ add(:char, Core::GUI::CharSelector.new(16, 16, @state.party))
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+
2
+ require_relative "character.rb"
3
+
4
+ module Core::Game
5
+
6
+ # TODO < Array
7
+ class Party
8
+ attr_accessor :location, :player_index
9
+ def initialize
10
+ @members = []
11
+ @members += Core::Game.characters
12
+ @player_index = 0
13
+ @location = ""
14
+ (Core::Game.items.size * 5).times { @members[0].inventory.add(Core::Game.items.sample) }
15
+ @members[0].equipment.equip(Core::Game.items[1], :rarm, @members[0].inventory)
16
+ end
17
+ def add(char)
18
+ @members.push(char)
19
+ end
20
+ def remove(char)
21
+ @members.delete(char)
22
+ end
23
+ def members
24
+ return @members
25
+ end
26
+ def update
27
+ @members.each { |m|
28
+ m.update
29
+ }
30
+ end
31
+ def player
32
+ return @members[@player_index]
33
+ end
34
+ end
35
+
36
+ # Character states
37
+ NORMAL = 0
38
+ ASLEEP = 1
39
+ DEAD = 2
40
+ UNCONSCIOUS = 3
41
+
42
+ end
@@ -0,0 +1,64 @@
1
+
2
+ module Core::Game
3
+ @skills = []
4
+
5
+ def self.skills
6
+ return @skills
7
+ end
8
+
9
+ def self.skills=(ary)
10
+ @skills = ary
11
+ end
12
+
13
+ def self.find_skill(name)
14
+ @skills.each { |skill|
15
+ if skill.name == name
16
+ return skill
17
+ end
18
+ }
19
+ end
20
+
21
+ class Skill
22
+ attr_reader :name, :desc, :img
23
+ end
24
+
25
+ # skills and their respective levels, instanced once for every character
26
+ class Skills
27
+ def initialize
28
+ @skills = {}
29
+ Core::Game.skills.each { |skill|
30
+ @skills.store(skill, 0)
31
+ }
32
+ end
33
+
34
+ def list
35
+ return @skills
36
+ end
37
+
38
+ def advance(skill)
39
+ @skills.each { |s|
40
+ if skill.class == s.class
41
+ @skills[s] += 1
42
+ end
43
+ }
44
+ end
45
+
46
+ def level_to_s(skill)
47
+ lvl = @skills[skill]
48
+ case lvl
49
+ when 0
50
+ return Core::Trans.menu(:xp_none)
51
+ when 1
52
+ return Core::Trans.menu(:xp_vlittle)
53
+ when 2
54
+ return Core::Trans.menu(:xp_little)
55
+ when 3
56
+ return Core::Trans.menu(:xp_mediocre)
57
+ when 4
58
+ return Core::Trans.menu(:xp_experienced)
59
+ when 5
60
+ return Core::Trans.menu(:xp_vexperienced)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,35 @@
1
+
2
+ require_relative "../def/spells.rb"
3
+
4
+ module Core::Game
5
+
6
+ def self.spells
7
+ return @spells
8
+ end
9
+
10
+ def self.spells=(ary)
11
+ @spells = ary
12
+ end
13
+
14
+ def self.find_spell(name)
15
+ @spells.each { |spell|
16
+ if spell.name == name
17
+ return spell
18
+ end
19
+ }
20
+ return nil
21
+ end
22
+
23
+ class Spell
24
+ attr_reader :name, :icon, :type, :cost
25
+ def cast(caster, target=nil)
26
+ puts("#{caster} casts #{self.name} on #{target}")
27
+ if Core::Game::Spells.respond_to?(self.name)
28
+ Core::Game::Spells.send(self.name)
29
+ else
30
+ puts("ERROR: No ruby definition for spell #{self.name} found")
31
+ end
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,95 @@
1
+
2
+ #--
3
+ # Main state logic - obviously
4
+
5
+ if Core.config[:opengl]
6
+ require_relative "ext/shader.rb"
7
+ end
8
+
9
+ require_relative "particles.rb"
10
+ require_relative "animation.rb"
11
+
12
+ module Core
13
+
14
+ class GameWindow < Gosu::Window
15
+
16
+ attr_accessor :state, :saved
17
+
18
+ include States
19
+
20
+ def initialize
21
+ super(1024, 768, false)
22
+ self.caption = "Essytas - v#{Core::VERSION}"
23
+ Core.window = self
24
+ Core.particles = Core::Parse.particles
25
+ Core.animations = Core::Parse.animations
26
+ @state = StartMenu.new(self)
27
+ @unpress = []
28
+ @font = Core.font(Core::DEFAULT_FONT, 20)
29
+ @bg = Core.sprite("pixel", true)
30
+ if (Core.config[:contrast] != 1.0 and Core.config[:opengl])
31
+ @contrast = Shader.new(self, "glsl/contrast")
32
+ @contrast["contrast"] = Core.config[:contrast]
33
+ else
34
+ @contrast = nil
35
+ end
36
+ end
37
+
38
+ def update
39
+ @state.update
40
+ unpress
41
+ self.caption = "Essytas - v#{Core::VERSION} - #{Gosu.fps} FPS"
42
+ end
43
+
44
+ def draw
45
+ @state.draw
46
+ if $DEBUG
47
+ @bg.draw(16, 16, 999999, 160, 48, 0x99000000)
48
+ @font.draw("Mouse: #{mouse_x.to_i}|#{mouse_y.to_i}", 32, 32, 999999)
49
+ end
50
+ if @contrast
51
+ @contrast.apply
52
+ end
53
+ end
54
+
55
+ # Advance to the next state
56
+ #
57
+ # Requires an EH::States::State instance
58
+ def advance(state)
59
+ @state.finish
60
+ @state = state
61
+ end
62
+
63
+ # Save current state so we can go back to it later
64
+ #
65
+ # The GameState is saved when switching to a MenuState
66
+ def save
67
+ @saved = @state
68
+ end
69
+
70
+ def load
71
+ @state = @saved
72
+ end
73
+ # Checks if a specific key is pressed
74
+ #
75
+ # Only returns true again if the key was held loose
76
+ def pressed?(key)
77
+ p = button_down?(key)
78
+ if p
79
+ if @unpress.include?(key)
80
+ p = false
81
+ else
82
+ @unpress.push(key)
83
+ end
84
+ end
85
+ return p
86
+ end
87
+ def unpress
88
+ @unpress.each { |key|
89
+ if !button_down?(key)
90
+ @unpress.delete(key)
91
+ end
92
+ }
93
+ end
94
+ end
95
+ end