diff --git a/res/mob1_1.png b/res/mob1_1.png new file mode 100644 index 0000000..7c3c8a9 Binary files /dev/null and b/res/mob1_1.png differ diff --git a/src/color.lua b/src/color.lua new file mode 100644 index 0000000..74ae63f --- /dev/null +++ b/src/color.lua @@ -0,0 +1,26 @@ + + +COLORS = { + TRANSPARENT = vec4(0.4), + + -- tones + WHITE = vec4(0.8, 0.8, 0.7, 1), + BLACK = vec4(0, 0, 0, 1), + TRUEBLACK = vec4(0, 0, 0, 1), + + -- hues + BLUE_STONE = vec4(0.12, 0.3, 0.3, 1), + MYRTLE = vec4(0.10, 0.25, 0.10, 1), + BROWN_POD = vec4(0.25, 0.20, 0.10, 1), + BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1), + MAGENTA = vec4(1, 0, 1, 1), + TEAL = vec4(16/255, 126/255, 124/244, 1), + YALE_BLUE = vec4(4/255, 75/255, 127/255, 1), + OLIVE = vec4(111/255, 124/254, 18/255, 1), + LIGHT_CYAN = vec4(224/255, 251/255, 252/255, 1), + PALE_SILVER = vec4(193/255, 178/255, 171/255, 1), + CLARET = vec4(139/255, 30/255, 63/255, 1), + BISTRO = vec4(73/255, 44/255, 29/255, 1), + DEEP_SPACE_SPARKLE = vec4(61/255, 90/255, 108/255, 1) +} + diff --git a/src/colors.lua b/src/colors.lua deleted file mode 100644 index 5b22f72..0000000 --- a/src/colors.lua +++ /dev/null @@ -1,17 +0,0 @@ - -COLORS = { - TRANSPARENT = vec4(0.4), - - -- tones - WHITE = vec4(0.8, 0.8, 0.7, 1), - BLACK = vec4(0, 0, 0, 1), - TRUEBLACK = vec4(0, 0, 0, 1), - - -- hues - BLUE_STONE = vec4(0.12, 0.3, 0.3, 1), - MYRTLE = vec4(0.10, 0.25, 0.10, 1), - BROWN_POD = vec4(0.25, 0.20, 0.10, 1), - BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1), - MAGENTA = vec4(1, 0, 1, 1) -} - diff --git a/src/grid.lua b/src/grid.lua index 41cfde6..23d2d09 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -1,6 +1,5 @@ -require "colors" -require "gui" +require "hexyz" HEX_SIZE = 20 HEX_GRID_WIDTH = 65 -- 65 @@ -79,6 +78,7 @@ function random_map(seed, do_seed_rng) -- the center of the map in some radius is always considered 'passable' terrain and is home base -- terraform this area to ensure it's passable -- @NOTE no idea why the y-coord doesn't need to be transformed + -- @TODO @FIXME also terraform the edges of the map to be passable - it is theoretically possible to get maps where mobs can be stuck from the very beginning local home = spiral_map(HEX_GRID_CENTER, 3) for _,hex in pairs(home) do map[hex.x][hex.y].elevation = 0 diff --git a/src/hexyz.lua b/src/hexyz.lua index be46bc2..d98f6f9 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -170,12 +170,44 @@ function hex_corners(hex, size, orientation) return corners end +-- @TODO test +function hex_to_oddr(hex) + local z = -hex.x - hex.y + return vec2(hex.x + (z - (z % 2)) / 2) +end + +-- @TODO test +function oddr_to_hex(oddr) + return vec2(hex.x - (hex.y - (hex.y % 2)) / 2, -hex.x - hex.y) +end + +-- @TODO test +function hex_to_evenr(hex) + local z = -hex.x - hex.y + return vec2(hex.x + (z + (z % 2)) / 2, z) +end + +-- @TODO test +function evenr_to_hex(evenr) + return vec2(hex.x - (hex.y + (hex.y % 2)) / 2, -hex.x - hex.y) +end + +-- @TODO test +function hex_to_oddq(hex) + return vec2(hex.x, -hex.x - hex.y + (hex.x - (hex.x % 2)) / 2) +end + +-- @TODO test +function oddq_to_hex(oddq) + return vec2(hex.x, -hex.x - (hex.y - (hex.x - (hex.y % 2)) / 2)) +end + function hex_to_evenq(hex) return vec2(hex.x, (-hex.x - hex.y) + (hex.x + (hex.x % 2)) / 2) end -function evenq_to_hex(off) - return vec2(off.x, -off.x - (off.y - (off.x + (off.x % 2)) / 2)) +function evenq_to_hex(evenq) + return vec2(evenq.x, -evenq.x - (evenq.y - (evenq.x + (evenq.x % 2)) / 2)) end --============================================================================ @@ -404,19 +436,15 @@ function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f) end for _,next_ in pairs(neighbour_f(current.hex)) do - local entry = map.get(next_.x, next_.y) - - if entry then - local new_cost = map_get(cost_so_far, current.hex.x, current.hex.y) - + cost_f(entry) - - local next_cost = map_get(cost_so_far, next_.x, next_.y) - if not next_cost or new_cost < next_cost then - map_set(cost_so_far, next_.x, next_.y, new_cost) - local priority = new_cost + heuristic_f(goal, next_) - table.insert(frontier, { hex = next_, priority = priority }) - map_set(came_from, next_.x, next_.y, current) - end + local new_cost = map_get(cost_so_far, current.hex.x, current.hex.y) + + cost_f(next_) + local next_cost = map_get(cost_so_far, next_.x, next_.y) + + if not next_cost or new_cost < next_cost then + map_set(cost_so_far, next_.x, next_.y, new_cost) + local priority = new_cost + heuristic_f(goal, next_) + table.insert(frontier, { hex = next_, priority = priority }) + map_set(came_from, next_.x, next_.y, current) end end end diff --git a/src/main.lua b/src/main.lua index 8468b63..3ed8f77 100644 --- a/src/main.lua +++ b/src/main.lua @@ -3,34 +3,21 @@ math.randomseed(os.time()); math.random(); math.random(); math.random() --============================================================================ -- Imports -require "hexyz" +require "color" require "grid" require "mob" -require "util" +require "math" +require "table" + --============================================================================ -- Globals +TIME = 0 win = am.window{ width = 1920, height = 1080 } -function mask() - return am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRANSPARENT):tag"mask" -end - -function get_menu_for_tile(x, y, tile) - local pos = hex_to_pixel(vec2(x, y)) + WORLDSPACE_COORDINATE_OFFSET - return am.translate(pos) ^ am.group{ - am.rect(-50, -50, 50, 50, COLORS.TRANSPARENT), - make_button_widget(x .. y, pos, vec2(100, 37), "close") - } -end - -function invoke_tile_menu(x, y, tile) - win.scene:append(get_menu_for_tile(x, y, tile)) -end - function game_action(scene) - local time = am.current_time() + TIME = am.current_time() local mouse = win:mouse_position() local hex = pixel_to_hex(mouse - WORLDSPACE_COORDINATE_OFFSET) @@ -44,18 +31,12 @@ function game_action(scene) if win:key_pressed"f1" then end - for wid,widget in pairs(get_widgets()) do - if widget.poll() then - log('we clicked button with id %s!', wid) - end - end - do_mob_updates() do_mob_spawning() -- draw stuff win.scene"hex_cursor".center = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET - win.scene"score".text = string.format("SCORE: %.2f", time) + win.scene"score".text = string.format("SCORE: %.2f", TIME) win.scene"coords".text = string.format("%d,%d", hex.x, hex.y) end @@ -79,7 +60,7 @@ function game_scene() curtain, hex_cursor, score, - coords, + coords } scene:action(game_action) @@ -88,6 +69,8 @@ function game_scene() end function init() + require "texture" + load_textures() win.scene = am.scale(1) ^ game_scene() end diff --git a/src/math.lua b/src/math.lua new file mode 100644 index 0000000..aac0697 --- /dev/null +++ b/src/math.lua @@ -0,0 +1,9 @@ + +function math.wrapf(float, range) + return float - range * math.floor(float / range) +end + +function math.lerpv2(v1, v2, t) + return v1 * t + v2 * (1 - t) +end + diff --git a/src/mob.lua b/src/mob.lua index 4eab791..10ef5e0 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -1,5 +1,47 @@ + MOBS = {} +--[[ + mob structure: + { + position - vec2 -- true pixel coordinates + TOB - float -- time stamp in seconds of when the mob when spawned + node - node -- the root graph node for this mob + hex - vec2 -- hexagon the mob is on top of + update - function -- the function that gets called every frame + } +]] + +require "sound" +require "util" + +MOB_UPDATES = { + BEEPER = function(mob, index) + mob.hex = pixel_to_hex(mob.position - WORLDSPACE_COORDINATE_OFFSET) + + local frame_target = map_get(mob.path, mob.hex.x, mob.hex.y) + + if frame_target then + mob.position = math.lerpv2(mob.position, hex_to_pixel(frame_target.hex) + WORLDSPACE_COORDINATE_OFFSET, 0.91) + mob.node.position2d = mob.position + + -- can't find path, or dead + else + win.scene:action(am.play(am.sfxr_synth(SOUNDS.EXPLOSION1), false, math.random() + 0.5)) + + local i,v = table.find(MOBS, function(_mob) return _mob == mob end) + table.remove(MOBS, index) + win.scene:remove(mob.node) + end + + -- passive animation + if math.random() < 0.01 then + mob.node"rotate":action(am.tween(0.3, { angle = mob.node"rotate".angle + math.pi*3 })) + else + mob.node"rotate".angle = math.wrapf(mob.node"rotate".angle + am.delta_time, math.pi*2) + end + end +} -- check if a the tile at |hex| is passable by |mob| function can_pass_through(mob, hex) @@ -7,10 +49,7 @@ function can_pass_through(mob, hex) return tile and tile.elevation < 0.5 and tile.elevation > -0.5 end -function get_movement_cost(mob, start_hex, goal_hex) - return 1 -end - +-- @FIXME there's a bug here where the position of the spawn hex is sometimes 1 closer to the center than we want function get_spawn_hex(mob) local spawn_hex repeat @@ -40,16 +79,15 @@ function get_spawn_hex(mob) return spawn_hex end --- @NOTE spawn hex +-- function make_mob() local mob = {} - local spawn_hex = get_spawn_hex(mob) - local spawn_position = hex_to_pixel(spawn_hex) + WORLDSPACE_COORDINATE_OFFSET - - mob.position = spawn_position - mob.hex = spawn_hex - mob.path = Astar(HEX_MAP, HEX_GRID_CENTER, spawn_hex, + mob.TOB = TIME + mob.update = MOB_UPDATES.BEEPER + mob.hex = get_spawn_hex(mob) + mob.position = hex_to_pixel(mob.hex) + WORLDSPACE_COORDINATE_OFFSET + mob.path = Astar(HEX_MAP, HEX_GRID_CENTER, mob.hex, -- neighbour function function(hex) @@ -64,39 +102,31 @@ function make_mob() end, -- cost function - function(map_entry) - return math.abs(map_entry.elevation) + function(hex) + return math.abs(HEX_MAP.get(hex.x, hex.y).elevation) end ) - mob.sprite = am.circle(spawn_position, 18, COLORS.WHITE, 4) - win.scene:append(mob.sprite) + mob.node = am.translate(mob.position) + ^ am.scale(2) + ^ am.rotate(mob.TOB) + ^ pack_texture_into_sprite(TEX_MOB1_1, 20, 20) + + win.scene:append(mob.node) return mob end -local SPAWN_CHANCE = 25 +local SPAWN_CHANCE = 50 function do_mob_spawning() - if win:key_pressed"space" then - --if math.random(SPAWN_CHANCE) == 1 then + if math.random(SPAWN_CHANCE) == 1 then table.insert(MOBS, make_mob()) end end function do_mob_updates() - --if win:key_pressed"a" then - for _,mob in pairs(MOBS) do - mob.hex = pixel_to_hex(mob.position - WORLDSPACE_COORDINATE_OFFSET) - - local frame_target = map_get(mob.path, mob.hex.x, mob.hex.y) - - if frame_target then - mob.position = lerp(mob.position, hex_to_pixel(frame_target.hex) + WORLDSPACE_COORDINATE_OFFSET, 0.9) - mob.sprite.center = mob.position - else - - end + for i,mob in pairs(MOBS) do + mob.update(mob, i) end - --end end diff --git a/src/sound.lua b/src/sound.lua new file mode 100644 index 0000000..29ae28c --- /dev/null +++ b/src/sound.lua @@ -0,0 +1,14 @@ + +SOUNDS = { + EXPLOSION1 = 49179102, -- this slowed sounds metal as fuck + EXPLOSION2 = 19725402, + LASER1 = 79859301, + PUSH1 = 30455908, + BIRD1 = 50838307, + RANDOM1 = 85363309, + RANDOM2 = 15482409, + RANDOM3 = 58658009, + RANDOM4 = 89884209, + RANDOM5 = 36680709 +} + diff --git a/src/table.lua b/src/table.lua new file mode 100644 index 0000000..81c7e0b --- /dev/null +++ b/src/table.lua @@ -0,0 +1,14 @@ + +function table.rchoice(t) + return t[math.floor(math.random() * #t) + 1] +end + +function table.find(t, predicate) + for i,v in pairs(t) do + if predicate(v) then + return i,v + end + end + return nil +end + diff --git a/src/texture.lua b/src/texture.lua new file mode 100644 index 0000000..db0de40 --- /dev/null +++ b/src/texture.lua @@ -0,0 +1,5 @@ + +function load_textures() + TEX_MOB1_1 = am.texture2d("../res/mob1_1.png") +end + diff --git a/src/util.lua b/src/util.lua index 972a16a..fd7e481 100644 --- a/src/util.lua +++ b/src/util.lua @@ -1,6 +1,10 @@ -function lerp(v1, v2, t) - return v1 * t + v2 * (1 - t) +function pack_texture_into_sprite(texture, width, height) + return am.sprite{ + texture = texture, + s1 = 0, s2 = 1, t1 = 0, t2 = 1, + x1 = 0, x2 = width, width = width, + y1 = 0, y2 = height, height = height + } end -