Browse Source

gattler!

master
Nicholas Hayashi 3 years ago
parent
commit
5046f3cde0
  1. 10
      main.lua
  2. BIN
      res/Sprite-0001.aseprite
  3. BIN
      res/Sprite-0002.aseprite
  4. BIN
      res/sound-off.png
  5. BIN
      res/sound-on.png
  6. BIN
      res/tower_gattler_icon.png
  7. 0
      res/tower_howitzer.png
  8. BIN
      res/tower_howitzer_icon-export.png
  9. BIN
      res/tower_howitzer_icon.png
  10. 1
      src/entity.lua
  11. 1
      src/extra.lua
  12. 9
      src/game.lua
  13. 3
      src/hexyz.lua
  14. 66
      src/projectile.lua
  15. 129
      src/tower.lua
  16. 11
      texture.lua

10
main.lua

@ -156,7 +156,7 @@ function make_main_scene_toolbelt()
end end
end end
}, },
{
false and {
texture = TEXTURES.MAP_EDITOR_HEX, texture = TEXTURES.MAP_EDITOR_HEX,
action = function() alert("not yet :)") end action = function() alert("not yet :)") end
}, },
@ -164,7 +164,7 @@ function make_main_scene_toolbelt()
texture = TEXTURES.UNPAUSE_HEX, texture = TEXTURES.UNPAUSE_HEX,
action = function() unpause(win.scene("menu")) end action = function() unpause(win.scene("menu")) end
} or false, } or false,
{
false and {
texture = TEXTURES.SETTINGS_HEX, texture = TEXTURES.SETTINGS_HEX,
action = function() alert("not yet :)") end action = function() alert("not yet :)") end
}, },
@ -261,11 +261,17 @@ function main_scene(do_backdrop, do_logo)
) )
end end
-- version/author info
group:append( group:append(
am.translate(win.right - 10, win.bottom + 10) am.translate(win.right - 10, win.bottom + 10)
^ am.text(string.format("v%s, by %s", version, author), COLORS.WHITE, "right", "bottom") ^ 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 if do_logo then
local position = vec2(0, win.top - 20 - TEXTURES.LOGO.height/2) local position = vec2(0, win.top - 20 - TEXTURES.LOGO.height/2)
local logo = local logo =

BIN
res/Sprite-0001.aseprite

BIN
res/Sprite-0002.aseprite

BIN
res/sound-off.png

After

Width: 980  |  Height: 728  |  Size: 28 KiB

BIN
res/sound-on.png

After

Width: 835  |  Height: 653  |  Size: 123 KiB

BIN
res/tower_gattler_icon.png

Before

Width: 516  |  Height: 501  |  Size: 145 KiB

After

Width: 64  |  Height: 64  |  Size: 360 B

0
res/cannon1.png → res/tower_howitzer.png

Before

Width: 64  |  Height: 64  |  Size: 292 B

After

Width: 64  |  Height: 64  |  Size: 292 B

BIN
res/tower_howitzer_icon-export.png

After

Width: 64  |  Height: 64  |  Size: 366 B

BIN
res/tower_howitzer_icon.png

Before

Width: 600  |  Height: 450  |  Size: 85 KiB

After

Width: 64  |  Height: 64  |  Size: 366 B

1
src/entity.lua

@ -1,5 +1,4 @@
--[[ --[[
entity structure: entity structure:
{ {

1
src/extra.lua

@ -1,5 +1,4 @@
-- @TODO make it work with functions that return multiple values -- @TODO make it work with functions that return multiple values
-- right now it discards returned values beyond the first -- right now it discards returned values beyond the first
function fprofile(f, ...) function fprofile(f, ...)

9
src/game.lua

@ -408,9 +408,9 @@ local function make_game_toolbelt()
am.translate(vec2(half_size)) am.translate(vec2(half_size))
^ am.group( ^ 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.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) select_tower_type(nil)
else else
state = get_initial_game_state() 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 end
game = true game = true

3
src/hexyz.lua

@ -8,9 +8,6 @@
-- and some utility functions not present in your standard lua, like: -- and some utility functions not present in your standard lua, like:
-- table.append -- table.append
-- @TODO -- @TODO
if not table.append then end if not table.append then end
if not table.filter then end if not table.filter then end

66
src/projectile.lua

@ -2,6 +2,7 @@
PROJECTILE_TYPE = { PROJECTILE_TYPE = {
SHELL = 1, SHELL = 1,
LASER = 2, LASER = 2,
BULLET = 3,
} }
local PROJECTILE_SPECS = { local PROJECTILE_SPECS = {
@ -15,6 +16,11 @@ local PROJECTILE_SPECS = {
damage = 20, damage = 20,
hitbox_radius = 20 hitbox_radius = 20
}, },
[PROJECTILE_TYPE.BULLET] = {
velocity = 25,
damage = 5,
hitbox_radius = 10
}
} }
function get_projectile_velocity(projectile_type) function get_projectile_velocity(projectile_type)
@ -174,7 +180,59 @@ local function update_projectile_laser(projectile, projectile_index)
-- hit the mob, affect the world -- hit the mob, affect the world
do_hit_mob(closest_mob, projectile.damage, closest_mob_index) 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) vplay_sfx(SOUNDS.HIT1, 0.5)
end end
@ -182,6 +240,9 @@ function make_projectile_node(projectile_type, vector)
if projectile_type == PROJECTILE_TYPE.LASER then if projectile_type == PROJECTILE_TYPE.LASER then
return am.line(vector, vector*get_projectile_hitbox_radius(projectile_type), 3, COLORS.CLARET) 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 elseif projectile_type == PROJECTILE_TYPE.SHELL then
return am.circle(vec2(0), 3, COLORS.VERY_DARK_GRAY) return am.circle(vec2(0), 3, COLORS.VERY_DARK_GRAY)
end end
@ -191,6 +252,9 @@ function get_projectile_update_function(projectile_type)
if projectile_type == PROJECTILE_TYPE.LASER then if projectile_type == PROJECTILE_TYPE.LASER then
return update_projectile_laser return update_projectile_laser
elseif projectile_type == PROJECTILE_TYPE.BULLET then
return update_projectile_bullet
elseif projectile_type == PROJECTILE_TYPE.SHELL then elseif projectile_type == PROJECTILE_TYPE.SHELL then
return update_projectile_shell return update_projectile_shell
end end

129
src/tower.lua

@ -33,14 +33,14 @@ local TOWER_SPECS = {
icon_texture = TEXTURES.TOWER_GATTLER_ICON, icon_texture = TEXTURES.TOWER_GATTLER_ICON,
cost = 20, cost = 20,
range = 4, range = 4,
fire_rate = 10,
fire_rate = 0.5,
size = 0, size = 0,
height = 1, height = 1,
}, },
[TOWER_TYPE.HOWITZER] = { [TOWER_TYPE.HOWITZER] = {
name = "Howitzer", name = "Howitzer",
placement_rules_text = "Place on non-Water, non-Mountain", 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, texture = TEXTURES.TOWER_HOWITZER,
icon_texture = TEXTURES.TOWER_HOWITZER_ICON, icon_texture = TEXTURES.TOWER_HOWITZER_ICON,
cost = 50, cost = 50,
@ -52,19 +52,19 @@ local TOWER_SPECS = {
[TOWER_TYPE.REDEYE] = { [TOWER_TYPE.REDEYE] = {
name = "Redeye", name = "Redeye",
placement_rules_text = "Place on Mountains", 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, texture = TEXTURES.TOWER_REDEYE,
icon_texture = TEXTURES.TOWER_REDEYE_ICON, icon_texture = TEXTURES.TOWER_REDEYE_ICON,
cost = 140, cost = 140,
range = 9, range = 9,
fire_rate = 1,
fire_rate = 3,
size = 0, size = 0,
height = 1, height = 1,
}, },
[TOWER_TYPE.MOAT] = { [TOWER_TYPE.MOAT] = {
name = "Moat", name = "Moat",
placement_rules_text = "Place on Ground", placement_rules_text = "Place on Ground",
short_description = "Restricts movement",
short_description = "Restricts movement, similar to water.",
texture = TEXTURES.TOWER_MOAT, texture = TEXTURES.TOWER_MOAT,
icon_texture = TEXTURES.TOWER_MOAT_ICON, icon_texture = TEXTURES.TOWER_MOAT_ICON,
cost = 10, cost = 10,
@ -139,13 +139,17 @@ function make_tower_node(tower_type)
return make_tower_sprite(tower_type) return make_tower_sprite(tower_type)
elseif tower_type == TOWER_TYPE.GATTLER then 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 elseif tower_type == TOWER_TYPE.HOWITZER then
return am.group{ 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{ 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 elseif tower_type == TOWER_TYPE.LIGHTHOUSE then
@ -217,18 +221,6 @@ do
end end
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) function tower_serialize(tower)
local serialized = entity_basic_devectored_copy(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 mobs_blocking = table.count(blocking_mobs) ~= 0
local blocked = mobs_blocking or towers_blocking 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) return not (blocked or has_water or has_mountain)
elseif tower_type == TOWER_TYPE.REDEYE then 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 return not blocked
and not has_water and not has_water
and not has_ground and not has_ground
@ -361,7 +336,7 @@ function tower_type_is_buildable_on(hex, tile, tower_type)
end end
end end
function update_tower_redeye(tower, tower_index)
local function update_tower_redeye(tower, tower_index)
if not tower.target_index then if not tower.target_index then
for index,mob in pairs(state.mobs) do for index,mob in pairs(state.mobs) do
if mob then if mob then
@ -391,7 +366,53 @@ function update_tower_redeye(tower, tower_index)
end end
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 if not tower.target_index then
-- we don't have a target -- we don't have a target
for index,mob in pairs(state.mobs) do for index,mob in pairs(state.mobs) do
@ -403,6 +424,8 @@ function update_tower_howitzer(tower, tower_index)
end end
end end
end end
-- passive animation
tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2)
else else
-- we should have a target -- we should have a target
@ -432,6 +455,7 @@ function update_tower_howitzer(tower, tower_index)
play_sfx(SOUNDS.EXPLOSION2) play_sfx(SOUNDS.EXPLOSION2)
end 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 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 local diff = tower.node("rotate").angle - theta
@ -440,7 +464,7 @@ function update_tower_howitzer(tower, tower_index)
end end
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 -- check if there's a mob on a hex in our perimeter
for _,h in pairs(tower.perimeter) do for _,h in pairs(tower.perimeter) do
local mobs = mobs_on_hex(h) local mobs = mobs_on_hex(h)
@ -455,7 +479,7 @@ function update_tower_lighthouse(tower, tower_index)
if made_it then if made_it then
m.path = path 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) local area = spiral_map(tower.hex, tower.range)
@ -477,6 +501,21 @@ function update_tower_lighthouse(tower, tower_index)
end end
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) function make_and_register_tower(hex, tower_type)
local tower = make_basic_entity( local tower = make_basic_entity(
hex, hex,

11
texture.lua

@ -1,10 +1,12 @@
local fail_count = 0
local function load_texture(filepath) local function load_texture(filepath)
local status, texture = pcall(am.texture2d, filepath) local status, texture = pcall(am.texture2d, filepath)
if status then if status then
return texture return texture
else else
fail_count = fail_count + 1
return am.texture2d("res/bagel.jpg") return am.texture2d("res/bagel.jpg")
end end
end end
@ -26,11 +28,13 @@ TEXTURES = {
CURTAIN = load_texture("res/curtain1.png"), CURTAIN = load_texture("res/curtain1.png"),
SOUND_ON1 = load_texture("res/sound-on.png"),
SOUND_OFF = load_texture("res/sound-off.png"),
-- gui stuff -- gui stuff
BUTTON1 = load_texture("res/button1.png"), BUTTON1 = load_texture("res/button1.png"),
WIDER_BUTTON1 = load_texture("res/wider_button1.png"), WIDER_BUTTON1 = load_texture("res/wider_button1.png"),
TAB_ICON = load_texture("res/tab_icon.png"), TAB_ICON = load_texture("res/tab_icon.png"),
GUI_SLIDER = load_texture("res/slider.png"),
GEAR = load_texture("res/gear.png"), GEAR = load_texture("res/gear.png"),
SELECT_BOX = load_texture("res/select_box.png"), SELECT_BOX = load_texture("res/select_box.png"),
@ -41,7 +45,6 @@ TEXTURES = {
TOWER_GATTLER = load_texture("res/tower_gattler.png"), TOWER_GATTLER = load_texture("res/tower_gattler.png"),
TOWER_GATTLER_ICON = load_texture("res/tower_gattler_icon.png"), TOWER_GATTLER_ICON = load_texture("res/tower_gattler_icon.png"),
TOWER_HOWITZER = load_texture("res/tower_howitzer.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_HOWITZER_ICON = load_texture("res/tower_howitzer_icon.png"),
TOWER_REDEYE = load_texture("res/tower_redeye.png"), TOWER_REDEYE = load_texture("res/tower_redeye.png"),
TOWER_REDEYE_ICON = load_texture("res/tower_redeye_icon.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 return sprite
end end
if fail_count > 0 then
log("failed to load %d textures", fail_count)
end
Loading…
Cancel
Save