diff --git a/res/Sprite-0001.aseprite b/res/Sprite-0001.aseprite index 557cbdb..55a59b0 100644 Binary files a/res/Sprite-0001.aseprite and b/res/Sprite-0001.aseprite differ diff --git a/res/gem1.png b/res/gem1.png new file mode 100644 index 0000000..38a0b2d Binary files /dev/null and b/res/gem1.png differ diff --git a/res/logo.png b/res/logo.png index b8f50e5..1ea6567 100644 Binary files a/res/logo.png and b/res/logo.png differ diff --git a/src/game.lua b/src/game.lua index baa985e..2097a7e 100644 --- a/src/game.lua +++ b/src/game.lua @@ -12,29 +12,33 @@ function update_money(diff) state.money = state.money + diff end -- top right display types local TRDTS = { - NOTHING = 0, - CENTERED_EVENQ = 1, - EVENQ = 2, - HEX = 3, - PLATFORM = 4, - PERF = 5, - SEED = 6, - TILE = 7, + NOTHING = 0, + CENTERED_EVENQ = 1, + EVENQ = 2, + HEX = 3, + PLATFORM = 4, + PERF = 5, + SEED = 6, + TILE = 7, } local function get_initial_game_state(seed) local STARTING_MONEY = 10000 - local map, world = random_map(seed) + -- 2014 + local map, world = random_map(2014) return { map = map, -- map of hex coords map[x][y] to some stuff at that location world = world, -- the root scene graph node for the game 'world' + ui = nil, -- unused, root scene graph node for the 'ui' stuff perf = {}, -- result of call to am.perf_stats, called every frame - time = 0, -- time since game started in seconds + time = 0, -- real time since game started in seconds score = 0, -- current game score money = STARTING_MONEY, -- current money + current_wave = 0, + selected_tower_type = false, selected_toolbelt_button = 9, selected_top_right_display_type = TRDTS.SEED, @@ -67,10 +71,6 @@ local function get_top_right_display_text(hex, evenq, centered_evenq, display_ty return str end -local function handle_left_click(hex, tile, tower_type, toolbelt_button) - -end - -- initialized later, as part of the init of the toolbelt function select_tower_type(tower_type) end @@ -84,27 +84,29 @@ function select_toolbelt_button(i) end function do_day_night_cycle() - local tstep = (math.sin(state.time / 100) + 1) * am.delta_time + local tstep = (math.sin(state.time * am.delta_time) + 1) / 100 state.world"negative_mask".color = vec4(tstep){a=1} end local function game_pause() - WIN.scene"game".paused = true - WIN.scene"game":append(am.group{ + WIN.scene("game").paused = true + + WIN.scene("game"):append(am.group{ am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRANSPARENT), am.scale(3) ^ am.text(string.format( "Paused.\nSeed: %d\nEscape to Resume\nf4 to start a new game", state.map.seed - ), COLORS.BLACK) + ), COLORS.BLACK), } :tag"pause_menu") + WIN.scene:action(function() - if WIN:key_pressed"escape" then - WIN.scene:remove"pause_menu" - WIN.scene"game".paused = false + if WIN:key_pressed("escape") then + WIN.scene:remove("pause_menu") + WIN.scene("game").paused = false return true - elseif WIN:key_pressed"f4" then + elseif WIN:key_pressed("f4") then game_end() return true end @@ -160,11 +162,19 @@ local function game_action(scene) end end + if WIN:mouse_pressed"middle" then + state.world"scale".scale2d = vec2(1) + + else + local mwd = WIN:mouse_wheel_delta() + state.world"scale".scale = state.world"scale".scale + vec3(mwd, 0) / 100 + end + if WIN:key_pressed"escape" then game_pause() elseif WIN:key_pressed"f1" then - state.top_right_display_type = (state.top_right_display_type + 1) % #table.keys(TRDTS) + state.selected_top_right_display_type = (state.selected_top_right_display_type + 1) % #table.keys(TRDTS) elseif WIN:key_pressed"f2" then state.world"flow_field".hidden = not state.world"flow_field".hidden @@ -219,7 +229,7 @@ local function make_game_toolbelt() register_button_widget("toolbelt_tower_button" .. i , am.rect(x1, y1, x2, y2) - , function() select_tower_type(i) end) + , function() select_toolbelt_button(i) end) return am.translate(vec2(size + padding, 0) * i + offset) ^ am.group{ @@ -332,6 +342,7 @@ local function make_game_toolbelt() WIN.scene:action(am.play(am.sfxr_synth(SOUNDS.SELECT1), false, 1, SFX_VOLUME)) else + -- de-selecting currently selected tower if any toolbelt("tower_select_square").hidden = true WIN.scene:replace("cursor", make_hex_cursor(0, COLORS.TRANSPARENT)) @@ -394,9 +405,9 @@ end function game_init() state = get_initial_game_state() - build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR) - -- hack to make the center tile passable even though there's a tower on it + build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR) + -- @HACK to make the center tile passable even though there's a tower on it state.map.get(HEX_GRID_CENTER.x, HEX_GRID_CENTER.y).elevation = 0 WIN.scene:remove("game") diff --git a/src/grid.lua b/src/grid.lua index df970ca..7d7e25c 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -144,10 +144,10 @@ function random_map(seed) -- the world's appearance relies largely on a backdrop which can be scaled in -- tone to give the appearance of light or darkness -- @NOTE replace this with a shader program + -- interestingly, if it's colored white, it almost gives the impression of a winter biome local neg_mask = am.rect(0, 0, HEX_GRID_PIXEL_WIDTH, HEX_GRID_PIXEL_HEIGHT, COLORS.TRUE_BLACK):tag"negative_mask" local world = am.group(neg_mask):tag"world" - local edge_nodes = {} for i,_ in pairs(map) do for j,noise in pairs(map[i]) do local evenq = hex_to_evenq(vec2(i, j)) @@ -157,9 +157,6 @@ function random_map(seed) -- if we're on an edge -- terraform edges to be passable noise = 0 - -- it's nice to also store the coords for edge nodes in a separate table for later - table.insert(edge_nodes, vec2(i, j)) - elseif j == HEX_GRID_CENTER.y and i == HEX_GRID_CENTER.x then -- also terraform the center of the grid to be passable -- very infrequently, but still sometimes it is not medium elevation diff --git a/src/hexyz.lua b/src/hexyz.lua index 776d62b..df93d3b 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -292,9 +292,9 @@ function parallelogram_map(width, height, seed) local seed = seed or math.random(width * height) local map = {} - for i = 0, width do + for i = 0, width - 1 do map[i] = {} - for j = 0, height do + for j = 0, height - 1 do -- Calculate Noise local idelta = i / width @@ -408,6 +408,7 @@ function hexagonal_map(radius, seed) end -- Returns Unordered Rectangular Map of |width| and |height| with Simplex Noise +-- @TODO - this doesn't work for pointy orientations function rectangular_map(width, height, seed) local seed = seed or math.random(width * height) diff --git a/src/mob.lua b/src/mob.lua index 9763d52..044bd42 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -86,7 +86,9 @@ local function get_spawn_hex() end -- @NOTE negate 'y' because hexyz algorithms assume south is positive, in amulet north is positive - return evenq_to_hex(vec2(x, -y)) + local hex = evenq_to_hex(vec2(x, -y)) + + return hex end local function update_mob(mob, mob_index) @@ -198,7 +200,7 @@ local function make_and_register_mob(mob_type) 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 diff --git a/src/projectile.lua b/src/projectile.lua index d7e6ec6..63f76e7 100644 --- a/src/projectile.lua +++ b/src/projectile.lua @@ -7,11 +7,11 @@ PROJECTILE_TYPE = { LASER = 2, } -PROJECTILE_SPECS = { +local PROJECTILE_SPECS = { [PROJECTILE_TYPE.SHELL] = { velocity = 13, damage = 20, - hitbox_radius = 3 + hitbox_radius = 20 }, [PROJECTILE_TYPE.LASER] = { velocity = 25, @@ -20,7 +20,6 @@ PROJECTILE_SPECS = { }, } - function get_projectile_velocity(projectile_type) return PROJECTILE_SPECS[projectile_type].velocity end @@ -34,17 +33,41 @@ function get_projectile_spec(projectile_type) return PROJECTILE_SPECS[projectile_type] end +local function make_shell_explosion_node(source_position) + return am.particles2d{ + source_pos = source_position, + source_pos_var = vec2(4), + start_size = 2, + start_size_var = 1, + end_size = 0, + end_size_var = 0, + angle = 0, + angle_var = math.pi, + speed = 25, + speed_var = 15, + life = 10, + life_var = 1, + start_color = COLORS.VERY_DARK_GRAY, + start_color_var = vec4(0.2), + end_color = vec4(0), + end_color_var = vec4(0.1), + emission_rate = 100, + start_particles = 150, + max_particles = 150, + gravity = vec2(0, -10), + warmup_time = 1 + } + :action(coroutine.create(function(self) + am.wait(am.delay(3)) + end)) +end + local function update_projectile_shell(projectile, projectile_index) - projectile.position = projectile.position + projectile.vector * projectile.velocity + projectile.position = projectile.position + projectile.vector * projectile.velocity projectile.node.position2d = projectile.position - projectile.hex = pixel_to_hex(projectile.position) - projectile.props.z = projectile.props.z - 0.6 * am.delta_time + projectile.hex = pixel_to_hex(projectile.position) - if projectile.props.z <= 0 then - log('exploded cuz we hit da grund') - delete_entity(PROJECTILES, projectile_index) - return true - end + projectile.props.z = projectile.props.z - 0.6 * am.delta_time -- check if we hit something -- get a list of hexes that could have something we could hit on them @@ -52,30 +75,49 @@ local function update_projectile_shell(projectile, projectile_index) -- this is done to avoid having to check every mob on screen, though maybe it's not necessary. local do_explode = false local search_hexes = spiral_map(projectile.hex, 1) + local mobs = {} for _,hex in pairs(search_hexes) do - for mob_index,mob in pairs(mobs_on_hex(hex)) do - if mob and circles_intersect(mob.position - , projectile.position - , mob.hurtbox_radius - , projectile.hitbox_radius) then - do_explode = true - break + if mob then + table.insert(mobs, mob_index, mob) + + if circles_intersect(mob.position + , projectile.position + , mob.hurtbox_radius + , projectile.hitbox_radius) then + log('exploded cuz we hit a boi') + do_explode = true + -- we don't break here because if we hit a mob we have to collect all the mobs on the hexes in the search space anyway + end end end end + local tile = state.map.get(projectile.hex.x, projectile.hex.y) + if tile and tile.elevation >= projectile.props.z then + log('we exploded cuz we hit something toll') + do_explode = true + + elseif projectile.props.z <= 0 then + log('exploded cuz we hit da grund') + do_explode = true + end + if do_explode then - log('exploded cuz we hit a boi') + log(#mobs) + for index,mob in pairs(mobs) do + do_hit_mob(mob, 1 / math.distance(mob.position, projectile.position) * projectile.damage, index) + end + WIN.scene:append(make_shell_explosion_node(projectile.position)) delete_entity(PROJECTILES, projectile_index) return true end end local function update_projectile_laser(projectile, projectile_index) - projectile.position = projectile.position + projectile.vector * projectile.velocity + projectile.position = projectile.position + projectile.vector * projectile.velocity projectile.node.position2d = projectile.position - projectile.hex = pixel_to_hex(projectile.position) + projectile.hex = pixel_to_hex(projectile.position) -- check if we're out of bounds if not point_in_rect(projectile.position + WORLDSPACE_COORDINATE_OFFSET, { diff --git a/src/tower.lua b/src/tower.lua index 7e2c157..10bb275 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -321,7 +321,10 @@ function update_tower_howitzer(tower, tower_index) math.normalize(mob.position - tower.position) ) - projectile.props.z = tower.props.z + -- @HACK, the projectile will explode if it encounters something taller than it, + -- but the tower it spawns on quickly becomes taller than it, so we just pad it + -- if it's not enough the shell explodes before it leaves its spawning hex + projectile.props.z = tower.props.z + 0.1 tower.last_shot_time = state.time play_sfx(SOUNDS.EXPLOSION2) diff --git a/texture.lua b/texture.lua index 6566d4b..831fdea 100644 --- a/texture.lua +++ b/texture.lua @@ -11,6 +11,7 @@ end TEXTURES = { LOGO = load_texture("res/logo.png"), + GEM1 = load_texture("res/gem1.png"), BUTTON1 = load_texture("res/button1.png"), WIDER_BUTTON1 = load_texture("res/wider_button1.png"),