diff --git a/color.lua b/color.lua index c940ae6..ae26654 100644 --- a/color.lua +++ b/color.lua @@ -19,5 +19,6 @@ COLORS = { -- hues CLARET = vec4(139/255, 30/255, 63/255, 1), SUNRAY = vec4(228/255, 179/255, 99/255, 1), + GREEN_YELLOW = vec4(204/255, 255/255, 102/255, 1) } diff --git a/src/game.lua b/src/game.lua index 2097a7e..d5e2e0c 100644 --- a/src/game.lua +++ b/src/game.lua @@ -93,7 +93,7 @@ local function game_pause() WIN.scene("game"):append(am.group{ am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRANSPARENT), - am.scale(3) + am.scale(2) ^ am.text(string.format( "Paused.\nSeed: %d\nEscape to Resume\nf4 to start a new game", state.map.seed ), COLORS.BLACK), @@ -163,11 +163,11 @@ local function game_action(scene) end if WIN:mouse_pressed"middle" then - state.world"scale".scale2d = vec2(1) + WIN.scene("world_scale").scale2d = vec2(1) else 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 if WIN:key_pressed"escape" then @@ -389,7 +389,7 @@ function game_scene() end)) 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), score, money, diff --git a/src/grid.lua b/src/grid.lua index 7d7e25c..5704ca1 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -55,16 +55,16 @@ function tile_is_medium_elevation(tile) end 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 } - 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 } - 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 } - elseif elevation <= 1 then -- high elevation + elseif elevation < 1 then -- high elevation return COLORS.MOUNTAIN{ ra = elevation } end end @@ -76,7 +76,10 @@ end function grid_cost(map, from, to) 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 - math.abs(t2.elevation)^0.5) diff --git a/src/mob.lua b/src/mob.lua index 044bd42..5c21b35 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -50,18 +50,39 @@ function mob_die(mob, mob_index) delete_entity(MOBS, mob_index) end +local HEALTHBAR_WIDTH = HEX_PIXEL_WIDTH/2 +local HEALTHBAR_HEIGHT = HEALTHBAR_WIDTH/4 function do_hit_mob(mob, damage, mob_index) mob.health = mob.health - damage if mob.health <= 0 then update_score(mob.bounty) update_money(mob.bounty) 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 --- @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) return Astar(map, goal, start, grid_heuristic, grid_cost) end @@ -105,8 +126,7 @@ local function update_mob(mob, mob_index) if last_frame_hex ~= mob.hex or not mob.frame_target then local frame_target, tile = false, false 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] if not path_entry then @@ -119,10 +139,8 @@ local function update_mob(mob, mob_index) 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. - -- recalculate our path. 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 end else @@ -137,6 +155,13 @@ local function update_mob(mob, mob_index) for _,n in pairs(neighbours) do 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 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 -- check for that now 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.node.position2d = mob.position @@ -184,7 +211,7 @@ end local function make_and_register_mob(mob_type) local mob = make_basic_entity( 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 ) @@ -195,12 +222,13 @@ local function make_and_register_mob(mob_type) mob.speed = spec.speed mob.bounty = spec.bounty mob.hurtbox_radius = spec.hurtbox_radius + mob.healthbar = mob.node:child(1):child(2):child(1) register_entity(MOBS, mob) return mob end -local SPAWN_CHANCE = 50 +local SPAWN_CHANCE = 25 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 63f76e7..980127d 100644 --- a/src/projectile.lua +++ b/src/projectile.lua @@ -35,7 +35,7 @@ end local function make_shell_explosion_node(source_position) return am.particles2d{ - source_pos = source_position, + source_pos = source_position + WORLDSPACE_COORDINATE_OFFSET, source_pos_var = vec2(4), start_size = 2, start_size_var = 1, @@ -43,17 +43,17 @@ local function make_shell_explosion_node(source_position) end_size_var = 0, angle = 0, angle_var = math.pi, - speed = 25, - speed_var = 15, - life = 10, + speed = 85, + speed_var = 45, + life = 2, 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, + emission_rate = 0, + start_particles = 100, + max_particles = 100, gravity = vec2(0, -10), warmup_time = 1 } @@ -62,12 +62,23 @@ local function make_shell_explosion_node(source_position) end)) end +local SHELL_GRAVITY = 0.6 local function update_projectile_shell(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(PROJECTILES, projectile_index) + return true + end + projectile.node.position2d = 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 -- 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 mobs = {} 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 - table.insert(mobs, mob_index, mob) + table.insert(mobs, 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 @@ -95,18 +105,16 @@ local function update_projectile_shell(projectile, projectile_index) 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(#mobs) 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 WIN.scene:append(make_shell_explosion_node(projectile.position)) delete_entity(PROJECTILES, projectile_index) @@ -116,8 +124,6 @@ end local function update_projectile_laser(projectile, projectile_index) 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 if not point_in_rect(projectile.position + WORLDSPACE_COORDINATE_OFFSET, { @@ -130,6 +136,9 @@ local function update_projectile_laser(projectile, projectile_index) return true end + projectile.node.position2d = projectile.position + projectile.hex = pixel_to_hex(projectile.position) + -- check if we hit something -- 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. diff --git a/src/tower.lua b/src/tower.lua index 10bb275..c2d5a7f 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -215,11 +215,11 @@ end function tower_type_is_buildable_on(hex, tile, tower_type) 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 @@ -368,6 +368,7 @@ function update_tower_lighthouse(tower, tower_index) end end +local TOWER_HEIGHT = 0.6 function make_and_register_tower(hex, tower_type) local tower = make_basic_entity( hex, @@ -385,27 +386,27 @@ function make_and_register_tower(hex, tower_type) if tower_type == TOWER_TYPE.REDEYE then 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 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 elseif tower_type == TOWER_TYPE.LIGHTHOUSE then tower.perimeter = ring_map(tower.hex, tower.range) 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 - 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 - 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 local tile = state.map.get(hex.x, hex.y) - tile.elevation = tile.elevation + 0.6 + tile.elevation = tile.elevation + TOWER_HEIGHT end return register_entity(TOWERS, tower)