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.

353 lines
10 KiB

3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 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
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 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. -- @TODO @TODO @TODO @TODO
  2. -- main
  3. -- -- scale menu hexes to window size, right now they look bad on smaller resolutions
  4. -- settings menu
  5. -- -- make the volume icon clickable
  6. -- -- music volume slider or number input box
  7. -- -- sfx volume slider or number input box
  8. -- -- allow different resolution options, as long as you are 4:3
  9. -- serialization
  10. -- -- allow saving by name
  11. -- -- allow loading by name
  12. -- -- investigate saving as lua instead, and having as a consequence a less janky map serialization - we don't care about exploitability
  13. -- sound
  14. -- -- fix the non-seamless loop in the soundtrack
  15. -- -- more trax
  16. -- game
  17. -- -- allow selecting of tiles, if tower is selected then allow sell/upgrade
  18. -- -- new game menu allowing set seed
  19. -- -- make art, birds-eye-ify the redeye tower and lighthouse maybe?
  20. -- map editor?
  21. -- -- paint terrain elevation levels
  22. -- -- place tiles of set elevation
  23. -- -- place towers
  24. -- -- move home?
  25. -- lua's random number generator doesn't really produce random looking values if you don't seed it and discard a few calls first
  26. math.randomseed(os.time())
  27. math.random()
  28. math.random()
  29. math.random()
  30. math.random()
  31. -- aspect ratios seem like a huge mess
  32. -- for now, i think we should enforce 4:3
  33. local RESOLUTION_OPTIONS = {
  34. { width = 1440, height = 1080 },
  35. { width = 1400, height = 1050 }, -- seems like a good default one
  36. { width = 1280, height = 960 },
  37. { width = 1152, height = 864 },
  38. { width = 1024, height = 768 },
  39. { width = 960, height = 720 },
  40. { width = 832, height = 624 },
  41. { width = 800, height = 600 },
  42. }
  43. local DEFAULT_RESOLUTION = RESOLUTION_OPTIONS[2]
  44. settings = am.load_state("settings", "json") or {
  45. fullscreen = false,
  46. window_width = DEFAULT_RESOLUTION.width,
  47. window_height = DEFAULT_RESOLUTION.height,
  48. music_volume = 0.2,
  49. sfx_volume = 0.1,
  50. sound_on = true
  51. }
  52. win = am.window{
  53. width = settings.window_width,
  54. height = settings.window_height,
  55. title = "hexyz",
  56. mode = settings.fullscreen and "fullscreen" or "windowed",
  57. resizable = false,
  58. highdpi = true,
  59. letterbox = true,
  60. show_cursor = true,
  61. }
  62. -- top right display types
  63. -- different scenes overlay different content in the top right of the screen
  64. -- f1 toggles what is displayed in the top right of the screen in some scenes
  65. TRDTS = {
  66. NOTHING = 0,
  67. CENTERED_EVENQ = 1,
  68. EVENQ = 2,
  69. HEX = 3,
  70. PLATFORM = 4,
  71. PERF = 5,
  72. SEED = 6,
  73. TILE = 7,
  74. }
  75. function make_top_right_display_node()
  76. return am.translate(win.right - 10, win.top - 15)
  77. ^ am.text("", "right", "top"):tag"top_right_display"
  78. end
  79. -- asset interfaces and/or trivial code
  80. require "conf"
  81. require "color"
  82. require "sound"
  83. require "texture"
  84. --
  85. require "src/entity"
  86. require "src/extra"
  87. require "src/memory"
  88. require "src/geometry"
  89. require "src/hexyz"
  90. require "src/game"
  91. require "src/gui"
  92. require "src/grid"
  93. require "src/mob"
  94. require "src/projectile"
  95. require "src/tower"
  96. require "src/map-editor"
  97. ------------------------------------------------------------------
  98. local sound_toggle_node_tag = "sound_on_off_icon"
  99. local function make_sound_toggle_node(on)
  100. local sprite
  101. if on then
  102. sprite = pack_texture_into_sprite(TEXTURES.SOUND_ON1, 40, 30)
  103. else
  104. sprite = pack_texture_into_sprite(TEXTURES.SOUND_OFF, 40, 30)
  105. end
  106. return (am.translate(win.right - 30, win.top - 60) ^ sprite)
  107. :tag(sound_toggle_node_tag)
  108. :action(function()
  109. -- @TODO click me!
  110. end)
  111. end
  112. local cached_music_volume = 0.2
  113. local cached_sfx_volume = 0.1
  114. local function toggle_mute()
  115. settings.sound_on = not settings.sound_on
  116. if settings.sound_on then
  117. settings.music_volume = cached_music_volume
  118. settings.sfx_volume = cached_sfx_volume
  119. else
  120. cached_music_volume = settings.music_volume
  121. cached_sfx_volume = settings.sfx_volume
  122. settings.music_volume = 0
  123. settings.sfx_volume = 0
  124. end
  125. update_music_volume(settings.music_volume)
  126. win.scene:replace(sound_toggle_node_tag, make_sound_toggle_node(settings.sound_on))
  127. end
  128. function main_action(self)
  129. if win:key_pressed("escape") then
  130. if game then
  131. unpause(self)
  132. else
  133. --win:close()
  134. end
  135. elseif win:key_pressed("f4") then
  136. win:close()
  137. elseif win:key_pressed("m") then
  138. toggle_mute()
  139. end
  140. if self"hex_backdrop" then
  141. self"hex_backdrop""rotate".angle = math.wrapf(self"hex_backdrop""rotate".angle - 0.005 * am.delta_time, math.pi*2)
  142. end
  143. end
  144. function make_scene_menu(scene_options, tag)
  145. -- calculate the dimensions of the whole grid
  146. local spacing = 150
  147. local grid_width = 6
  148. local grid_height = 2
  149. local hhs = hex_horizontal_spacing(spacing)
  150. local hvs = hex_vertical_spacing(spacing)
  151. local grid_pixel_width = grid_width * hhs
  152. local grid_pixel_height = grid_height * hvs
  153. local pixel_offset = vec2(-grid_pixel_width/2, win.bottom + hex_height(spacing)/2 + 20)
  154. -- generate a map of hexagons (the menu is made up of two rows of hexes) and populate their locations with buttons from the provided options
  155. local map = hex_rectangular_map(grid_width, grid_height, HEX_ORIENTATION.POINTY)
  156. local group = am.group():tag(tag or "menu")
  157. local option_index = 1
  158. for i,_ in pairs(map) do
  159. for j,_ in pairs(map[i]) do
  160. local hex = vec2(i, j)
  161. local position = hex_to_pixel(hex, vec2(spacing), HEX_ORIENTATION.POINTY)
  162. local option = scene_options[option_index]
  163. local texture = option and option.texture or TEXTURES.SHADED_HEX
  164. local color = option and COLORS.TRANSPARENT or vec4(0.3)
  165. local node = am.translate(position)
  166. ^ pack_texture_into_sprite(texture, texture.width, texture.height, color)
  167. hex_map_set(map, i, j, {
  168. node = node,
  169. option = option
  170. })
  171. local tile = hex_map_get(map, i, j)
  172. local selected = false
  173. node:action(function(self)
  174. local mouse = win:mouse_position()
  175. local hex_ = pixel_to_hex(mouse - pixel_offset, vec2(spacing), HEX_ORIENTATION.POINTY)
  176. if tile.option then
  177. if hex == hex_ then
  178. if not selected then
  179. play_sfx(SOUNDS.SELECT1)
  180. end
  181. selected = true
  182. tile.node"sprite".color = vec4(1)
  183. if win:mouse_pressed("left") then
  184. tile.option.action()
  185. end
  186. else
  187. selected = false
  188. tile.node"sprite".color = COLORS.TRANSPARENT
  189. end
  190. end
  191. end)
  192. group:append(node)
  193. option_index = option_index + 1
  194. end
  195. end
  196. return am.translate(pixel_offset) ^ group
  197. end
  198. function main_scene(do_backdrop, do_logo)
  199. local group = am.group():tag"main_scene"
  200. if do_backdrop then
  201. local map = hex_hexagonal_map(30)
  202. local hex_backdrop = (am.rotate(0) ^ am.group()):tag"hex_backdrop"
  203. for i,_ in pairs(map) do
  204. for j,n in pairs(map[i]) do
  205. local color = map_elevation_color(n)
  206. color = color{a=color.a - 0.1}
  207. local node = am.translate(hex_to_pixel(vec2(i, j), vec2(HEX_SIZE)))
  208. ^ am.circle(vec2(0), HEX_SIZE, vec4(0), 6)
  209. node"circle":action(am.tween(0.6, { color = color }))
  210. hex_backdrop:append(node)
  211. end
  212. end
  213. group:append(hex_backdrop)
  214. else
  215. group:append(
  216. pack_texture_into_sprite(TEXTURES.CURTAIN, win.width, win.height)
  217. )
  218. end
  219. -- version/author info
  220. group:append(
  221. am.translate(win.right - 10, win.bottom + 10)
  222. ^ am.text(string.format("v%s, by %s", version, author), COLORS.WHITE, "right", "bottom")
  223. )
  224. group:append(
  225. make_sound_toggle_node(settings.sound_on)
  226. )
  227. if do_logo then
  228. local position = vec2(0, win.top - 20 - TEXTURES.LOGO.height/2)
  229. local logo =
  230. am.translate(position)
  231. ^ pack_texture_into_sprite(TEXTURES.LOGO, TEXTURES.LOGO.width, TEXTURES.LOGO.height)
  232. local selected = false
  233. logo:action(function(self)
  234. local mouse = win:mouse_position()
  235. if math.distance(mouse, position) < TEXTURES.LOGO.height/2 then
  236. selected = true
  237. self"sprite".color = vec4(1)
  238. if win:mouse_pressed("left") then
  239. vplay_sfx(math.random(1000000000))
  240. end
  241. else
  242. selected = false
  243. self"sprite".color = vec4(0.95)
  244. end
  245. end)
  246. group:append(logo)
  247. end
  248. local main_scene_options = {
  249. false,
  250. {
  251. texture = TEXTURES.NEW_GAME_HEX,
  252. action = function()
  253. win.scene:remove"main_scene"
  254. game_init()
  255. end
  256. },
  257. false,
  258. false,
  259. false,
  260. {
  261. texture = TEXTURES.LOAD_GAME_HEX,
  262. action = function()
  263. local save = am.load_state("save", "json")
  264. if save then
  265. win.scene:remove("main_scene")
  266. game_init(save)
  267. else
  268. gui_alert("no saved games")
  269. end
  270. end
  271. },
  272. {
  273. texture = TEXTURES.MAP_EDITOR_HEX,
  274. action = function()
  275. win.scene:remove("main_scene")
  276. map_editor_init()
  277. end
  278. },
  279. false,
  280. {
  281. texture = TEXTURES.SETTINGS_HEX,
  282. action = function()
  283. gui_alert("not yet :)")
  284. end
  285. },
  286. {
  287. texture = TEXTURES.QUIT_HEX,
  288. action = function()
  289. win:close()
  290. end
  291. },
  292. false
  293. }
  294. group:append(make_scene_menu(main_scene_options))
  295. group:action(main_action)
  296. return group
  297. end
  298. win.scene = am.group(
  299. main_scene(true, true)
  300. )
  301. play_track(SOUNDS.MAIN_THEME)
  302. noglobals()