diff --git a/res/mob2_1.png b/res/mob2_1.png new file mode 100644 index 0000000..53a7e49 Binary files /dev/null and b/res/mob2_1.png differ diff --git a/src/color.lua b/src/color.lua index f5d288b..582d3fd 100644 --- a/src/color.lua +++ b/src/color.lua @@ -5,14 +5,16 @@ COLORS = { -- tones WHITE = vec4(0.8, 0.8, 0.7, 1), - BLACK = vec4(0, 0, 0, 1), + BLACK = vec4(0, 0, 0.02, 1), TRUE_BLACK = vec4(0, 0, 0, 1), + -- non-standard hues + WATER = vec4(0.12, 0.3, 0.3, 1), + GRASS = vec4(0.10, 0.25, 0.10, 1), + DIRT = vec4(0.22, 0.20, 0.10, 1), + MOUNTAIN = vec4(0.45, 0.30, 0.20, 1), + -- hues - BLUE_STONE = vec4(0.12, 0.3, 0.3, 1), - MYRTLE = vec4(0.10, 0.25, 0.10, 1), - BROWN_POD = vec4(0.25, 0.20, 0.10, 1), - BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1), MAGENTA = vec4(1, 0, 1, 1), TEAL = vec4(16/255, 126/255, 124/244, 1), YALE_BLUE = vec4(4/255, 75/255, 127/255, 1), diff --git a/src/entity.lua b/src/entity.lua new file mode 100644 index 0000000..d1c490d --- /dev/null +++ b/src/entity.lua @@ -0,0 +1,64 @@ + +ENTITY_TYPE = { + ENTITY = 0, + MOB = 1, + TOWER = 2, + PROJECTILE = 3 +} + +ENTITIES = {} +-- entity structure: +-- { +-- TOB - number - time of birth, const +-- +-- hex - vec2 - current occupied hex, if any +-- position - vec2 - current pixel position of it's translate (forced parent) node +-- update - function - runs every frame with itself and its index as an argument +-- node - node - scene graph node +-- } +-- +-- mob(entity) structure: +-- { +-- path - 2d table - map of hexes to other hexes, forms a path +-- speed - number - multiplier on distance travelled per frame, up to the update function to use correctly +-- } +-- +-- tower(entity) structure: +-- { +-- -- @NOTE these should probably be wrapped in a 'weapon' struct or something, so towers can have multiple weapons +-- range - number - distance it can shoot +-- last_shot_time - number - timestamp (seconds) of last time it shot +-- target_index - number - index of entity it is currently shooting +-- } +-- +-- bullet/projectile structure +-- { +-- } +-- +function make_and_register_entity(type_, hex, node, update) + local entity = {} + + entity.type = type_ + entity.TOB = TIME + entity.hex = hex + entity.position = hex_to_pixel(hex) + entity.update = update or function() log("unimplemented update function!") end + entity.node = am.translate(entity.position) ^ node + + table.insert(ENTITIES, entity) + WIN.scene"world":append(entity.node) + return entity +end + +function delete_entity(index) + WIN.scene"world":remove(ENTITIES[index].node) + ENTITIES[index] = nil +end + +function do_entity_updates() + for index,entity in pairs(ENTITIES) do + entity.update(entity, index) + end +end + + diff --git a/src/grid.lua b/src/grid.lua index 26ca933..309d5c2 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -6,9 +6,10 @@ require "hexyz" HEX_SIZE = 20 -- with 1920x1080, this is the minimal dimensions to cover the screen (65x33) +-- @NOTE added 2 cell padding, because we terraform the very outer edge and it looks ugly -- odd numbers are important because we want a 'true' center -HEX_GRID_WIDTH = 65 -HEX_GRID_HEIGHT = 33 +HEX_GRID_WIDTH = 67 +HEX_GRID_HEIGHT = 35 HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT) -- leaving y == 0 makes this the center in hex coordinates @@ -29,7 +30,6 @@ end GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions() - HEX_GRID_INTERACTABLE_REGION_PADDING = 2 function is_interactable(tile, evenq) return point_in_rect(evenq, { @@ -44,25 +44,27 @@ function is_passable(tile, mob) return tile.elevation > -0.5 and tile.elevation < 0.5 end --- map elevation to appropriate tile color. +-- map elevation to appropriate color function color_at(elevation) - if elevation < -0.5 then -- lowest elevation : impassable - return COLORS.BLUE_STONE{ a = (elevation + 1.4) / 2 + 0.2 } + if elevation < -0.5 then -- lowest elevation + return COLORS.WATER{ a = (elevation + 1.4) / 2 + 0.2 } - elseif elevation < 0 then -- med-low elevation : passable - return math.lerp(COLORS.MYRTLE, COLORS.BROWN_POD, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.2 } + elseif elevation < 0 then -- med-low elevation + return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.2 } - elseif elevation < 0.5 then -- med-high elevation : passable - return math.lerp(COLORS.MYRTLE, COLORS.BROWN_POD, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.2 } + elseif elevation < 0.5 then -- med-high elevation + return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.2 } - elseif elevation < 1 then -- highest elevation : impassable - return COLORS.BOTTLE_GREEN{ a = (elevation + 1.0) / 2 + 0.2 } + elseif elevation < 1 then -- high elevation + return COLORS.MOUNTAIN{ ra = elevation } else log('bad elevation'); return vec4(0) end end +-- hex_neighbours returns all coordinate positions that could be valid for a map extending infinite in all directions +-- grid_neighbours only gets you the neighbours that are actually in the grid function grid_neighbours(map, hex) return table.filter(hex_neighbours(hex), function(_hex) return map.get(_hex.x, _hex.y) @@ -76,9 +78,24 @@ function random_map(seed) local world = am.group():tag"world" for i,_ in pairs(map) do for j,noise in pairs(map[i]) do - local off = hex_to_evenq(vec2(i, j)) - local mask = vec4(0, 0, 0, math.max(((off.x - HEX_GRID_DIMENSIONS.x/2) / HEX_GRID_DIMENSIONS.x) ^ 2 - , ((-off.y - HEX_GRID_DIMENSIONS.y/2) / HEX_GRID_DIMENSIONS.y) ^ 2)) + local evenq = hex_to_evenq(vec2(i, j)) + + -- check if we're on an edge -- terraform edges to be passable + if evenq.x == 0 or evenq.x == (HEX_GRID_WIDTH - 1) + or -evenq.y == 0 or -evenq.y == (HEX_GRID_HEIGHT - 1) then + noise = 0 + + else + -- scale noise to be closer to 0 the closer we are to the center + -- @NOTE i don't know if this 100% of the time makes the center tile passable, but it probably does 99.9+% of the time + local nx, ny = evenq.x/HEX_GRID_WIDTH - 0.5, -evenq.y/HEX_GRID_HEIGHT - 0.5 + local d = math.sqrt(nx^2 + ny^2) / math.sqrt(0.5) + noise = noise * d^0.125 -- arbitrary, seems to work good + end + + -- 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 local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6) @@ -92,15 +109,6 @@ function random_map(seed) end end - -- the center of the map in some radius is always considered 'passable' terrain and is home base - -- terraform this area to ensure it's passable - -- @NOTE no idea why the y-coord doesn't need to be transformed - -- @TODO @FIXME also terraform the edges of the map to be passable - it is theoretically possible to get maps where mobs can be stuck from the very beginning - local home = spiral_map(HEX_GRID_CENTER, 3) - for _,hex in pairs(home) do - map[hex.x][hex.y].elevation = 0 - map[hex.x][hex.y].node.color = color_at(0) - end world:append(am.circle(hex_to_pixel(HEX_GRID_CENTER), HEX_SIZE/2, COLORS.MAGENTA, 4)) WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2 diff --git a/src/hexyz.lua b/src/hexyz.lua index e8a993d..59d3a32 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -426,59 +426,32 @@ end -- PATHFINDING --- maps each hex of |map| to a numeric value, which is its distance from |start| --- returns this new map -function breadth_first_distance(map, start, neighbour_f) - local neighbour_f = neighbour_f or function(map, hex) return hex_neighbours(hex) end - - local frontier = { start } - - local distance = {} - distance[start.x] = {} - distance[start.x][start.y] = 0 +--[[ @TODO bad breadth first + local frontier = { _tower.hex } + local history = {} + history[_tower.hex.x] = {} + history[_tower.hex.x][_tower.hex.y] = true while not (#frontier == 0) do local current = table.remove(frontier, 1) - for _,next_ in pairs(neighbour_f(map, current)) do - if not map_get(distance, next_.x, next_.y) then - table.insert(frontier, next_) - map_set(distance, next_.x, next_.y, 1 + map_get(distance, current.x, current.y)) + for _,neighbour in pairs(grid_neighbours(HEX_MAP, _tower.hex)) do + if not (history[neighbour.x] and history[neighbour.x][neighbour.y]) then + local mob = mob_on_hex(neighbour) + if mob then + _tower.target = mob + break + end + table.insert(frontier, neighbour) end end - end - - return setmetatable(distance, { __index = { - get = function(x, y) return map_get(distance, x, y) end - }}) -end - --- maps each hex of |map| to an adjacent cell that is on the path to |start|, or nil if no path exists --- returns this new map -function breadth_first_vectorfield(map, start, neighbour_f) - local neighbour_f = neighbour_f or function(map, hex) return hex_neighbours(hex) end - - local frontier = { start } - local came_from = {} - came_from[start.x] = {} - came_from[start.x][start.y] = false - - while not (#frontier == 0) do - local current = table.remove(frontier, 1) - - for _,next_ in pairs(neighbour_f(map, current)) do - if not map_get(came_from, next_.x, next_.y) then - table.insert(frontier, next_) - map_set(came_from, next_.x, next_.y, current) - end + if _tower.target then + log(_tower.target) + break end end - - came_from.get = function(x, y) return map_get(came_from, x, y) end - - return came_from -end +]] -- generic A* pathfinding -- @@ -511,7 +484,7 @@ function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f) end for _,next_ in pairs(neighbour_f(map, current.hex)) do - local new_cost = map_get(path_so_far, current.hex.x, current.hex.y) + cost_f(current, next_) + local new_cost = map_get(path_so_far, current.hex.x, current.hex.y) + cost_f(current.hex, next_) local next_cost = map_get(path_so_far, next_.x, next_.y) if not next_cost or new_cost < next_cost then diff --git a/src/main.lua b/src/main.lua index 1a3ea63..09dcf6f 100644 --- a/src/main.lua +++ b/src/main.lua @@ -6,16 +6,20 @@ math.random() math.random() require "color" +require "entity" require "grid" require "mob" +require "projectile" require "tower" -- Globals -WIN = am.window{ width = 1920, height = 1080 } +WIN = am.window{ width = 1920, height = 1080, title = "hexyz" } + +OFF_SCREEN = vec2(WIN.width * 2) -- arbitrary pixel position that is garunteed to be off screen TIME = 0 SCORE = 0 -OFF_SCREEN = vec2(WIN.width * 2) -- random pixel position that is garunteed to be off screen +MOUSE = vec2(0) local COORDINATE_DISPLAY_TYPES = { CENTERED_EVENQ = 0, @@ -25,31 +29,37 @@ local COORDINATE_DISPLAY_TYPES = { local COORDINATE_DISPLAY_TYPE = COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ local function game_action(scene) - TIME = am.current_time() - SCORE = TIME - - local mouse = WIN:mouse_position() - local hex = pixel_to_hex(mouse - WORLDSPACE_COORDINATE_OFFSET) - local rounded_mouse = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET - local evenq = hex_to_evenq(hex) - local centered_evenq = evenq{ y = -evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2) - , math.floor(HEX_GRID_HEIGHT/2)) + TIME = am.current_time() + SCORE = TIME + MOUSE = WIN:mouse_position() + + local hex = pixel_to_hex(MOUSE - WORLDSPACE_COORDINATE_OFFSET) + local rounded_mouse = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET + local evenq = hex_to_evenq(hex) + local centered_evenq = evenq{ y = -evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2) + , math.floor(HEX_GRID_HEIGHT/2)) local tile = HEX_MAP.get(hex.x, hex.y) local hot = is_interactable(tile, evenq{ y = -evenq.y }) + do_mob_spawning() + do_entity_updates() + if WIN:mouse_pressed"left" then if hot and is_buildable(hex, tile, nil) then - make_tower(hex) + make_and_register_tower(hex) end end - if WIN:key_pressed"f3" then + if WIN:key_pressed"escape" then + WIN.scene = game_scene() + + elseif WIN:key_pressed"f3" then COORDINATE_DISPLAY_TYPE = (COORDINATE_DISPLAY_TYPE + 1) % #table.keys(COORDINATE_DISPLAY_TYPES) - end - do_tower_updates() - do_mob_updates() - do_mob_spawning() + elseif WIN:key_pressed"f4" then + log(HEX_MAP.seed) + print(HEX_MAP.seed) + end if tile and hot then WIN.scene"hex_cursor".center = rounded_mouse @@ -94,7 +104,8 @@ local function toolbelt() return toolbelt end -local function game_scene() +-- @NOTE must be global to allow the game_action to reference it +function game_scene() local score = am.translate(WIN.left + 10, WIN.top - 20) ^ am.text("", "left"):tag"score" local coords = am.translate(WIN.right - 10, WIN.top - 20) ^ am.text("", "right"):tag"coords" local hex_cursor = am.circle(OFF_SCREEN, HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor" @@ -122,12 +133,8 @@ local function game_scene() return scene end -local function init() - require "texture" - load_textures() - WIN.scene = am.scale(1) ^ game_scene() -end - -init() +require "texture" +load_textures() +WIN.scene = am.scale(1) ^ game_scene() noglobals() diff --git a/src/mob.lua b/src/mob.lua index 87cf6d9..57a2280 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -1,54 +1,37 @@ -local MOBS = {} ---[[ - mob structure: - { - TOB - float -- time stamp in seconds of when the tower was spawned - hex - vec2 -- hexagon the mob is on - position - vec2 -- true pixel coordinates - node - node -- the root graph node for this mob - update - function -- function that gets called every frame with itself as an argument - path - 2d table -- map of hexes to other hexes, forms a path - speed - number -- multiplier on distance travelled per frame, up to the update function to use correctly - } -]] - require "extra" require "sound" +function mob_die(mob, entity_index) + WIN.scene"world":action( + am.play(am.sfxr_synth(SOUNDS.EXPLOSION1), false, math.random() + 0.5) + ) + delete_entity(entity_index) +end -local MOB_UPDATES = { - BEEPER = function(mob, index) - mob.hex = pixel_to_hex(mob.position) - - local frame_target = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y] - - if frame_target then - mob.position = mob.position + math.normalize(hex_to_pixel(frame_target.hex) - mob.position) * mob.speed - mob.node.position2d = mob.position +-- @NOTE returns i,v in the table +function mob_on_hex(hex) + return table.find(ENTITIES, function(entity) + return entity.type == ENTITY_TYPE.MOB and entity.hex == hex + end) +end - else - if mob.hex == HEX_GRID_CENTER then - WIN.scene"world":action( - am.play(am.sfxr_synth(SOUNDS.EXPLOSION1), false, math.random() + 0.5) - ) +function do_hit_mob(mob, damage, index) + mob.health = mob.health - damage - table.remove(MOBS, index) - WIN.scene"world":remove(mob.node) - else - log("stuck") - end - end + if mob.health < 1 then + mob_die(mob, index) + end +end - -- passive animation - if math.random() < 0.01 then - mob.node"rotate":action(am.tween(0.3, { angle = mob.node"rotate".angle + math.pi*3 })) - else - mob.node"rotate".angle = math.wrapf(mob.node"rotate".angle + am.delta_time, math.pi*2) +function check_for_broken_mob_pathing(hex) + for _,entity in pairs(ENTITIES) do + if entity.type == ENTITY_TYPE.MOB and entity.path[hex.x] and entity.path[hex.x][hex.y] then + entity.path = get_mob_path(entity, HEX_MAP, entity.hex, HEX_GRID_CENTER) end end -} +end -- check if a the tile at |hex| is passable by |mob| local function mob_can_pass_through(mob, hex) @@ -56,8 +39,11 @@ local function mob_can_pass_through(mob, hex) return tile and tile.elevation < 0.5 and tile.elevation > -0.5 end -local function get_mob_path(map, start, goal, mob) - return Astar(map, goal, start, -- goal and start are switched intentionally +-- @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 get_mob_path(mob, map, start, goal) + return Astar(map, goal, start, -- neighbour function function(map, hex) return table.filter(grid_neighbours(map, hex), function(_hex) @@ -70,13 +56,13 @@ local function get_mob_path(map, start, goal, mob) -- cost function function(from, to) - return math.abs(map.get(to.x, to.y).elevation) + return math.abs(map.get(from.x, from.y).elevation - map.get(to.x, to.y).elevation) end ) end -- @FIXME there's a bug here where the position of the spawn hex is sometimes 1 closer to the center than we want -local function get_spawn_hex(mob) +local function get_spawn_hex() local spawn_hex repeat -- ensure we spawn on an random tile along the map's edges @@ -100,48 +86,61 @@ local function get_spawn_hex(mob) spawn_hex = evenq_to_hex(vec2(x, -y)) local tile = HEX_MAP[spawn_hex.x][spawn_hex.y] - until mob_can_pass_through(mob, spawn_hex) + until is_passable(tile) return spawn_hex end -local function make_mob() - local mob = {} +local function make_and_register_mob() + local mob = make_and_register_entity( + -- type + ENTITY_TYPE.MOB, - mob.TOB = TIME - mob.update = MOB_UPDATES.BEEPER - mob.hex = get_spawn_hex(mob) - mob.position = hex_to_pixel(mob.hex) - mob.path = get_mob_path(HEX_MAP, mob.hex, HEX_GRID_CENTER, mob) - mob.speed = 10 + -- hex spawn position + get_spawn_hex(), - mob.node = am.translate(mob.position) - ^ am.scale(2) - ^ am.rotate(mob.TOB) - ^ pack_texture_into_sprite(TEX_MOB1_1, 20, 20) + -- node + am.scale(2) + ^ am.rotate(TIME) + ^ pack_texture_into_sprite(TEX_MOB1_1, 20, 20), - WIN.scene"world":append(mob.node) + -- update + function(_mob, _mob_index) + _mob.hex = pixel_to_hex(_mob.position) - return mob -end + local frame_target = _mob.path[_mob.hex.x] and _mob.path[_mob.hex.x][_mob.hex.y] -function mob_on_hex(hex) - return table.find(MOBS, function(mob) - return mob.hex == hex - end) + if frame_target then + _mob.position = _mob.position + math.normalize(hex_to_pixel(frame_target.hex) - _mob.position) * _mob.speed + _mob.node.position2d = _mob.position + + else + if _mob.hex == HEX_GRID_CENTER then + mob_die(_mob, index) + else + log("stuck") + end + end + + -- passive animation + if math.random() < 0.01 then + _mob.node"rotate":action(am.tween(0.3, { angle = _mob.node"rotate".angle + math.pi*3 })) + else + _mob.node"rotate".angle = math.wrapf(_mob.node"rotate".angle + am.delta_time, math.pi*2) + end + end + ) + + mob.path = get_mob_path(mob, HEX_MAP, mob.hex, HEX_GRID_CENTER) + mob.health = 10 + mob.speed = 1 end local SPAWN_CHANCE = 50 function do_mob_spawning() --if WIN:key_pressed"space" then if math.random(SPAWN_CHANCE) == 1 then - table.insert(MOBS, make_mob()) - end -end - -function do_mob_updates() - for i,mob in pairs(MOBS) do - mob.update(mob, i) + make_and_register_mob() end end diff --git a/src/projectile.lua b/src/projectile.lua new file mode 100644 index 0000000..b3fd279 --- /dev/null +++ b/src/projectile.lua @@ -0,0 +1,33 @@ + + + +function make_and_register_projectile(hex, vector, velocity) + local projectile = make_and_register_entity( + -- type + ENTITY_TYPE.PROJECTILE, + + hex, + + -- node + am.line(vector, vector * 4, 2, COLORS.CLARET), + + function(_projectile, _projectile_index) + _projectile.position = _projectile.position + vector * velocity + _projectile.node.position2d = _projectile.position + _projectile.hex = pixel_to_hex(_projectile.position) + + local mob_index,mob = mob_on_hex(_projectile.hex) + if mob and (math.distance(mob.position, _projectile.position) > _projectile.hitbox_radius) then + do_hit_mob(mob, _projectile.damage, mob_index) + delete_entity(_projectile_index) + WIN.scene"world":action(am.play(am.sfxr_synth(SOUNDS.HIT1))) + end + end + ) + + projectile.vector = vector + projectile.velocity = velocity + projectile.damage = 5 + projectile.hitbox_radius = 10 +end + diff --git a/src/scratch b/src/scratch new file mode 100644 index 0000000..fbd86f7 --- /dev/null +++ b/src/scratch @@ -0,0 +1,5 @@ + + +x = y - floor(z/2) + +j + floor(z/2) = j2 diff --git a/src/sound.lua b/src/sound.lua index bbc67b7..ee99515 100644 --- a/src/sound.lua +++ b/src/sound.lua @@ -2,6 +2,8 @@ SOUNDS = { EXPLOSION1 = 49179102, -- this slowed sounds metal as fuck EXPLOSION2 = 19725402, + EXPLOSION3 = 69338002, + HIT1 = 25811004, LASER1 = 79859301, PUSH1 = 30455908, BIRD1 = 50838307, diff --git a/src/texture.lua b/src/texture.lua index 296ac99..56fbcf9 100644 --- a/src/texture.lua +++ b/src/texture.lua @@ -1,4 +1,5 @@ + function load_textures() TEX_MARQUIS = am.texture2d("../res/marquis.png") @@ -6,8 +7,10 @@ function load_textures() TEX_WALL_CLOSED = am.texture2d("../res/wall_closed.png") TEX_TOWER1 = am.texture2d("../res/tower1.png") + TEX_TOWER2 = am.texture2d("../res/tower2.png") TEX_MOB1_1 = am.texture2d("../res/mob1_1.png") + TEX_MOB2_1 = am.texture2d("../res/mob2_1.png") end function pack_texture_into_sprite(texture, width, height) diff --git a/src/tower.lua b/src/tower.lua index 17e6ec3..fe396df 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -1,43 +1,59 @@ -local TOWERS = {} ---[[ - tower structure: - { - TOB - float -- time stamp in seconds of when the tower was spawned - hex - vec2 -- hexagon the tower is on - position - vec2 -- true pixel coordinates - node - node -- the root graph node for this tower - update - function -- function that gets called every frame with itself as an argument - } -]] function is_buildable(hex, tile, tower) local blocked = mob_on_hex(hex) return not blocked and is_passable(tile) end -function make_tower(hex) - local tower = {} - - tower.TOB = TIME - tower.hex = hex - tower.position = hex_to_pixel(tower.hex) - tower.node = am.translate(tower.position) - ^ pack_texture_into_sprite(TEX_TOWER1, 55, 55) - - tower.update = function(_tower) end +function make_and_register_tower(hex) + local tower = make_and_register_entity( + -- type + ENTITY_TYPE.TOWER, + + -- spawning hex + hex, + + -- node + pack_texture_into_sprite(TEX_TOWER2, 45, 34), + + -- update function + function(_tower, _tower_index) + if not _tower.target_index then + for index,entity in pairs(ENTITIES) do + if entity and entity.type == ENTITY_TYPE.MOB then + local d = math.distance(entity.hex, _tower.hex) + if d <= _tower.range then + _tower.target_index = index + break + end + end + end + else + if ENTITIES[_tower.target_index] == nil then + _tower.target_index = false + + elseif (TIME - _tower.last_shot_time) > 1 then + local entity = ENTITIES[_tower.target_index] + + make_and_register_projectile( + _tower.hex, + math.normalize(hex_to_pixel(entity.hex) - _tower.position), + 5 + ) + + _tower.last_shot_time = TIME + _tower.node:action(am.play(am.sfxr_synth(SOUNDS.EXPLOSION3))) + end + end + end + ) + + tower.range = 10 + tower.last_shot_time = tower.TOB + tower.target_index = false -- make this cell impassable - --HEX_MAP.get(hex.x, hex.y).elevation = 2 - - WIN.scene"world":append(tower.node) - - return tower -end - -function do_tower_updates() - for i,tower in pairs(TOWERS) do - tower.update(tower, i) - end + HEX_MAP[hex.x][hex.y].elevation = 2 + check_for_broken_mob_pathing(hex) end