hexyz is tower defense game, and a lua library for dealing with hexagonal grids
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

335 lines
10 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
4 years ago
5 years ago
  1. math.randomseed(os.time())
  2. math.random()
  3. math.random()
  4. math.random()
  5. -- Globals
  6. WIN = am.window{
  7. width = 1920,
  8. height = 1080,
  9. title = "hexyz",
  10. highdpi = true,
  11. letterbox = true
  12. }
  13. -- assets/non-or-trivial code
  14. require "color"
  15. require "sound"
  16. require "texture"
  17. require "src/entity"
  18. require "src/extra"
  19. require "src/geometry"
  20. require "src/hexyz"
  21. require "src/grid"
  22. WIN.clear_color = color_at(0)
  23. require "src/mob"
  24. require "src/projectile"
  25. require "src/tower"
  26. OFF_SCREEN = vec2(WIN.width * 2) -- arbitrary pixel position that is garunteed to be off screen
  27. PERF_STATS = false -- result of am.perf_stats() -- should be called every frame
  28. WORLD = false -- root scene node of everything considered to be in the game world
  29. -- aka non gui stuff
  30. TIME = 0 -- runtime of the current game in seconds (not whole program runtime)
  31. SCORE = 0 -- score of the player
  32. STARTING_MONEY = 50
  33. MONEY = STARTING_MONEY -- available resources
  34. MOUSE = false -- position of the mouse at the start of every frame, if an action is tracking it
  35. -- global audio settings
  36. MUSIC_VOLUME = 0.1
  37. SFX_VOLUME = 0.1
  38. -- game stuff
  39. SELECTED_TOWER_TYPE = 1
  40. -- top right display types
  41. local TRDTS = {
  42. NOTHING = -1,
  43. CENTERED_EVENQ = 0,
  44. EVENQ = 1,
  45. HEX = 2,
  46. PLATFORM = 3,
  47. PERF = 4,
  48. SEED = 5,
  49. TILE = 6
  50. }
  51. local TRDT = TRDTS.SEED
  52. local function select_hex(hex)
  53. local tower = tower_on_hex(hex)
  54. local tile = HEX_MAP.get(hex.x, hex.y)
  55. end
  56. local function can_do_build(hex, tile, tower_type)
  57. return can_afford_tower(MONEY, tower_type) and tower_is_buildable_on(hex, tile, tower_type)
  58. end
  59. local function game_action(scene)
  60. --if SCORE < 0 then game_end() end
  61. TIME = TIME + am.delta_time
  62. SCORE = SCORE + am.delta_time
  63. MOUSE = WIN:mouse_position()
  64. PERF_STATS = am.perf_stats()
  65. local hex = pixel_to_hex(MOUSE - WORLDSPACE_COORDINATE_OFFSET)
  66. local rounded_mouse = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET
  67. local evenq = hex_to_evenq(hex)
  68. local centered_evenq = evenq{ y = -evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2)
  69. , math.floor(HEX_GRID_HEIGHT/2))
  70. local tile = HEX_MAP.get(hex.x, hex.y)
  71. local hot = evenq_is_interactable(evenq{ y = -evenq.y })
  72. do_entity_updates()
  73. do_mob_spawning()
  74. if WIN:mouse_pressed"left" then
  75. if hot and can_do_build(hex, tile, SELECTED_TOWER_TYPE) then
  76. build_tower(hex, SELECTED_TOWER_TYPE)
  77. end
  78. end
  79. if WIN:mouse_pressed"middle" then
  80. WIN.scene"scale".scale2d = vec2(1)
  81. else
  82. local mwd = vec2(WIN:mouse_wheel_delta().y / 1000)
  83. WIN.scene"scale".scale2d = WIN.scene"scale".scale2d + mwd
  84. WIN.scene"scale".scale2d = WIN.scene"scale".scale2d + mwd
  85. end
  86. if WIN:key_pressed"escape" then
  87. WIN.scene"game".paused = true
  88. WIN.scene:action(function()
  89. if WIN:key_pressed"escape" then
  90. WIN.scene"game".paused = false
  91. return true
  92. end
  93. end)
  94. --game_end()
  95. elseif WIN:key_pressed"f1" then
  96. TRDT = (TRDT + 1) % #table.keys(TRDTS)
  97. elseif WIN:key_pressed"f2" then
  98. WORLD"flow_field".hidden = not WORLD"flow_field".hidden
  99. elseif WIN:key_pressed"tab" then
  100. local num_of_types = #table.keys(TOWER_TYPE)
  101. if WIN:key_down"lshift" then
  102. select_tower_type((SELECTED_TOWER_TYPE + num_of_types - 2) % num_of_types + 1)
  103. else
  104. select_tower_type((SELECTED_TOWER_TYPE) % num_of_types + 1)
  105. end
  106. elseif WIN:key_pressed"1" then select_tower_type(TOWER_TYPE.REDEYE)
  107. elseif WIN:key_pressed"2" then select_tower_type(2)
  108. elseif WIN:key_pressed"3" then select_tower_type(3)
  109. elseif WIN:key_pressed"4" then select_tower_type(4)
  110. elseif WIN:key_pressed"5" then select_tower_type(5)
  111. elseif WIN:key_pressed"6" then select_tower_type(6)
  112. elseif WIN:key_pressed"7" then select_tower_type(7)
  113. elseif WIN:key_pressed"8" then select_tower_type(8)
  114. elseif WIN:key_pressed"9" then select_tower_type(9)
  115. elseif WIN:key_pressed"0" then select_tower_type(10)
  116. elseif WIN:key_pressed"minus" then select_tower_type(11)
  117. elseif WIN:key_pressed"equals" then select_tower_type(12)
  118. end
  119. if tile and hot then
  120. WIN.scene"hex_cursor".center = rounded_mouse
  121. else
  122. WIN.scene"hex_cursor".center = OFF_SCREEN
  123. end
  124. WIN.scene"score".text = string.format("SCORE: %.2f", SCORE)
  125. WIN.scene"money".text = string.format("MONEY: %d", MONEY)
  126. do
  127. local str = ""
  128. if TRDT == TRDTS.CENTERED_EVENQ then
  129. str = centered_evenq.x .. "," .. centered_evenq.y .. " (cevenq)"
  130. elseif TRDT == TRDTS.EVENQ then
  131. str = evenq.x .. "," .. evenq.y .. " (evenq)"
  132. elseif TRDT == TRDTS.HEX then
  133. str = hex.x .. "," .. hex.y .. " (hex)"
  134. elseif TRDT == TRDTS.PLATFORM then
  135. str = string.format("%s %s lang %s", am.platform, am.version, am.language())
  136. elseif TRDT == TRDTS.PERF then
  137. str = table.tostring(PERF_STATS)
  138. elseif TRDT == TRDTS.SEED then
  139. str = "SEED: " .. HEX_MAP.seed
  140. elseif TRDT == TRDTS.TILE then
  141. str = table.tostring(HEX_MAP.get(hex.x, hex.y))
  142. end
  143. WIN.scene"coords".text = str
  144. end
  145. do_day_night_cycle()
  146. end
  147. function do_day_night_cycle()
  148. local slow = 100
  149. local tstep = (math.sin(TIME / 100) + 1) / PERF_STATS.avg_fps
  150. WORLD"negative_mask".color = vec4(tstep){a=1}
  151. end
  152. function game_end()
  153. -- de-initialize stuff
  154. delete_all_entities()
  155. TIME = 0
  156. SCORE = 0
  157. MONEY = STARTING_MONEY
  158. WORLD = false
  159. WIN.scene = am.group(am.scale(1) ^ game_scene())
  160. end
  161. function update_score(diff)
  162. SCORE = SCORE + diff
  163. end
  164. function update_money(diff)
  165. MONEY = MONEY + diff
  166. end
  167. function get_tower_tooltip_text(tower_type)
  168. return string.format(
  169. "%s\n%s\n%s\ncost: %d"
  170. , get_tower_name(tower_type)
  171. , get_tower_placement_rules_text(tower_type)
  172. , get_tower_short_description(tower_type)
  173. , get_tower_base_cost(tower_type)
  174. )
  175. end
  176. local function toolbelt()
  177. -- init the toolbelt
  178. local toolbelt_height = hex_height(HEX_SIZE) * 2
  179. local toolbelt = am.group{
  180. am.translate(WIN.left + 10, WIN.bottom + toolbelt_height + 20)
  181. ^ am.text(get_tower_tooltip_text(SELECTED_TOWER_TYPE), "left", "bottom"):tag"tower_tooltip",
  182. am.rect(WIN.left, WIN.bottom, WIN.right, WIN.bottom + toolbelt_height, COLORS.TRANSPARENT)
  183. }:tag"toolbelt"
  184. local padding = 15
  185. local size = toolbelt_height - padding
  186. local half_size = size/2
  187. local offset = vec2(WIN.left + padding*3, WIN.bottom + padding/3)
  188. local tab_button = am.translate(vec2(0, half_size) + offset) ^ am.group{
  189. pack_texture_into_sprite(TEXTURES.WIDER_BUTTON1, 54, 32),
  190. pack_texture_into_sprite(TEXTURES.TAB_ICON, 25, 25)
  191. }
  192. toolbelt:append(tab_button)
  193. local tower_select_square = (
  194. am.translate(vec2(size + padding, half_size) + offset)
  195. ^ am.rect(-size/2-3, -size/2-3, size/2+3, size/2+3, COLORS.SUNRAY)
  196. ):tag"tower_select_square"
  197. toolbelt:append(tower_select_square)
  198. -- fill in the other tower options
  199. local tower_type_values = {
  200. TOWER_TYPE.REDEYE,
  201. TOWER_TYPE.LIGHTHOUSE,
  202. TOWER_TYPE.WALL,
  203. TOWER_TYPE.MOAT
  204. }
  205. local keys = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=' }
  206. for i = 1, #keys do
  207. if tower_type_values[i] then
  208. toolbelt:append(
  209. am.translate(vec2(size + padding, 0) * i + offset)
  210. ^ am.group{
  211. am.translate(0, half_size)
  212. ^ pack_texture_into_sprite(TEXTURES.BUTTON1, size, size),
  213. am.translate(0, half_size)
  214. ^ pack_texture_into_sprite(get_tower_texture(tower_type_values[i]), size, size),
  215. am.translate(vec2(half_size))
  216. ^ am.group{
  217. pack_texture_into_sprite(TEXTURES.BUTTON1, half_size, half_size),
  218. am.scale(2)
  219. ^ am.text(keys[i], COLORS.BLACK)
  220. }
  221. }
  222. )
  223. else
  224. toolbelt:append(
  225. am.translate(vec2(size + padding, 0) * i + offset)
  226. ^ am.group{
  227. am.translate(0, half_size)
  228. ^ pack_texture_into_sprite(TEXTURES.BUTTON1, size, size),
  229. am.translate(vec2(half_size))
  230. ^ am.group{
  231. pack_texture_into_sprite(TEXTURES.BUTTON1, half_size, half_size),
  232. am.scale(2)
  233. ^ am.text(keys[i], COLORS.BLACK)
  234. }
  235. }
  236. )
  237. end
  238. end
  239. select_tower_type = function(tower_type)
  240. SELECTED_TOWER_TYPE = tower_type
  241. if TOWER_SPECS[SELECTED_TOWER_TYPE] then
  242. WIN.scene"tower_tooltip".text = get_tower_tooltip_text(tower_type)
  243. local new_position = vec2((size + padding) * tower_type, size/2) + offset
  244. WIN.scene"tower_select_square":action(am.tween(0.1, { position2d = new_position }))
  245. WIN.scene:action(am.play(am.sfxr_synth(SOUNDS.SELECT1), false, 1, SFX_VOLUME))
  246. end
  247. end
  248. return toolbelt
  249. end
  250. -- @NOTE must be global to allow the game_action to reference it
  251. function game_scene()
  252. local score = am.translate(WIN.left + 10, WIN.top - 20) ^ am.text("", "left"):tag"score"
  253. local money = am.translate(WIN.left + 10, WIN.top - 40) ^ am.text("", "left"):tag"money"
  254. local coords = am.translate(WIN.right - 10, WIN.top - 20) ^ am.text("", "right", "top"):tag"coords"
  255. local hex_cursor = am.circle(OFF_SCREEN, HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor"
  256. local curtain = am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRUE_BLACK)
  257. curtain:action(coroutine.create(function()
  258. am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola)))
  259. WIN.scene:remove(curtain)
  260. end))
  261. -- 2227
  262. HEX_MAP, WORLD = random_map()
  263. local scene = am.group{
  264. WORLD,
  265. curtain,
  266. hex_cursor,
  267. toolbelt(),
  268. score,
  269. money,
  270. coords,
  271. }:tag"game"
  272. scene:action(game_action)
  273. --scene:action(am.play(SOUNDS.TRACK1))
  274. return scene
  275. end
  276. WIN.scene = am.group(am.scale(vec2(1)) ^ game_scene())
  277. noglobals()