diff --git a/main.lua b/main.lua index 6b85fc6..b97c553 100644 --- a/main.lua +++ b/main.lua @@ -116,6 +116,8 @@ function main_action(self) else --win:close() end + elseif win:key_pressed("f4") then + win:close() end if self"hex_backdrop" then self"hex_backdrop""rotate".angle = math.wrapf(self"hex_backdrop""rotate".angle - 0.005 * am.delta_time, math.pi*2) @@ -175,17 +177,14 @@ function make_main_scene_toolbelt() false } - local spacing = 150 - -- calculate the dimensions of the whole grid + local spacing = 150 local grid_width = 6 local grid_height = 2 local hhs = hex_horizontal_spacing(spacing) local hvs = hex_vertical_spacing(spacing) local grid_pixel_width = grid_width * hhs local grid_pixel_height = grid_height * hvs - -- @TODO the vertical offset should be different depending on if this is the main menu or the pause menu - -- perhaps the map that makes the grid of hexes should be different as well local pixel_offset = vec2(-grid_pixel_width/2, win.bottom + hex_height(spacing)/2 + 20) local map = hex_rectangular_map(grid_width, grid_height, HEX_ORIENTATION.POINTY) @@ -271,10 +270,27 @@ function main_scene(do_backdrop, do_logo) ) if do_logo then - group:append( - am.translate(0, win.top - 20 - TEXTURES.LOGO.height/2) + local position = vec2(0, win.top - 20 - TEXTURES.LOGO.height/2) + local logo = + am.translate(position) ^ pack_texture_into_sprite(TEXTURES.LOGO, TEXTURES.LOGO.width, TEXTURES.LOGO.height) - ) + + local selected = false + logo:action(function(self) + local mouse = win:mouse_position() + if math.distance(mouse, position) < TEXTURES.LOGO.height/2 then + selected = true + self"sprite".color = vec4(1) + if win:mouse_pressed("left") then + + end + else + selected = false + self"sprite".color = vec4(0.95) + end + end) + + group:append(logo) end group:append(make_main_scene_toolbelt()) diff --git a/res/loadgamehex.png b/res/loadgamehex.png index d4f791b..3c2ce26 100644 Binary files a/res/loadgamehex.png and b/res/loadgamehex.png differ diff --git a/res/mapeditorhex.png b/res/mapeditorhex.png index e2ad95e..9294f76 100644 Binary files a/res/mapeditorhex.png and b/res/mapeditorhex.png differ diff --git a/res/newgamehex.png b/res/newgamehex.png index 5008f8b..cf50e03 100644 Binary files a/res/newgamehex.png and b/res/newgamehex.png differ diff --git a/res/quithex.png b/res/quithex.png index 0b5cf86..6a48154 100644 Binary files a/res/quithex.png and b/res/quithex.png differ diff --git a/res/savegamehex.png b/res/savegamehex.png index fa5ee75..129d677 100644 Binary files a/res/savegamehex.png and b/res/savegamehex.png differ diff --git a/res/settingshex.png b/res/settingshex.png index 213a591..ee0f874 100644 Binary files a/res/settingshex.png and b/res/settingshex.png differ diff --git a/res/unpausehex.png b/res/unpausehex.png index 701968c..5e3ea23 100644 Binary files a/res/unpausehex.png and b/res/unpausehex.png differ diff --git a/src/game.lua b/src/game.lua index 0623279..ce34b59 100644 --- a/src/game.lua +++ b/src/game.lua @@ -3,14 +3,6 @@ game = false -- flag to tell if there is a game running state = {} -function game_end() - state = {} - game = false - -- @TODO -end - -function update_score(diff) state.score = state.score + diff end -function update_money(diff) state.money = state.money + diff end -- top right display types local TRDTS = { @@ -25,9 +17,9 @@ local TRDTS = { } local function get_initial_game_state(seed) - local STARTING_MONEY = 200 + local STARTING_MONEY = 50 - local map, world = random_map() + local map, world = random_map(seed) return { map = map, -- map of hex coords map[x][y] to a 'tile' @@ -95,14 +87,14 @@ local function select_tower_type(tower_type) end local function select_toolbelt_button(i) end local function get_wave_time(current_wave) - return 90 + return 45 end local function get_break_time(current_wave) - return 15 + return 20 end -function do_day_night_cycle() +local function do_day_night_cycle() local tstep = (math.sin(state.time * am.delta_time) + 1) / 100 --state.world"negative_mask".color = vec4(tstep){a=1} end @@ -166,8 +158,12 @@ local function game_serialize() serialized.map = nil -- we re-generate the entire map from the seed on de-serialize -- in order to serialize the game state, we have to convert all relevant userdata into - -- something else. this practically only means vectors need to become arrays of floats. - -- this is dumb and if i forsaw this i would have probably used float arrays the whole time. + -- something else. + -- + -- this practically means vectors need to become arrays of floats, + -- and the scene graph needs to be re-constituted at load time + -- + -- this is dumb and if i forsaw this i would have probably used float arrays instead of vectors serialized.towers = {} for i,t in pairs(state.towers) do @@ -194,11 +190,6 @@ local function game_serialize() return am.to_json(serialized) end -function game_save() - am.save_state("save", game_serialize(), "json") - log("succesfully saved!") -end - local function game_action(scene) if state.score < 0 then game_end() return true end @@ -226,7 +217,7 @@ local function game_action(scene) state.spawning = true -- calculate spawn chance for next wave - state.spawn_chance = math.log(state.current_wave) + 0.002 + state.spawn_chance = math.log(state.current_wave)/2 + 0.002 state.time_until_next_break = get_wave_time(state.current_wave) end @@ -242,7 +233,6 @@ local function game_action(scene) local interactable = evenq_is_in_interactable_region(evenq{ y = -evenq.y }) local buildable = tower_type_is_buildable_on(hex, tile, state.selected_tower_type) - local firable = false if win:mouse_pressed"left" then if interactable then @@ -274,6 +264,7 @@ local function game_action(scene) end elseif not state.selected_tower_type then -- interactable tile, but no tower type selected + local towers = towers_on_hex(hex) end end @@ -323,16 +314,23 @@ local function game_action(scene) do_mob_spawning(state.spawn_chance) do_day_night_cycle() - if interactable then - win.scene("cursor").hidden = false + -- update the cursor + if not interactable then + win.scene("cursor").hidden = true + + else + if state.selected_tower_type then + if buildable then + win.scene("cursor").hidden = false - if buildable then - win.scene("cursor_translate").position2d = rounded_mouse + else + win.scene("cursor").hidden = true + end else - win.scene("cursor").hidden = true + -- if we don't have a tower selected, but the tile is interactable, then show the 'select' cursor + win.scene("cursor").hidden = false end - else - win.scene("cursor").hidden = true + win.scene("cursor_translate").position2d = rounded_mouse end win.scene("score").text = string.format("SCORE: %.2f", state.score) @@ -343,8 +341,9 @@ end local function make_game_toolbelt() local function toolbelt_button(size, half_size, tower_texture, padding, i, offset, key_name) - local button = am.translate(vec2(size + padding, 0) * i + offset) - ^ am.group{ + local button = + am.translate(vec2(size + padding, 0) * i + offset) + ^ am.group( am.translate(0, half_size) ^ pack_texture_into_sprite(TEXTURES.BUTTON1, size, size), @@ -352,12 +351,12 @@ local function make_game_toolbelt() ^ pack_texture_into_sprite(tower_texture, size, size), am.translate(vec2(half_size)) - ^ am.group{ + ^ am.group( pack_texture_into_sprite(TEXTURES.BUTTON1, half_size, half_size), am.scale(2) ^ am.text(key_name, COLORS.BLACK) - } - } + ) + ) local x1 = (size + padding) * i + offset.x - half_size local y1 = offset.y @@ -366,8 +365,12 @@ local function make_game_toolbelt() local rect = { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } button:action(function(self) - if win:mouse_pressed"left" and point_in_rect(win:mouse_position(), rect) then - select_toolbelt_button(i) + if point_in_rect(win:mouse_position(), rect) then + win.scene:replace("tower_tooltip_text", get_tower_tooltip_text_node(tower_type)) + + if win:mouse_pressed"left" then + select_toolbelt_button(i) + end end end) @@ -387,7 +390,8 @@ local function make_game_toolbelt() local color = COLORS.WHITE return (am.translate(win.left + 10, win.bottom + toolbelt_height + 20) - ^ am.group{ + ^ am.scale(1) + ^ am.group( am.translate(0, 60) ^ am.text(name, color, "left"):tag"tower_name", @@ -399,7 +403,7 @@ local function make_game_toolbelt() am.translate(0, 0) ^ am.text(string.format("cost: %d", cost), color, "left"):tag"tower_cost" - } + ) ) :tag"tower_tooltip_text" end @@ -424,9 +428,6 @@ local function make_game_toolbelt() 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', '-', '=' } - local TOOLBELT_OPTION = { - SELECT = 11 - } local toolbelt_options = { TOWER_TYPE.WALL, TOWER_TYPE.HOWITZER, @@ -434,29 +435,11 @@ local function make_game_toolbelt() TOWER_TYPE.MOAT, TOWER_TYPE.RADAR, TOWER_TYPE.LIGHTHOUSE, - - -- reserved for tower types - false, - false, - false, - false, - - TOOLBELT_OPTION.SELECT, - false } 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 @@ -473,6 +456,9 @@ local function make_game_toolbelt() ) end + local a = win.left + #toolbelt_options * (size + padding) + log(a) + 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, @@ -517,9 +503,8 @@ 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)) + win.scene:replace("cursor", make_hex_cursor(0, COLORS.TRANSPARENT):tag"cursor") end end @@ -531,48 +516,32 @@ local function make_game_toolbelt() else select_tower_type(nil) - - if i == 11 then - log('hi') - end end end return toolbelt end --- this is a stupid name, it just returns a scene node group of hexagons in a hexagonal shape centered at 0,0, of size |radius| --- |color_f| can be a function that takes a hex and returns a color, or just a color --- optionally, |action_f| is a function that operates on the group node every frame -function make_hex_cursor(radius, color_f, action_f) - local color = type(color_f) == "userdata" and color_f or nil - local map = hex_spiral_map(vec2(0), radius) - local group = am.group() +local function game_scene() + local score = + am.translate(win.left + 10, win.top - 20) + ^ am.text("", "left"):tag"score" - for _,h in pairs(map) do - local hexagon = am.circle(hex_to_pixel(h, vec2(HEX_SIZE)), HEX_SIZE, color or color_f(h), 6) - group:append(hexagon) - end + local money = + am.translate(win.left + 10, win.top - 40) + ^ am.text("", "left"):tag"money" - if action_f then - group:action(action_f) - end + local wave_timer = + am.translate(0, win.top - 20) + ^ am.text(get_wave_timer_text()):tag"wave_timer" - return group:tag"cursor" -end + local top_right_display = + am.translate(win.right - 10, win.top - 20) + ^ am.text("", "right", "top"):tag"top_right_display" -function game_scene() - local score = am.translate(win.left + 10, win.top - 20) - ^ am.text("", "left"):tag"score" - - local money = am.translate(win.left + 10, win.top - 40) - ^ am.text("", "left"):tag"money" - - local wave_timer = am.translate(0, win.top - 20) - ^ am.text(get_wave_timer_text()):tag"wave_timer" - - local top_right_display = am.translate(win.right - 10, win.top - 20) - ^ am.text("", "right", "top"):tag"top_right_display" + local bottom_right_display = + am.translate(win.right - 10, win.bottom + win.height * 0.07 + 20) + ^ am.text("", "right", "bottom"):tag"bottom_right_display" local curtain = am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRUE_BLACK) curtain:action(coroutine.create(function() @@ -581,25 +550,43 @@ function game_scene() return true end)) - local scene = am.group{ + local scene = am.group( am.scale(1):tag"world_scale" ^ state.world, - am.translate(HEX_GRID_CENTER):tag"cursor_translate" ^ make_hex_cursor(0, COLORS.TRANSPARENT), + am.translate(HEX_GRID_CENTER):tag"cursor_translate" ^ make_hex_cursor(0, COLORS.TRANSPARENT):tag"cursor", score, money, wave_timer, top_right_display, make_game_toolbelt(), - curtain, - }:tag"game" + curtain + ) + :tag"game" scene:action(game_action) return scene end +function update_score(diff) state.score = state.score + diff end +function update_money(diff) state.money = state.money + diff end + +function game_end() + state = {} + game = false + -- @TODO anything +end + +function game_save() + am.save_state("save", game_serialize(), "json") + alert("succesfully saved!") +end + function game_init(saved_state) if saved_state then state = game_deserialize(saved_state) + + -- @HACK fixes a bug where loading game state with a tower type selected, but you don't have a built tower cursor node, so hovering a buildable tile throws an error + select_tower_type(nil) else state = get_initial_game_state() local home_tower = build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR) @@ -614,3 +601,23 @@ function game_init(saved_state) win.scene:append(game_scene()) end +-- this is a stupid name, it just returns a scene node group of hexagons in a hexagonal shape centered at 0,0, of size |radius| +-- |color_f| can be a function that takes a hex and returns a color, or just a color +-- optionally, |action_f| is a function that operates on the group node every frame +function make_hex_cursor(radius, color_f, action_f) + local color = type(color_f) == "userdata" and color_f or nil + local map = hex_spiral_map(vec2(0), radius) + local group = am.group() + + for _,h in pairs(map) do + local hexagon = am.circle(hex_to_pixel(h, vec2(HEX_SIZE)), HEX_SIZE, color or color_f(h), 6) + group:append(hexagon) + end + + if action_f then + group:action(action_f) + end + + return group +end + diff --git a/src/grid.lua b/src/grid.lua index 755c75d..2b53b8c 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -6,6 +6,7 @@ do -- the size of the grid should basically always be constant (i think), -- but different aspect ratios complicate this in an annoying way + -- grid width should be ~== height * 3 / 2 HEX_GRID_WIDTH = 33 + padding HEX_GRID_HEIGHT = 23 + padding @@ -215,6 +216,7 @@ 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/mob.lua b/src/mob.lua index 539d6ff..0303872 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -14,7 +14,7 @@ MOB_SPECS = { [MOB_TYPE.BEEPER] = { health = 30, speed = 8, - bounty = 15, + bounty = 5, hurtbox_radius = MOB_SIZE/2 }, [MOB_TYPE.SPOODER] = { diff --git a/src/projectile.lua b/src/projectile.lua index 0b0338b..d461fb9 100644 --- a/src/projectile.lua +++ b/src/projectile.lua @@ -227,7 +227,7 @@ end function projectile_deserialize(json_string) local projectile = entity_basic_json_parse(json_string) - projectile.vector = vec2(projectile.vector[0], projectile.vector[1]) + projectile.vector = vec2(projectile.vector[1], projectile.vector[2]) projectile.update = get_projectile_update_function(projectile.type) projectile.node = am.translate(projectile.position) diff --git a/src/tower.lua b/src/tower.lua index 12393c3..d46102d 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -35,7 +35,7 @@ TOWER_SPECS = { texture = TEXTURES.TOWER_HOWITZER, icon_texture = TEXTURES.TOWER_HOWITZER_ICON, cost = 20, - range = 10, + range = 6, fire_rate = 4, size = 0, height = 1, @@ -47,7 +47,7 @@ TOWER_SPECS = { texture = TEXTURES.TOWER_REDEYE, icon_texture = TEXTURES.TOWER_REDEYE_ICON, cost = 20, - range = 12, + range = 8, fire_rate = 1, size = 0, height = 1, @@ -83,7 +83,7 @@ TOWER_SPECS = { texture = TEXTURES.TOWER_LIGHTHOUSE, icon_texture = TEXTURES.TOWER_LIGHTHOUSE_ICON, cost = 20, - range = 8, + range = 7, fire_rate = 1, size = 0, height = 1, @@ -220,7 +220,7 @@ end function tower_serialize(tower) local serialized = entity_basic_devectored_copy(tower) - for i,h in pairs(serialized.hexes) do + for i,h in pairs(tower.hexes) do serialized.hexes[i] = { h.x, h.y } end