diff --git a/main.lua b/main.lua index 485bf27..41b7381 100644 --- a/main.lua +++ b/main.lua @@ -156,7 +156,7 @@ function make_main_scene_toolbelt() end end }, - { + false and { texture = TEXTURES.MAP_EDITOR_HEX, action = function() alert("not yet :)") end }, @@ -164,7 +164,7 @@ function make_main_scene_toolbelt() texture = TEXTURES.UNPAUSE_HEX, action = function() unpause(win.scene("menu")) end } or false, - { + false and { texture = TEXTURES.SETTINGS_HEX, action = function() alert("not yet :)") end }, @@ -261,11 +261,17 @@ function main_scene(do_backdrop, do_logo) ) end + -- version/author info group:append( am.translate(win.right - 10, win.bottom + 10) ^ am.text(string.format("v%s, by %s", version, author), COLORS.WHITE, "right", "bottom") ) + group:append( + am.translate(win.right - 30, win.top - 60) + ^ pack_texture_into_sprite(TEXTURES.SOUND_ON1, 40, 30) + ) + if do_logo then local position = vec2(0, win.top - 20 - TEXTURES.LOGO.height/2) local logo = diff --git a/res/Sprite-0001.aseprite b/res/Sprite-0001.aseprite index 14dfa04..28f64ed 100644 Binary files a/res/Sprite-0001.aseprite and b/res/Sprite-0001.aseprite differ diff --git a/res/Sprite-0002.aseprite b/res/Sprite-0002.aseprite index eed5b31..844b954 100644 Binary files a/res/Sprite-0002.aseprite and b/res/Sprite-0002.aseprite differ diff --git a/res/sound-off.png b/res/sound-off.png new file mode 100644 index 0000000..30bb7e2 Binary files /dev/null and b/res/sound-off.png differ diff --git a/res/sound-on.png b/res/sound-on.png new file mode 100644 index 0000000..d9cd38d Binary files /dev/null and b/res/sound-on.png differ diff --git a/res/tower_gattler_icon.png b/res/tower_gattler_icon.png index 1578dbe..fc51c91 100644 Binary files a/res/tower_gattler_icon.png and b/res/tower_gattler_icon.png differ diff --git a/res/cannon1.png b/res/tower_howitzer.png similarity index 100% rename from res/cannon1.png rename to res/tower_howitzer.png diff --git a/res/tower_howitzer_icon-export.png b/res/tower_howitzer_icon-export.png new file mode 100644 index 0000000..9e6417f Binary files /dev/null and b/res/tower_howitzer_icon-export.png differ diff --git a/res/tower_howitzer_icon.png b/res/tower_howitzer_icon.png index 3e914a3..9e6417f 100644 Binary files a/res/tower_howitzer_icon.png and b/res/tower_howitzer_icon.png differ diff --git a/src/entity.lua b/src/entity.lua index 2961f5e..5f1e1b0 100644 --- a/src/entity.lua +++ b/src/entity.lua @@ -1,5 +1,4 @@ - --[[ entity structure: { diff --git a/src/extra.lua b/src/extra.lua index 491c7d0..2214fd4 100644 --- a/src/extra.lua +++ b/src/extra.lua @@ -1,5 +1,4 @@ - -- @TODO make it work with functions that return multiple values -- right now it discards returned values beyond the first function fprofile(f, ...) diff --git a/src/game.lua b/src/game.lua index 25444ff..94935ab 100644 --- a/src/game.lua +++ b/src/game.lua @@ -408,9 +408,9 @@ local function make_game_toolbelt() am.translate(vec2(half_size)) ^ am.group( - pack_texture_into_sprite(TEXTURES.BUTTON1, half_size, half_size), + pack_texture_into_sprite(TEXTURES.BUTTON1, half_size, half_size, vec4(0.4, 0.4, 0.4, 1)), am.scale(2) - ^ am.text(keys[i], COLORS.BLACK) + ^ am.text(keys[i], COLORS.WHITE) ) ) @@ -663,11 +663,6 @@ function game_init(saved_state) select_tower_type(nil) else state = get_initial_game_state() - local home_tower = build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR) - for _,h in pairs(home_tower.hexes) do - -- @HACK to make the center tile(s) passable even though there's a tower on it - hex_map_get(state.map, h).elevation = 0 - end end game = true diff --git a/src/hexyz.lua b/src/hexyz.lua index 2bedf07..1430f6d 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -8,9 +8,6 @@ -- and some utility functions not present in your standard lua, like: -- table.append - - - -- @TODO if not table.append then end if not table.filter then end diff --git a/src/projectile.lua b/src/projectile.lua index df5b0bf..7472c4d 100644 --- a/src/projectile.lua +++ b/src/projectile.lua @@ -2,6 +2,7 @@ PROJECTILE_TYPE = { SHELL = 1, LASER = 2, + BULLET = 3, } local PROJECTILE_SPECS = { @@ -15,6 +16,11 @@ local PROJECTILE_SPECS = { damage = 20, hitbox_radius = 20 }, + [PROJECTILE_TYPE.BULLET] = { + velocity = 25, + damage = 5, + hitbox_radius = 10 + } } function get_projectile_velocity(projectile_type) @@ -174,7 +180,59 @@ local function update_projectile_laser(projectile, projectile_index) -- hit the mob, affect the world do_hit_mob(closest_mob, projectile.damage, closest_mob_index) - -- delete_entity(state.projectiles, projectile_index) -- laser doesn't delete itself on mob hit + vplay_sfx(SOUNDS.HIT1, 0.5) +end + +local function update_projectile_bullet(projectile, projectile_index) + projectile.position = projectile.position + projectile.vector * projectile.velocity + + if not point_in_rect(projectile.position + WORLDSPACE_COORDINATE_OFFSET, { + x1 = win.left, + y1 = win.bottom, + x2 = win.right, + y2 = win.top + }) then + delete_entity(state.projectiles, projectile_index) + return true + end + + projectile.node.position2d = projectile.position + projectile.hex = pixel_to_hex(projectile.position, vec2(HEX_SIZE)) + + local search_hexes = hex_spiral_map(projectile.hex, 1) + local hit_mob_count = 0 + local hit_mobs = {} + for _,hex in pairs(search_hexes) do + + -- check if there's a mob on the hex + 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 + table.insert(hit_mobs, mob_index, mob) + hit_mob_count = hit_mob_count + 1 + end + end + end + + -- we didn't hit anyone + if hit_mob_count == 0 then return end + + -- we could have hit multiple, (optionally) find the closest + local closest_mob_index, closest_mob = next(hit_mobs, nil) + local closest_d = math.distance(closest_mob.position, projectile.position) + for _mob_index,mob in pairs(hit_mobs) do + local d = math.distance(mob.position, projectile.position) + if d < closest_d then + closest_mob_index = _mob_index + closest_mob = mob + closest_d = d + end + end + + -- hit the mob, affect the world + do_hit_mob(closest_mob, projectile.damage, closest_mob_index) vplay_sfx(SOUNDS.HIT1, 0.5) end @@ -182,6 +240,9 @@ function make_projectile_node(projectile_type, vector) if projectile_type == PROJECTILE_TYPE.LASER then return am.line(vector, vector*get_projectile_hitbox_radius(projectile_type), 3, COLORS.CLARET) + elseif projectile_type == PROJECTILE_TYPE.BULLET then + return am.circle(vec2(0), 2, COLORS.VERY_DARK_GRAY) + elseif projectile_type == PROJECTILE_TYPE.SHELL then return am.circle(vec2(0), 3, COLORS.VERY_DARK_GRAY) end @@ -191,6 +252,9 @@ function get_projectile_update_function(projectile_type) if projectile_type == PROJECTILE_TYPE.LASER then return update_projectile_laser + elseif projectile_type == PROJECTILE_TYPE.BULLET then + return update_projectile_bullet + elseif projectile_type == PROJECTILE_TYPE.SHELL then return update_projectile_shell end diff --git a/src/tower.lua b/src/tower.lua index f686341..c3d1b8b 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -33,14 +33,14 @@ local TOWER_SPECS = { icon_texture = TEXTURES.TOWER_GATTLER_ICON, cost = 20, range = 4, - fire_rate = 10, + fire_rate = 0.5, size = 0, height = 1, }, [TOWER_TYPE.HOWITZER] = { name = "Howitzer", placement_rules_text = "Place on non-Water, non-Mountain", - short_description = "Fires area of effect artillery.", + short_description = "Medium-range, medium fire-rate area of effect artillery tower.", texture = TEXTURES.TOWER_HOWITZER, icon_texture = TEXTURES.TOWER_HOWITZER_ICON, cost = 50, @@ -52,19 +52,19 @@ local TOWER_SPECS = { [TOWER_TYPE.REDEYE] = { name = "Redeye", placement_rules_text = "Place on Mountains", - short_description = "Long-range, penetrating laser tower", + short_description = "Long-range, penetrating high-velocity laser tower.", texture = TEXTURES.TOWER_REDEYE, icon_texture = TEXTURES.TOWER_REDEYE_ICON, cost = 140, range = 9, - fire_rate = 1, + fire_rate = 3, size = 0, height = 1, }, [TOWER_TYPE.MOAT] = { name = "Moat", placement_rules_text = "Place on Ground", - short_description = "Restricts movement", + short_description = "Restricts movement, similar to water.", texture = TEXTURES.TOWER_MOAT, icon_texture = TEXTURES.TOWER_MOAT_ICON, cost = 10, @@ -139,13 +139,17 @@ function make_tower_node(tower_type) return make_tower_sprite(tower_type) elseif tower_type == TOWER_TYPE.GATTLER then - return make_tower_sprite(tower_type) + return am.group{ + am.circle(vec2(0), HEX_SIZE - 4, COLORS.VERY_DARK_GRAY, 5), + am.rotate(state.time or 0) + ^ pack_texture_into_sprite(TEXTURES.TOWER_HOWITZER, HEX_PIXEL_HEIGHT*1.5, HEX_PIXEL_WIDTH*2, COLORS.GREEN_YELLOW) + } elseif tower_type == TOWER_TYPE.HOWITZER then return am.group{ - am.circle(vec2(0), HEX_SIZE, COLORS.VERY_DARK_GRAY, 6), + am.circle(vec2(0), HEX_SIZE - 4, COLORS.VERY_DARK_GRAY, 6), am.rotate(state.time or 0) ^ am.group{ - pack_texture_into_sprite(TEXTURES.CANNON1, HEX_PIXEL_HEIGHT*1.5, HEX_PIXEL_WIDTH*2) -- CHONK + pack_texture_into_sprite(TEXTURES.TOWER_HOWITZER, HEX_PIXEL_HEIGHT*1.5, HEX_PIXEL_WIDTH*2) -- CHONK } } elseif tower_type == TOWER_TYPE.LIGHTHOUSE then @@ -217,18 +221,6 @@ do end end -local function get_tower_update_function(tower_type) - if tower_type == TOWER_TYPE.REDEYE then - return update_tower_redeye - - elseif tower_type == TOWER_TYPE.HOWITZER then - return update_tower_howitzer - - elseif tower_type == TOWER_TYPE.LIGHTHOUSE then - return update_tower_lighthouse - end -end - function tower_serialize(tower) local serialized = entity_basic_devectored_copy(tower) @@ -309,30 +301,13 @@ function tower_type_is_buildable_on(hex, tile, tower_type) local mobs_blocking = table.count(blocking_mobs) ~= 0 local blocked = mobs_blocking or towers_blocking - if tower_type == TOWER_TYPE.HOWITZER then - if not mobs_blocking and towers_blocking then - -- you can build howitzers on top of walls. - blocked = false - for _,tower in pairs(blocking_towers) do - if tower.type ~= TOWER_TYPE.WALL then - blocked = true - break - end - end - end + if tower_type == TOWER_TYPE.GATTLER then + return not (blocked or has_water or has_mountain) + + elseif tower_type == TOWER_TYPE.HOWITZER then return not (blocked or has_water or has_mountain) elseif tower_type == TOWER_TYPE.REDEYE then - if not mobs_blocking and towers_blocking then - -- you can build redeyes on top of walls - blocked = false - for _,tower in pairs(blocking_towers) do - if tower.type ~= TOWER_TYPE.WALL then - blocked = true - break - end - end - end return not blocked and not has_water and not has_ground @@ -361,7 +336,7 @@ function tower_type_is_buildable_on(hex, tile, tower_type) end end -function update_tower_redeye(tower, tower_index) +local function update_tower_redeye(tower, tower_index) if not tower.target_index then for index,mob in pairs(state.mobs) do if mob then @@ -391,7 +366,53 @@ function update_tower_redeye(tower, tower_index) end end -function update_tower_howitzer(tower, tower_index) +local function update_tower_gattler(tower, tower_index) + if not tower.target_index then + -- we should try and acquire a target + for index,mob in pairs(state.mobs) do + if mob then + local d = math.distance(mob.hex, tower.hex) + if d <= tower.range then + tower.target_index = index + break + end + end + end + + -- passive animation + tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) + else + -- should have a target, so we should try and shoot it + if not state.mobs[tower.target_index] then + -- the target we have was invalidated + tower.target_index = false + + else + -- the target we have is valid + local mob = state.mobs[tower.target_index] + local vector = math.normalize(mob.position - tower.position) + + if (state.time - tower.last_shot_time) > tower.fire_rate then + local projectile = make_and_register_projectile( + tower.hex, + PROJECTILE_TYPE.BULLET, + vector + ) + + tower.last_shot_time = state.time + play_sfx(SOUNDS.HIT1) + end + + -- point the cannon at the dude + local theta = math.rad(90) - math.atan((tower.position.y - mob.position.y)/(tower.position.x - mob.position.x)) + local diff = tower.node("rotate").angle - theta + + tower.node("rotate").angle = -theta + math.pi/2 + end + end +end + +local function update_tower_howitzer(tower, tower_index) if not tower.target_index then -- we don't have a target for index,mob in pairs(state.mobs) do @@ -403,6 +424,8 @@ function update_tower_howitzer(tower, tower_index) end end end + + -- passive animation tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) else -- we should have a target @@ -432,6 +455,7 @@ function update_tower_howitzer(tower, tower_index) play_sfx(SOUNDS.EXPLOSION2) end + -- point the cannon at the dude local theta = math.rad(90) - math.atan((tower.position.y - mob.position.y)/(tower.position.x - mob.position.x)) local diff = tower.node("rotate").angle - theta @@ -440,7 +464,7 @@ function update_tower_howitzer(tower, tower_index) end end -function update_tower_lighthouse(tower, tower_index) +local function update_tower_lighthouse(tower, tower_index) -- check if there's a mob on a hex in our perimeter for _,h in pairs(tower.perimeter) do local mobs = mobs_on_hex(h) @@ -455,7 +479,7 @@ function update_tower_lighthouse(tower, tower_index) if made_it then m.path = path - m.seen_lighthouse = true + m.seen_lighthouse = true -- right now mobs don't care about lighthouses if they've already seen one. --[[ local area = spiral_map(tower.hex, tower.range) @@ -477,6 +501,21 @@ function update_tower_lighthouse(tower, tower_index) end end +local function get_tower_update_function(tower_type) + if tower_type == TOWER_TYPE.REDEYE then + return update_tower_redeye + + elseif tower_type == TOWER_TYPE.GATTLER then + return update_tower_gattler + + elseif tower_type == TOWER_TYPE.HOWITZER then + return update_tower_howitzer + + elseif tower_type == TOWER_TYPE.LIGHTHOUSE then + return update_tower_lighthouse + end +end + function make_and_register_tower(hex, tower_type) local tower = make_basic_entity( hex, diff --git a/texture.lua b/texture.lua index 85cbe01..50568cd 100644 --- a/texture.lua +++ b/texture.lua @@ -1,10 +1,12 @@ +local fail_count = 0 local function load_texture(filepath) local status, texture = pcall(am.texture2d, filepath) if status then return texture else + fail_count = fail_count + 1 return am.texture2d("res/bagel.jpg") end end @@ -26,11 +28,13 @@ TEXTURES = { CURTAIN = load_texture("res/curtain1.png"), + SOUND_ON1 = load_texture("res/sound-on.png"), + SOUND_OFF = load_texture("res/sound-off.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 = load_texture("res/select_box.png"), @@ -41,7 +45,6 @@ TEXTURES = { TOWER_GATTLER = load_texture("res/tower_gattler.png"), TOWER_GATTLER_ICON = load_texture("res/tower_gattler_icon.png"), TOWER_HOWITZER = load_texture("res/tower_howitzer.png"), - CANNON1 = load_texture("res/cannon1.png"), TOWER_HOWITZER_ICON = load_texture("res/tower_howitzer_icon.png"), TOWER_REDEYE = load_texture("res/tower_redeye.png"), TOWER_REDEYE_ICON = load_texture("res/tower_redeye_icon.png"), @@ -72,3 +75,7 @@ function pack_texture_into_sprite(texture, width, height, color) return sprite end +if fail_count > 0 then + log("failed to load %d textures", fail_count) +end +