Browse Source

added healthbar

master
Nicholas Hayashi 4 years ago
parent
commit
c4a097e8d9
  1. 1
      color.lua
  2. 8
      src/game.lua
  3. 13
      src/grid.lua
  4. 50
      src/mob.lua
  5. 45
      src/projectile.lua
  6. 21
      src/tower.lua

1
color.lua

@ -19,5 +19,6 @@ COLORS = {
-- hues -- hues
CLARET = vec4(139/255, 30/255, 63/255, 1), CLARET = vec4(139/255, 30/255, 63/255, 1),
SUNRAY = vec4(228/255, 179/255, 99/255, 1), SUNRAY = vec4(228/255, 179/255, 99/255, 1),
GREEN_YELLOW = vec4(204/255, 255/255, 102/255, 1)
} }

8
src/game.lua

@ -93,7 +93,7 @@ local function game_pause()
WIN.scene("game"):append(am.group{ WIN.scene("game"):append(am.group{
am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRANSPARENT), am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRANSPARENT),
am.scale(3)
am.scale(2)
^ am.text(string.format( ^ am.text(string.format(
"Paused.\nSeed: %d\nEscape to Resume\nf4 to start a new game", state.map.seed "Paused.\nSeed: %d\nEscape to Resume\nf4 to start a new game", state.map.seed
), COLORS.BLACK), ), COLORS.BLACK),
@ -163,11 +163,11 @@ local function game_action(scene)
end end
if WIN:mouse_pressed"middle" then if WIN:mouse_pressed"middle" then
state.world"scale".scale2d = vec2(1)
WIN.scene("world_scale").scale2d = vec2(1)
else else
local mwd = WIN:mouse_wheel_delta() local mwd = WIN:mouse_wheel_delta()
state.world"scale".scale = state.world"scale".scale + vec3(mwd, 0) / 100
WIN.scene("world_scale").scale2d = WIN.scene("world_scale").scale2d + vec2(mwd) / 100
end end
if WIN:key_pressed"escape" then if WIN:key_pressed"escape" then
@ -389,7 +389,7 @@ function game_scene()
end)) end))
local scene = am.group{ local scene = am.group{
state.world,
am.scale(1):tag"world_scale" ^ state.world,
am.translate(OFF_SCREEN):tag"cursor_translate" ^ make_hex_cursor(0, COLORS.TRANSPARENT), am.translate(OFF_SCREEN):tag"cursor_translate" ^ make_hex_cursor(0, COLORS.TRANSPARENT),
score, score,
money, money,

13
src/grid.lua

@ -55,16 +55,16 @@ function tile_is_medium_elevation(tile)
end end
function color_at(elevation) function color_at(elevation)
if elevation <= -0.5 then -- lowest elevation
if elevation < -0.5 then -- lowest elevation
return COLORS.WATER{ a = (elevation + 1.4) / 2 + 0.2 } return COLORS.WATER{ a = (elevation + 1.4) / 2 + 0.2 }
elseif elevation <= 0 then -- med-low elevation
elseif elevation < 0 then -- med-low elevation
return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.3 } return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.3 }
elseif elevation <= 0.5 then -- med-high elevation
elseif elevation < 0.5 then -- med-high elevation
return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.3 } return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.3 }
elseif elevation <= 1 then -- high elevation
elseif elevation < 1 then -- high elevation
return COLORS.MOUNTAIN{ ra = elevation } return COLORS.MOUNTAIN{ ra = elevation }
end end
end end
@ -76,7 +76,10 @@ end
function grid_cost(map, from, to) function grid_cost(map, from, to)
local t1, t2 = map.get(from.x, from.y), map.get(to.x, to.y) local t1, t2 = map.get(from.x, from.y), map.get(to.x, to.y)
local elevation_epsilon = HEX_GRID_MAXIMUM_ELEVATION - HEX_GRID_MINIMUM_ELEVATION
-- i have no fucking clue why, but adding +0.2 to the end of this fixes a bug where sometimes two (or more)
-- equivalent paths are found and mobs backpedal trying to decide between them
-- (seed 2014 at time of writing has this at the bottom)
local elevation_epsilon = HEX_GRID_MAXIMUM_ELEVATION - HEX_GRID_MINIMUM_ELEVATION + 0.2
local elevation_cost = math.abs(math.abs(t1.elevation)^0.5 local elevation_cost = math.abs(math.abs(t1.elevation)^0.5
- math.abs(t2.elevation)^0.5) - math.abs(t2.elevation)^0.5)

50
src/mob.lua

@ -50,18 +50,39 @@ function mob_die(mob, mob_index)
delete_entity(MOBS, mob_index) delete_entity(MOBS, mob_index)
end end
local HEALTHBAR_WIDTH = HEX_PIXEL_WIDTH/2
local HEALTHBAR_HEIGHT = HEALTHBAR_WIDTH/4
function do_hit_mob(mob, damage, mob_index) function do_hit_mob(mob, damage, mob_index)
mob.health = mob.health - damage mob.health = mob.health - damage
if mob.health <= 0 then if mob.health <= 0 then
update_score(mob.bounty) update_score(mob.bounty)
update_money(mob.bounty) update_money(mob.bounty)
mob_die(mob, mob_index) mob_die(mob, mob_index)
else
mob.healthbar:action(coroutine.create(function(self)
self:child(2).x2 = -HEALTHBAR_WIDTH/2 + mob.health/10 * HEALTHBAR_WIDTH/2
self.hidden = false
am.wait(am.delay(0.8))
self.hidden = true
end))
end end
end end
-- @TODO performance.
-- try reducing map size by identifying key nodes (inflection points)
-- there are performance hits everytime we spawn a mob and it's Astar's fault
function make_mob_node(mob_type, mob)
local healthbar = am.group{
am.rect(-HEALTHBAR_WIDTH/2, -HEALTHBAR_HEIGHT/2, HEALTHBAR_WIDTH/2, HEALTHBAR_HEIGHT/2, COLORS.VERY_DARK_GRAY),
am.rect(-HEALTHBAR_WIDTH/2, -HEALTHBAR_HEIGHT/2, HEALTHBAR_WIDTH/2, HEALTHBAR_HEIGHT/2, COLORS.GREEN_YELLOW)
}
healthbar.hidden = true
return am.group{
am.rotate(state.time)
^ pack_texture_into_sprite(TEXTURES.MOB_BEEPER, MOB_SIZE, MOB_SIZE),
am.translate(0, -10)
^ healthbar
}
end
function get_mob_path(mob, map, start, goal) function get_mob_path(mob, map, start, goal)
return Astar(map, goal, start, grid_heuristic, grid_cost) return Astar(map, goal, start, grid_heuristic, grid_cost)
end end
@ -105,8 +126,7 @@ local function update_mob(mob, mob_index)
if last_frame_hex ~= mob.hex or not mob.frame_target then if last_frame_hex ~= mob.hex or not mob.frame_target then
local frame_target, tile = false, false local frame_target, tile = false, false
if mob.path then if mob.path then
--log('A*')
-- we have an explicitly stored target
-- we (should) have an explicitly stored target
local path_entry = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y] local path_entry = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y]
if not path_entry then if not path_entry then
@ -119,10 +139,8 @@ local function update_mob(mob, mob_index)
mob.frame_target = path_entry.hex mob.frame_target = path_entry.hex
-- check if our target is valid, and if it's not we aren't going to move this frame. -- check if our target is valid, and if it's not we aren't going to move this frame.
-- recalculate our path.
if last_frame_hex ~= mob.hex and not mob_can_pass_through(mob, mob.frame_target) then if last_frame_hex ~= mob.hex and not mob_can_pass_through(mob, mob.frame_target) then
log('recalc')
mob.path = get_mob_path(mob, HEX_MAP, mob.hex, HEX_GRID_CENTER)
mob.path = false
mob.frame_target = false mob.frame_target = false
end end
else else
@ -137,6 +155,13 @@ local function update_mob(mob, mob_index)
for _,n in pairs(neighbours) do for _,n in pairs(neighbours) do
tile = state.map.get(n.x, n.y) tile = state.map.get(n.x, n.y)
if not tile.priority then
-- if there's no stored priority, that should mean it's the center tile
mob.frame_target = n
break
end
local current_cost = tile.priority local current_cost = tile.priority
if current_cost and current_cost < lowest_cost then if current_cost and current_cost < lowest_cost then
@ -162,7 +187,9 @@ local function update_mob(mob, mob_index)
-- or between when we last calculated this target and now -- or between when we last calculated this target and now
-- check for that now -- check for that now
if mob_can_pass_through(mob, mob.frame_target) then if mob_can_pass_through(mob, mob.frame_target) then
local rate = 4 * mob.speed * am.delta_time
local from = state.map.get(mob.hex.x, mob.hex.y)
local to = state.map.get(mob.frame_target.x, mob.frame_target.y)
local rate = 4 * mob.speed * am.delta_time - math.abs(from.elevation - to.elevation)
mob.position = mob.position + math.normalize(hex_to_pixel(mob.frame_target) - mob.position) * rate mob.position = mob.position + math.normalize(hex_to_pixel(mob.frame_target) - mob.position) * rate
mob.node.position2d = mob.position mob.node.position2d = mob.position
@ -184,7 +211,7 @@ end
local function make_and_register_mob(mob_type) local function make_and_register_mob(mob_type)
local mob = make_basic_entity( local mob = make_basic_entity(
get_spawn_hex(), get_spawn_hex(),
am.rotate(state.time) ^ pack_texture_into_sprite(TEXTURES.MOB_BEEPER, MOB_SIZE, MOB_SIZE),
make_mob_node(mob_type),
update_mob update_mob
) )
@ -195,12 +222,13 @@ local function make_and_register_mob(mob_type)
mob.speed = spec.speed mob.speed = spec.speed
mob.bounty = spec.bounty mob.bounty = spec.bounty
mob.hurtbox_radius = spec.hurtbox_radius mob.hurtbox_radius = spec.hurtbox_radius
mob.healthbar = mob.node:child(1):child(2):child(1)
register_entity(MOBS, mob) register_entity(MOBS, mob)
return mob return mob
end end
local SPAWN_CHANCE = 50
local SPAWN_CHANCE = 25
function do_mob_spawning() function do_mob_spawning()
--if WIN:key_pressed"space" then --if WIN:key_pressed"space" then
if math.random(SPAWN_CHANCE) == 1 then if math.random(SPAWN_CHANCE) == 1 then

45
src/projectile.lua

@ -35,7 +35,7 @@ end
local function make_shell_explosion_node(source_position) local function make_shell_explosion_node(source_position)
return am.particles2d{ return am.particles2d{
source_pos = source_position,
source_pos = source_position + WORLDSPACE_COORDINATE_OFFSET,
source_pos_var = vec2(4), source_pos_var = vec2(4),
start_size = 2, start_size = 2,
start_size_var = 1, start_size_var = 1,
@ -43,17 +43,17 @@ local function make_shell_explosion_node(source_position)
end_size_var = 0, end_size_var = 0,
angle = 0, angle = 0,
angle_var = math.pi, angle_var = math.pi,
speed = 25,
speed_var = 15,
life = 10,
speed = 85,
speed_var = 45,
life = 2,
life_var = 1, life_var = 1,
start_color = COLORS.VERY_DARK_GRAY, start_color = COLORS.VERY_DARK_GRAY,
start_color_var = vec4(0.2), start_color_var = vec4(0.2),
end_color = vec4(0), end_color = vec4(0),
end_color_var = vec4(0.1), end_color_var = vec4(0.1),
emission_rate = 100,
start_particles = 150,
max_particles = 150,
emission_rate = 0,
start_particles = 100,
max_particles = 100,
gravity = vec2(0, -10), gravity = vec2(0, -10),
warmup_time = 1 warmup_time = 1
} }
@ -62,12 +62,23 @@ local function make_shell_explosion_node(source_position)
end)) end))
end end
local SHELL_GRAVITY = 0.6
local function update_projectile_shell(projectile, projectile_index) local function update_projectile_shell(projectile, projectile_index)
projectile.position = projectile.position + projectile.vector * projectile.velocity 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(PROJECTILES, projectile_index)
return true
end
projectile.node.position2d = projectile.position projectile.node.position2d = projectile.position
projectile.hex = pixel_to_hex(projectile.position) projectile.hex = pixel_to_hex(projectile.position)
projectile.props.z = projectile.props.z - 0.6 * am.delta_time
projectile.props.z = projectile.props.z - SHELL_GRAVITY * am.delta_time
-- check if we hit something -- check if we hit something
-- get a list of hexes that could have something we could hit on them -- get a list of hexes that could have something we could hit on them
@ -77,15 +88,14 @@ local function update_projectile_shell(projectile, projectile_index)
local search_hexes = spiral_map(projectile.hex, 1) local search_hexes = spiral_map(projectile.hex, 1)
local mobs = {} local mobs = {}
for _,hex in pairs(search_hexes) do for _,hex in pairs(search_hexes) do
for mob_index,mob in pairs(mobs_on_hex(hex)) do
for index,mob in pairs(mobs_on_hex(hex)) do
if mob then if mob then
table.insert(mobs, mob_index, mob)
table.insert(mobs, index, mob)
if circles_intersect(mob.position if circles_intersect(mob.position
, projectile.position , projectile.position
, mob.hurtbox_radius , mob.hurtbox_radius
, projectile.hitbox_radius) then , projectile.hitbox_radius) then
log('exploded cuz we hit a boi')
do_explode = true 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 -- 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
@ -95,18 +105,16 @@ local function update_projectile_shell(projectile, projectile_index)
local tile = state.map.get(projectile.hex.x, projectile.hex.y) local tile = state.map.get(projectile.hex.x, projectile.hex.y)
if tile and tile.elevation >= projectile.props.z then if tile and tile.elevation >= projectile.props.z then
log('we exploded cuz we hit something toll')
do_explode = true do_explode = true
elseif projectile.props.z <= 0 then elseif projectile.props.z <= 0 then
log('exploded cuz we hit da grund')
do_explode = true do_explode = true
end end
if do_explode then if do_explode then
log(#mobs)
for index,mob in pairs(mobs) do for index,mob in pairs(mobs) do
do_hit_mob(mob, 1 / math.distance(mob.position, projectile.position) * projectile.damage, index)
local damage = (1 / (math.distance(mob.position, projectile.position) / (HEX_PIXEL_WIDTH * 2))) * projectile.damage
do_hit_mob(mob, damage, index)
end end
WIN.scene:append(make_shell_explosion_node(projectile.position)) WIN.scene:append(make_shell_explosion_node(projectile.position))
delete_entity(PROJECTILES, projectile_index) delete_entity(PROJECTILES, projectile_index)
@ -116,8 +124,6 @@ end
local function update_projectile_laser(projectile, projectile_index) 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)
-- check if we're out of bounds -- check if we're out of bounds
if not point_in_rect(projectile.position + WORLDSPACE_COORDINATE_OFFSET, { if not point_in_rect(projectile.position + WORLDSPACE_COORDINATE_OFFSET, {
@ -130,6 +136,9 @@ local function update_projectile_laser(projectile, projectile_index)
return true return true
end end
projectile.node.position2d = projectile.position
projectile.hex = pixel_to_hex(projectile.position)
-- check if we hit something -- check if we hit something
-- get a list of hexes that could have something we could hit on them -- get a list of hexes that could have something we could hit on them
-- right now, it's just the hex we're on and all of its neighbours. -- right now, it's just the hex we're on and all of its neighbours.

21
src/tower.lua

@ -215,11 +215,11 @@ end
function tower_type_is_buildable_on(hex, tile, tower_type) function tower_type_is_buildable_on(hex, tile, tower_type)
if not tower_type then return false end if not tower_type then return false end
local blocking_towers = towers_on_hex(hex)
local blocking_mobs = mobs_on_hex(hex)
local blocking_towers = towers_on_hex(hex)
local blocking_mobs = mobs_on_hex(hex)
local towers_blocking = #blocking_towers ~= 0
local mobs_blocking = #blocking_mobs ~= 0
local towers_blocking = #blocking_towers ~= 0
local mobs_blocking = #blocking_mobs ~= 0
local blocked = mobs_blocking or towers_blocking local blocked = mobs_blocking or towers_blocking
@ -368,6 +368,7 @@ function update_tower_lighthouse(tower, tower_index)
end end
end end
local TOWER_HEIGHT = 0.6
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,
@ -385,27 +386,27 @@ function make_and_register_tower(hex, tower_type)
if tower_type == TOWER_TYPE.REDEYE then if tower_type == TOWER_TYPE.REDEYE then
local tile = state.map.get(hex.x, hex.y) local tile = state.map.get(hex.x, hex.y)
tile.elevation = tile.elevation + 0.6
tile.elevation = tile.elevation + TOWER_HEIGHT
elseif tower_type == TOWER_TYPE.HOWITZER then elseif tower_type == TOWER_TYPE.HOWITZER then
local tile = state.map.get(hex.x, hex.y) local tile = state.map.get(hex.x, hex.y)
tile.elevation = tile.elevation + 0.6
tile.elevation = tile.elevation + TOWER_HEIGHT
tower.props.z = tile.elevation tower.props.z = tile.elevation
elseif tower_type == TOWER_TYPE.LIGHTHOUSE then elseif tower_type == TOWER_TYPE.LIGHTHOUSE then
tower.perimeter = ring_map(tower.hex, tower.range) tower.perimeter = ring_map(tower.hex, tower.range)
local tile = state.map.get(hex.x, hex.y) local tile = state.map.get(hex.x, hex.y)
tile.elevation = tile.elevation + 0.6
tile.elevation = tile.elevation + TOWER_HEIGHT
elseif tower_type == TOWER_TYPE.WALL then elseif tower_type == TOWER_TYPE.WALL then
state.map.get(hex.x, hex.y).elevation = 0.6
state.map.get(hex.x, hex.y).elevation = TOWER_HEIGHT
elseif tower_type == TOWER_TYPE.MOAT then elseif tower_type == TOWER_TYPE.MOAT then
state.map.get(hex.x, hex.y).elevation = -0.6
state.map.get(hex.x, hex.y).elevation = -TOWER_HEIGHT
elseif tower_type == TOWER_TYPE.RADAR then elseif tower_type == TOWER_TYPE.RADAR then
local tile = state.map.get(hex.x, hex.y) local tile = state.map.get(hex.x, hex.y)
tile.elevation = tile.elevation + 0.6
tile.elevation = tile.elevation + TOWER_HEIGHT
end end
return register_entity(TOWERS, tower) return register_entity(TOWERS, tower)

Loading…
Cancel
Save