diff --git a/color.lua b/color.lua index 551748d..c940ae6 100644 --- a/color.lua +++ b/color.lua @@ -12,7 +12,7 @@ COLORS = { -- non-standard hues WATER = vec4(0.12, 0.25, 0.3, 1), - GRASS = vec4(0.10, 0.25, 0.10, 1), + GRASS = vec4(0.05, 0.22, 0.11, 1), DIRT = vec4(0.22, 0.20, 0.10, 1), MOUNTAIN = vec4(0.95, 0.30, 0.20, 1), diff --git a/src/entity.lua b/src/entity.lua index ec99d11..239aa6a 100644 --- a/src/entity.lua +++ b/src/entity.lua @@ -9,6 +9,9 @@ entity structure: update - function - runs every frame with itself and its index in some array as an argument node - node - scene graph node + type - enum - sub type - unset if 'basic' entity + props - table - table of properties specific to this entity subtype + ... - any - a bunch of other shit depending on what entity type it is } --]] @@ -31,6 +34,8 @@ function make_basic_entity(hex, node, update, position) entity.update = update entity.node = am.translate(entity.position) ^ node + entity.type = false + entity.props = {} return entity end diff --git a/src/game.lua b/src/game.lua index 7b85ceb..baa985e 100644 --- a/src/game.lua +++ b/src/game.lua @@ -84,7 +84,7 @@ function select_toolbelt_button(i) end function do_day_night_cycle() - local tstep = (math.sin(state.time / 100) + 1) / state.perf.avg_fps + local tstep = (math.sin(state.time / 100) + 1) * am.delta_time state.world"negative_mask".color = vec4(tstep){a=1} end @@ -92,7 +92,10 @@ local function game_pause() 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("Paused.\nEscape to Resume\nf4 to start a new game", COLORS.BLACK) + am.scale(3) + ^ am.text(string.format( + "Paused.\nSeed: %d\nEscape to Resume\nf4 to start a new game", state.map.seed + ), COLORS.BLACK) } :tag"pause_menu") WIN.scene:action(function() @@ -286,6 +289,7 @@ local function make_game_toolbelt() toolbelt:append(tower_select_square) local keys = { '1', '2', '3', '4', 'q', 'w', 'e', 'r', 'a', 's', 'd', 'f' } + --local keys = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=' } -- order of this array is the order of towers on the toolbelt. local tower_type_values = { TOWER_TYPE.WALL, @@ -392,6 +396,9 @@ 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 + state.map.get(HEX_GRID_CENTER.x, HEX_GRID_CENTER.y).elevation = 0 + WIN.scene:remove("game") WIN.scene:append(game_scene()) end diff --git a/src/grid.lua b/src/grid.lua index 4e794b3..df970ca 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -174,7 +174,7 @@ function random_map(seed) noise = noise * d^0.125 -- arbitrary, seems to work good end - -- light shading on edge cells @TODO replace this with a skylight, that can move + -- light shading on edge cells local mask = vec4(0, 0, 0, math.max(((evenq.x - HEX_GRID_WIDTH/2) / HEX_GRID_WIDTH) ^ 2 , ((-evenq.y - HEX_GRID_HEIGHT/2) / HEX_GRID_HEIGHT) ^ 2)) local color = color_at(noise) - mask diff --git a/src/mob.lua b/src/mob.lua index 27e2754..9763d52 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -2,9 +2,26 @@ MOBS = {} +MOB_TYPE = { + BEEPER = 1, +} + MAX_MOB_SIZE = hex_height(HEX_SIZE, ORIENTATION.FLAT) / 2 MOB_SIZE = MAX_MOB_SIZE +MOB_SPECS = { + [MOB_TYPE.BEEPER] = { + health = 10, + speed = 10, + bounty = 5, + hurtbox_radius = MOB_SIZE/2 + } +} + +function get_mob_spec(mob_type) + return MOB_SPECS[mob_type] +end + function mobs_on_hex(hex) local t = {} for mob_index,mob in pairs(MOBS) do @@ -15,8 +32,8 @@ function mobs_on_hex(hex) return t end --- @NOTE returns i,v in the table function mob_on_hex(hex) + -- table.find returns i,v in the table return table.find(MOBS, function(mob) return mob and mob.hex == hex end) @@ -134,7 +151,7 @@ local function update_mob(mob, mob_index) end if mob.frame_target and mob.frame_target == last_frame_hex then - --log('backpedaling') + log('backpedaling') end -- do movement @@ -143,9 +160,7 @@ 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 - -- this is supposed to achieve frame rate independence, but i have no idea if it actually does - -- the constant multiplier at the beginning is how many pixels we want a mob with speed 1 to move in one frame - local rate = 4 * mob.speed / state.perf.avg_fps + local rate = 4 * mob.speed * am.delta_time mob.position = mob.position + math.normalize(hex_to_pixel(mob.frame_target) - mob.position) * rate mob.node.position2d = mob.position @@ -171,21 +186,24 @@ local function make_and_register_mob(mob_type) update_mob ) - --mob.path = get_mob_path(mob, HEX_MAP, mob.hex, HEX_GRID_CENTER) - mob.health = 10 - mob.speed = 10 - mob.bounty = 5 - mob.hurtbox_radius = MOB_SIZE/2 + mob.type = mob_type + + local spec = get_mob_spec(mob_type) + mob.health = spec.health + mob.speed = spec.speed + mob.bounty = spec.bounty + mob.hurtbox_radius = spec.hurtbox_radius register_entity(MOBS, mob) + return mob end -local SPAWN_CHANCE = 45 +local SPAWN_CHANCE = 25 function do_mob_spawning() --if WIN:key_pressed"space" then if math.random(SPAWN_CHANCE) == 1 then --if #MOBS < 1 then - make_and_register_mob() + make_and_register_mob(MOB_TYPE.BEEPER) end end diff --git a/src/projectile.lua b/src/projectile.lua index 2a823e3..d7e6ec6 100644 --- a/src/projectile.lua +++ b/src/projectile.lua @@ -2,7 +2,77 @@ PROJECTILES = {} -local function update_projectile(projectile, projectile_index) +PROJECTILE_TYPE = { + SHELL = 1, + LASER = 2, +} + +PROJECTILE_SPECS = { + [PROJECTILE_TYPE.SHELL] = { + velocity = 13, + damage = 20, + hitbox_radius = 3 + }, + [PROJECTILE_TYPE.LASER] = { + velocity = 25, + damage = 5, + hitbox_radius = 10 + }, +} + + +function get_projectile_velocity(projectile_type) + return PROJECTILE_SPECS[projectile_type].velocity +end +function get_projectile_damage(projectile_type) + return PROJECTILE_SPECS[projectile_type].damage +end +function get_projectile_hitbox_radius(projectile_type) + return PROJECTILE_SPECS[projectile_type].hitbox_radius +end +function get_projectile_spec(projectile_type) + return PROJECTILE_SPECS[projectile_type] +end + +local function update_projectile_shell(projectile, projectile_index) + 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 + + if projectile.props.z <= 0 then + log('exploded cuz we hit da grund') + delete_entity(PROJECTILES, projectile_index) + return true + end + + -- 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. + -- 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) + 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 + end + end + end + + if do_explode then + log('exploded cuz we hit a boi') + 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.node.position2d = projectile.position projectile.hex = pixel_to_hex(projectile.position) @@ -60,16 +130,38 @@ local function update_projectile(projectile, projectile_index) vplay_sfx(SOUNDS.HIT1, 0.5) end -function make_and_register_projectile(hex, vector, velocity, damage, hitbox_radius) +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.SHELL then + return am.circle(vec2(0), 3, COLORS.VERY_DARK_GRAY) + end +end + +function get_projectile_update_function(projectile_type) + if projectile_type == PROJECTILE_TYPE.LASER then + return update_projectile_laser + + elseif projectile_type == PROJECTILE_TYPE.SHELL then + return update_projectile_shell + end +end + +function make_and_register_projectile(hex, projectile_type, vector) local projectile = make_basic_entity(hex - , am.line(vector, vector*hitbox_radius, 3, COLORS.CLARET) - , update_projectile) - projectile.vector = vector - projectile.velocity = velocity - projectile.damage = damage - projectile.hitbox_radius = hitbox_radius + , make_projectile_node(projectile_type, vector) + , get_projectile_update_function(projectile_type)) + projectile.type = projectile_type + projectile.vector = vector + + local spec = get_projectile_spec(projectile_type) + projectile.velocity = spec.velocity + projectile.damage = spec.damage + projectile.hitbox_radius = spec.hitbox_radius register_entity(PROJECTILES, projectile) + return projectile end function delete_all_projectiles() diff --git a/src/tower.lua b/src/tower.lua index 9c7b3aa..7e2c157 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -15,7 +15,7 @@ TOWER_TYPE = { LIGHTHOUSE = 6 } -local TOWER_SPECS = { +TOWER_SPECS = { [TOWER_TYPE.WALL] = { name = "Wall", placement_rules_text = "Place on grass or dirt", @@ -24,17 +24,17 @@ local TOWER_SPECS = { icon_texture = TEXTURES.TOWER_WALL_ICON, cost = 10, range = 0, - props = {} + fire_rate = 2, }, [TOWER_TYPE.HOWITZER] = { name = "HOWITZER", placement_rules_text = "Place on non-Water", short_description = "Fires artillery. Range and cost increase with elevation of terrain underneath.", - texture = TEXTURES.TOWER_SHELL, - icon_texture = TEXTURES.TOWER_SHELL_ICON, + texture = TEXTURES.TOWER_HOWITZER, + icon_texture = TEXTURES.TOWER_HOWITZER_ICON, cost = 20, range = 10, - props = {} + fire_rate = 4, }, [TOWER_TYPE.REDEYE] = { name = "Redeye", @@ -44,7 +44,7 @@ local TOWER_SPECS = { icon_texture = TEXTURES.TOWER_REDEYE_ICON, cost = 20, range = 12, - props = {} + fire_rate = 1, }, [TOWER_TYPE.MOAT] = { name = "Moat", @@ -54,7 +54,7 @@ local TOWER_SPECS = { icon_texture = TEXTURES.TOWER_MOAT_ICON, cost = 10, range = 0, - props = {} + fire_rate = 2, }, [TOWER_TYPE.RADAR] = { name = "Radar", @@ -64,7 +64,7 @@ local TOWER_SPECS = { icon_texture = TEXTURES.TOWER_RADAR_ICON, cost = 20, range = 0, - props = {} + fire_rate = 1, }, [TOWER_TYPE.LIGHTHOUSE] = { name = "Lighthouse", @@ -74,7 +74,7 @@ local TOWER_SPECS = { icon_texture = TEXTURES.TOWER_LIGHTHOUSE_ICON, cost = 20, range = 8, - props = {} + fire_rate = 1, }, } @@ -102,6 +102,9 @@ end function get_tower_range(tower_type) return TOWER_SPECS[tower_type].range end +function get_tower_fire_rate(tower_type) + return TOWER_SPECS[tower_type].fire_rate +end local function make_tower_sprite(tower_type) return pack_texture_into_sprite(get_tower_texture(tower_type), HEX_PIXEL_WIDTH, HEX_PIXEL_HEIGHT) @@ -143,6 +146,9 @@ local function make_tower_node(tower_type) if tower_type == TOWER_TYPE.REDEYE then return make_tower_sprite(tower_type) + elseif tower_type == TOWER_TYPE.HOWITZER then + return make_tower_sprite(tower_type) + elseif tower_type == TOWER_TYPE.LIGHTHOUSE then return am.group( make_tower_sprite(tower_type), @@ -165,7 +171,8 @@ local function make_tower_node(tower_type) end_color_var = vec4(0.1), emission_rate = 4, start_particles = 4, - max_particles = 200 + max_particles = 200, + warmup_time = 5 } ) elseif tower_type == TOWER_TYPE.WALL then @@ -183,6 +190,9 @@ 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 @@ -213,7 +223,19 @@ function tower_type_is_buildable_on(hex, tile, tower_type) local blocked = mobs_blocking or towers_blocking - if tower_type == TOWER_TYPE.REDEYE then + if tower_type == TOWER_TYPE.HOWITZER then + if not mobs_blocking and towers_blocking then + blocked = false + for _,tower in pairs(TOWERS) do + if tower.type ~= TOWER_TYPE.WALL then + blocked = true + break + end + end + end + return not blocked and tile.elevation >= -0.5 + + elseif tower_type == TOWER_TYPE.REDEYE then if not mobs_blocking and towers_blocking then blocked = false for _,tower in pairs(TOWERS) do @@ -249,7 +271,7 @@ function update_tower_redeye(tower, tower_index) if not tower.target_index then for index,mob in pairs(MOBS) do if mob then - local d = math.distance(mob.position, tower.position) / (HEX_SIZE * 2) + local d = math.distance(mob.hex, tower.hex) if d <= tower.range then tower.target_index = index break @@ -260,15 +282,13 @@ function update_tower_redeye(tower, tower_index) if MOBS[tower.target_index] == false then tower.target_index = false - elseif (state.time - tower.last_shot_time) > 1 then + elseif (state.time - tower.last_shot_time) > tower.fire_rate then local mob = MOBS[tower.target_index] make_and_register_projectile( tower.hex, - math.normalize(hex_to_pixel(mob.hex) - tower.position), - 15, - 5, - 10 + PROJECTILE_TYPE.LASER, + math.normalize(mob.position - tower.position) ) tower.last_shot_time = state.time @@ -277,6 +297,38 @@ function update_tower_redeye(tower, tower_index) end end +function update_tower_howitzer(tower, tower_index) + if not tower.target_index then + for index,mob in pairs(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 + else + if MOBS[tower.target_index] == false then + tower.target_index = false + + elseif (state.time - tower.last_shot_time) > tower.fire_rate then + local mob = MOBS[tower.target_index] + + local projectile = make_and_register_projectile( + tower.hex, + PROJECTILE_TYPE.SHELL, + math.normalize(mob.position - tower.position) + ) + + projectile.props.z = tower.props.z + + tower.last_shot_time = state.time + play_sfx(SOUNDS.EXPLOSION2) + end + end +end + 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 @@ -293,6 +345,7 @@ function update_tower_lighthouse(tower, tower_index) if made_it then m.path = path + --[[ local area = spiral_map(tower.hex, tower.range) for _,h in pairs(area) do local node = state.map[h.x][h.y].node"circle" @@ -305,6 +358,7 @@ function update_tower_lighthouse(tower, tower_index) am.tween(node, 0.3, { color = initial_color }) }) end + ]] end end end @@ -319,30 +373,39 @@ function make_and_register_tower(hex, tower_type) ) tower.type = tower_type - tower.cost = get_tower_cost(tower_type) - tower.range = get_tower_range(tower_type) - tower.last_shot_time = tower.TOB -- a tower has never shot if its TOB == its last_shot_time - for k,v in pairs(TOWER_SPECS[tower_type].props) do - tower[k] = v - end + local spec = get_tower_spec(tower_type) + tower.cost = spec.cost + tower.range = spec.range + tower.fire_rate = spec.fire_rate + tower.last_shot_time = -spec.fire_rate if tower_type == TOWER_TYPE.REDEYE then + local tile = state.map.get(hex.x, hex.y) + tile.elevation = tile.elevation + 0.6 + + elseif tower_type == TOWER_TYPE.HOWITZER then + local tile = state.map.get(hex.x, hex.y) + tile.elevation = tile.elevation + 0.6 + 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.5 + tile.elevation = tile.elevation + 0.6 elseif tower_type == TOWER_TYPE.WALL then - state.map.get(hex.x, hex.y).elevation = 0.5 + state.map.get(hex.x, hex.y).elevation = 0.6 elseif tower_type == TOWER_TYPE.MOAT then - state.map.get(hex.x, hex.y).elevation = -0.49 + state.map.get(hex.x, hex.y).elevation = -0.6 elseif tower_type == TOWER_TYPE.RADAR then + local tile = state.map.get(hex.x, hex.y) + tile.elevation = tile.elevation + 0.6 end - register_entity(TOWERS, tower) + return register_entity(TOWERS, tower) end function build_tower(hex, tower_type)