diff --git a/curves.lua b/curves.lua new file mode 100644 index 0000000..79d8077 --- /dev/null +++ b/curves.lua @@ -0,0 +1,4 @@ + +-- nice curve, f(0) = ~1.1, f(100) = ~100, exponential slope roughly inbetween +-- math.exp((x + 1)/22) / 100 +-- diff --git a/main.lua b/main.lua index 24c0e9c..6b85fc6 100644 --- a/main.lua +++ b/main.lua @@ -1,8 +1,6 @@ + -- @TODO --- music --- -- title theme --- -- game theme -- -- settings menu -- -- music volume @@ -21,21 +19,39 @@ -- -- move home? -- -- game --- -- is 1920x1080 the optimal default resolution? -- -- HEX_GRID_CENTER =/= HOME -- -- allow selecting of tiles, if tower is selected then allow sell/upgrade -- -- button/ui to open pause menu - make it obvious that 'esc' is the button -- -- play the game and tweak numbers -- -- new game menu allowing set seed --- -- gattling gun tower +-- -- gattling gun tower (fast fire rate) -- -- spooder mob -- -- make art, birds-eye-ify the redeye tower and lighthouse maybe? + +-- resolutions and aspect ratios seem like a huge mess +-- for now, i think we should enforce 4:3 +local RESOLUTION_OPTIONS = { + -- 16:9 + -- { width = 1776, height = 1000 }, + -- { width = 1920, height = 1080 }, + -- { width = 1600, height = 900 }, + + -- 4:3 + { width = 1440, height = 1080 }, + { width = 1400, height = 1050 }, -- seems like a good one + { width = 1280, height = 960 }, + { width = 1152, height = 864 }, + { width = 1024, height = 768 }, + { width = 960, height = 720 }, + { width = 832, height = 624 }, + { width = 800, height = 600 }, +} settings = am.load_state("settings", "json") or { fullscreen = false, - window_width = 1920, - window_height = 1080, - music_volume = 0.1, + window_width = 1400, + window_height = 1050, + music_volume = 0.2, sfx_volume = 0.1, } @@ -51,9 +67,11 @@ do height = settings.window_height, title = "hexyz", mode = settings.fullscreen and "fullscreen" or "windowed", + resizable = true, highdpi = true, letterbox = true, resizable = true, -- user should probably set their resolution instead of resizing the window, but hey. + show_cursor = true, } end @@ -75,22 +93,26 @@ require "src/projectile" require "src/tower" --- js style popup in the middle of the screen that dissapates -function alert(message) +-- text popup in the middle of the screen that dissapates, call from anywhere +function alert(message, color) win.scene:append( - am.scale(3) ^ am.text(message) + am.scale(3) ^ am.text(message, color or COLORS.WHITE) :action(coroutine.create(function(self) - am.wait(am.tween(self, 1, { color = vec4(0) })) + am.wait(am.tween(self, 1, { color = vec4(0) }, am.ease_out)) win.scene:remove(self) end)) ) end +function unpause(root_node) + win.scene("game").paused = false + win.scene:remove(root_node) +end + function main_action(self) if win:key_pressed("escape") then - if win.scene("game") then - win.scene("game").paused = false - win.scene:remove(self) + if game then + unpause(self) else --win:close() end @@ -102,9 +124,8 @@ end function make_main_scene_toolbelt() local include_save_option = game + local include_unpause_option = game local options = { - false, - false, false, { texture = TEXTURES.NEW_GAME_HEX, @@ -128,38 +149,36 @@ function make_main_scene_toolbelt() local save = am.load_state("save", "json") if save then - win.scene:remove"menu" + win.scene:remove("menu") game_init(save) else alert("no saved games") end end }, - - false, { texture = TEXTURES.MAP_EDITOR_HEX, action = function() alert("not yet :)") end }, + include_unpause_option and { + texture = TEXTURES.UNPAUSE_HEX, + action = function() unpause(win.scene("menu")) end + } or false, { texture = TEXTURES.SETTINGS_HEX, action = function() alert("not yet :)") end }, - { - texture = TEXTURES.ABOUT_HEX, - action = function() alert("not yet :)") end - }, - false, { texture = TEXTURES.QUIT_HEX, action = function() win:close() end - } + }, + false } - local spacing = 160 + local spacing = 150 -- calculate the dimensions of the whole grid - local grid_width = 8 + local grid_width = 6 local grid_height = 2 local hhs = hex_horizontal_spacing(spacing) local hvs = hex_vertical_spacing(spacing) @@ -240,9 +259,12 @@ function main_scene(do_backdrop, do_logo) end group:append(hex_backdrop) else - group:append(am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRANSPARENT)) + group:append( + pack_texture_into_sprite(TEXTURES.CURTAIN, win.width, win.height) + ) end + -- @TODO add a hyperlink to an 'about' page or something group:append( am.translate(win.right - 10, win.bottom + 10) ^ am.text(string.format("v%s, by %s", version, author), COLORS.WHITE, "right", "bottom") @@ -265,5 +287,7 @@ end win.scene = am.group( main_scene(true, true) ) +play_track(SOUNDS.MAIN_THEME) + noglobals() diff --git a/res/curtain1.png b/res/curtain1.png new file mode 100644 index 0000000..063af5b Binary files /dev/null and b/res/curtain1.png differ diff --git a/res/gear.png b/res/gear.png new file mode 100644 index 0000000..a844dba Binary files /dev/null and b/res/gear.png differ diff --git a/res/gears.png b/res/gears.png new file mode 100644 index 0000000..0b24327 Binary files /dev/null and b/res/gears.png differ diff --git a/res/maintheme.ogg b/res/maintheme.ogg new file mode 100644 index 0000000..5dd779c Binary files /dev/null and b/res/maintheme.ogg differ diff --git a/res/select_box.png b/res/select_box.png new file mode 100644 index 0000000..53cb083 Binary files /dev/null and b/res/select_box.png differ diff --git a/res/track1.ogg b/res/track1.ogg deleted file mode 100644 index 338ddf9..0000000 Binary files a/res/track1.ogg and /dev/null differ diff --git a/res/unpausehex.png b/res/unpausehex.png new file mode 100644 index 0000000..701968c Binary files /dev/null and b/res/unpausehex.png differ diff --git a/sound.lua b/sound.lua index d1213b0..0689ab9 100644 --- a/sound.lua +++ b/sound.lua @@ -22,7 +22,7 @@ SOUNDS = { RANDOM5 = 36680709, -- audio buffers - TRACK1 = am.track(am.load_audio("res/track1.ogg"), true, 1, 0.1) + MAIN_THEME = am.track(am.load_audio("res/maintheme.ogg"), true, 1, settings.music_volume) } -- play sound effect with variable pitch @@ -35,3 +35,7 @@ function play_sfx(sound) win.scene:action(am.play(sound, false, 1, settings.sfx_volume)) end +function play_track(track) + win.scene:action(am.play(track)) +end + diff --git a/src/game.lua b/src/game.lua index 14a33a1..0623279 100644 --- a/src/game.lua +++ b/src/game.lua @@ -6,9 +6,7 @@ state = {} function game_end() state = {} game = false - -- @TODO - end function update_score(diff) state.score = state.score + diff end @@ -49,7 +47,7 @@ local function get_initial_game_state(seed) time_until_next_wave = 0, time_until_next_break = 0, spawning = false, - spawn_chance = 0.002, + spawn_chance = 0, last_mob_spawn_time = 0, selected_tower_type = false, @@ -216,6 +214,7 @@ local function game_action(scene) state.current_wave = state.current_wave + 1 state.spawning = false + state.time_until_next_wave = get_break_time(state.current_wave) end else @@ -224,10 +223,11 @@ local function game_action(scene) if state.time_until_next_wave <= 0 then state.time_until_next_wave = 0 + state.spawning = true + -- calculate spawn chance for next wave - state.spawn_chance = (state.current_wave + 1)/200 + state.spawn_chance = math.log(state.current_wave) + 0.002 - state.spawning = true state.time_until_next_break = get_wave_time(state.current_wave) end end @@ -255,7 +255,7 @@ local function game_action(scene) node.color = COLORS.CLARET node:action(am.tween(0.1, { color = COLORS.TRANSPARENT })) play_sfx(SOUNDS.BIRD2) - alert("breaks flow field") + alert("closes the circle") elseif cost > state.money then local node = win.scene("cursor"):child(2) @@ -272,6 +272,9 @@ local function game_action(scene) apply_flow_field(state.map, flow_field, state.world) end end + elseif not state.selected_tower_type then + -- interactable tile, but no tower type selected + end end end @@ -371,7 +374,7 @@ local function make_game_toolbelt() return button end - local toolbelt_height = win.height * 0.08 + local toolbelt_height = win.height * 0.07 local function get_tower_tooltip_text_node(tower_type) local name = get_tower_name(tower_type) local placement_rules = get_tower_placement_rules_text(tower_type) @@ -400,48 +403,68 @@ local function make_game_toolbelt() ) :tag"tower_tooltip_text" end - local toolbelt = am.group{ + local toolbelt = am.group( am.group():tag"tower_tooltip_text", am.rect(win.left, win.bottom, win.right, win.bottom + toolbelt_height, COLORS.TRANSPARENT) - } + ) :tag"toolbelt" - local padding = 15 + local padding = 12 local size = toolbelt_height - padding local half_size = size/2 - local offset = vec2(win.left + padding*3, win.bottom + padding/3) - local tab_button = am.translate(vec2(0, half_size) + offset) ^ am.group{ - pack_texture_into_sprite(TEXTURES.WIDER_BUTTON1, 54, 32), - pack_texture_into_sprite(TEXTURES.TAB_ICON, 25, 25) - } - toolbelt:append(tab_button) + local offset = vec2(win.left, win.bottom + padding/3) + local tower_select_square = ( am.translate(vec2(size + padding, half_size) + offset) ^ am.rect(-size/2-3, -size/2-3, size/2+3, size/2+3, COLORS.SUNRAY) ) :tag"tower_select_square" - tower_select_square.hidden = true toolbelt:append(tower_select_square) local keys = { '1', '2', '3', '4', 'q', 'w', 'e', 'r', 'a', 's', 'd', 'f' } --local keys = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=' } - -- order of this array is the order of towers on the toolbelt. - local tower_type_values = { + local TOOLBELT_OPTION = { + SELECT = 11 + } + local toolbelt_options = { TOWER_TYPE.WALL, TOWER_TYPE.HOWITZER, TOWER_TYPE.REDEYE, TOWER_TYPE.MOAT, TOWER_TYPE.RADAR, - TOWER_TYPE.LIGHTHOUSE + TOWER_TYPE.LIGHTHOUSE, + + -- reserved for tower types + false, + false, + false, + false, + + TOOLBELT_OPTION.SELECT, + false } - for i,v in pairs(tower_type_values) do - local icon_texture = get_tower_icon_texture(tower_type_values[i]) + local tower_type_count = table.count(TOWER_TYPE) + local function get_toolbelt_icon_texture(i) + if i <= tower_type_count then + return get_tower_icon_texture(toolbelt_options[i]) + + else + local toolbelt_option = TOOLBELT_OPTION[i - tower_type_count] + + if toolbelt_option then + if toolbelt_option == TOOLBELT_OPTION.SELECT then + return TEXTURES.SELECT_BOX_ICON + end + end + end + end + for i,v in pairs(toolbelt_options) do toolbelt:append( toolbelt_button( size, half_size, - icon_texture, + get_toolbelt_icon_texture(i), padding, i, offset, @@ -450,6 +473,30 @@ local function make_game_toolbelt() ) end + local settings_button_position = vec2(win.right - half_size - 20, win.bottom + half_size + padding/3) + local settings_button_rect = { + x1 = settings_button_position.x - size/2, + y1 = settings_button_position.y - size/2, + x2 = settings_button_position.x + size/2, + y2 = settings_button_position.y + size/2 + } + + -- make the 'escape/pause/settings' button in the lower right + toolbelt:append( + am.translate(settings_button_position) + ^ am.group( + pack_texture_into_sprite(TEXTURES.BUTTON1, size, size), + pack_texture_into_sprite(TEXTURES.GEAR, size-padding, size-padding) + ) + :action(function(self) + if point_in_rect(win:mouse_position(), settings_button_rect) then + if win:mouse_pressed("left") then + game_pause() + end + end + end) + ) + select_tower_type = function(tower_type) state.selected_tower_type = tower_type @@ -470,6 +517,7 @@ local function make_game_toolbelt() else -- de-selecting currently selected tower if any toolbelt("tower_select_square").hidden = true + log('hi2') win.scene:replace("cursor", make_hex_cursor(0, COLORS.TRANSPARENT)) end @@ -478,10 +526,15 @@ local function make_game_toolbelt() select_toolbelt_button = function(i) state.selected_toolbelt_button = i - if tower_type_values[i] then + if i <= tower_type_count then select_tower_type(i) + else select_tower_type(nil) + + if i == 11 then + log('hi') + end end end diff --git a/src/grid.lua b/src/grid.lua index 4b65e9e..b025ddd 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -1,37 +1,28 @@ --- distance from hex centerpoint to any vertex -HEX_SIZE = 26 - -HEX_PIXEL_WIDTH = hex_width(HEX_SIZE, HEX_ORIENTATION.FLAT) -HEX_PIXEL_HEIGHT = hex_height(HEX_SIZE, HEX_ORIENTATION.FLAT) -HEX_PIXEL_DIMENSIONS = vec2(HEX_PIXEL_WIDTH, HEX_PIXEL_HEIGHT) - do -- add padding, because we terraform the very outer edge and it looks ugly, so hide it - local padding = 3 + local padding = 2 - HEX_GRID_WIDTH = math.floor(win.width / (HEX_PIXEL_WIDTH + HEX_SIZE) * 2) + padding - HEX_GRID_HEIGHT = math.floor(win.height / HEX_PIXEL_HEIGHT) + padding - - -- odd numbers are important because we want a 'true' center - if HEX_GRID_WIDTH % 2 == 0 then - HEX_GRID_WIDTH = HEX_GRID_WIDTH + 1 - end - if HEX_GRID_HEIGHT % 2 == 0 then - HEX_GRID_HEIGHT = HEX_GRID_HEIGHT + 1 - end + -- the size of the grid should basically always be constant (i think), + -- but different aspect ratios complicate this in an annoying way + HEX_GRID_WIDTH = 41 + padding + HEX_GRID_HEIGHT = 27 + padding HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT) - -- leaving y == 0 makes this the center in hex/cube coordinates - -- assuming that our dimensions are correct (odd numbers) - HEX_GRID_CENTER = vec2(math.floor(HEX_GRID_WIDTH/2) - , 0) + HEX_GRID_CENTER = evenq_to_hex(vec2(math.floor(HEX_GRID_WIDTH/2) + , -math.floor(HEX_GRID_HEIGHT/2))) end -HEX_GRID_MINIMUM_ELEVATION = -1 -HEX_GRID_MAXIMUM_ELEVATION = 1 +-- pixel distance from hex centerpoint to any vertex +-- given a grid width gx, and window width wx, what's the smallest size a hex can be to fill the whole screen? +-- wx / (gx * 3 / 2) +HEX_SIZE = win.width / (41 * 3 / 2) + +HEX_PIXEL_WIDTH = hex_width(HEX_SIZE, HEX_ORIENTATION.FLAT) +HEX_PIXEL_HEIGHT = hex_height(HEX_SIZE, HEX_ORIENTATION.FLAT) +HEX_PIXEL_DIMENSIONS = vec2(HEX_PIXEL_WIDTH, HEX_PIXEL_HEIGHT) do local hhs = hex_horizontal_spacing(HEX_SIZE) @@ -63,13 +54,8 @@ function evenq_is_in_interactable_region(evenq) }) end -function is_water_elevation(elevation) - return elevation < -0.5 -end - -function is_mountain_elevation(elevation) - return elevation >= 0.5 -end +function is_water_elevation(elevation) return elevation < -0.5 end +function is_mountain_elevation(elevation) return elevation >= 0.5 end function tile_is_medium_elevation(tile) return tile.elevation >= -0.5 and tile.elevation < 0.5 @@ -79,6 +65,8 @@ function grid_heuristic(source, target) return math.distance(source, target) end +HEX_GRID_MINIMUM_ELEVATION = -1 +HEX_GRID_MAXIMUM_ELEVATION = 1 function grid_cost(map, from, to) local t1, t2 = hex_map_get(map, from), hex_map_get(map, to) @@ -229,7 +217,6 @@ function make_hex_grid_scene(map) end apply_flow_field(map, generate_flow_field(map, HEX_GRID_CENTER), world) - return am.translate(WORLDSPACE_COORDINATE_OFFSET) ^ world end diff --git a/src/hexyz.lua b/src/hexyz.lua index 6473020..d0fe0f9 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -9,18 +9,16 @@ -- and some utility functions not present in your standard lua, like: -- table.append + if not math.round then math.round = function(n) return math.floor(n + 0.5) end else - error("clobbering a math.round function, oopsie!") -end - -if not table.append then -end - -if not table.filter then + error("clobbering 'math.round', oopsie!") end +-- @TODO +if not table.append then end +if not table.filter then end -- wherever 'orientation' appears as an argument, use one of these two, or set a default just below HEX_ORIENTATION = { @@ -235,6 +233,10 @@ end --============================================================================ -- MAPS & STORAGE +-- maps that use their indices as the hex coordinates (parallelogram, hexagonal, rectangular, triangular), +-- fail to serialize ideally because they use negative indices, which json doesn't support + + -- Returns Ordered Ring-Shaped Map of |radius| from |center| function hex_ring_map(center, radius) local map = {} diff --git a/src/tower.lua b/src/tower.lua index 35b1ac8..12393c3 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -264,11 +264,9 @@ function tower_on_hex(hex) end function tower_type_is_buildable_on(hex, tile, tower_type) + -- this function gets polled a lot, and sometimes with nil/false tower types if not tower_type then return false end - -- @TODO remove this shit - if hex == HEX_GRID_CENTER then return false end - local blocking_towers = {} local blocking_mobs = {} local has_water = false diff --git a/texture.lua b/texture.lua index ada6fce..b608906 100644 --- a/texture.lua +++ b/texture.lua @@ -10,11 +10,13 @@ local function load_texture(filepath) end end + TEXTURES = { + -- note that in amulet, if you prefix paths with './', they fail to be found in the exported data.pak LOGO = load_texture("res/logo.png"), GEM1 = load_texture("res/gem1.png"), - SHADED_HEX = load_texture("res/shaded_hex.png"), + SHADED_HEX = load_texture("res/shaded_hex.png"), NEW_GAME_HEX = load_texture("res/newgamehex.png"), SAVE_GAME_HEX = load_texture("res/savegamehex.png"), LOAD_GAME_HEX = load_texture("res/loadgamehex.png"), @@ -22,12 +24,18 @@ TEXTURES = { MAP_EDITOR_HEX = load_texture("res/mapeditorhex.png"), ABOUT_HEX = load_texture("res/abouthex.png"), QUIT_HEX = load_texture("res/quithex.png"), + UNPAUSE_HEX = load_texture("res/unpausehex.png"), + + CURTAIN = load_texture("res/curtain1.png"), -- gui stuff BUTTON1 = load_texture("res/button1.png"), WIDER_BUTTON1 = load_texture("res/wider_button1.png"), TAB_ICON = load_texture("res/tab_icon.png"), GUI_SLIDER = load_texture("res/slider.png"), + GEAR = load_texture("res/gear.png"), + + SELECT_BOX_ICON = load_texture("res/select_box.png"), -- tower stuff TOWER_WALL = load_texture("res/tower_wall.png"),