Browse Source

towers shoot n stuff

master
Nicholas Hayashi 4 years ago
parent
commit
c45f266831
  1. BIN
      res/mob2_1.png
  2. 12
      src/color.lua
  3. 64
      src/entity.lua
  4. 56
      src/grid.lua
  5. 63
      src/hexyz.lua
  6. 57
      src/main.lua
  7. 141
      src/mob.lua
  8. 33
      src/projectile.lua
  9. 5
      src/scratch
  10. 2
      src/sound.lua
  11. 3
      src/texture.lua
  12. 80
      src/tower.lua

BIN
res/mob2_1.png

After

Width: 40  |  Height: 40  |  Size: 558 B

12
src/color.lua

@ -5,14 +5,16 @@ COLORS = {
-- tones -- tones
WHITE = vec4(0.8, 0.8, 0.7, 1), 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), 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 -- 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), MAGENTA = vec4(1, 0, 1, 1),
TEAL = vec4(16/255, 126/255, 124/244, 1), TEAL = vec4(16/255, 126/255, 124/244, 1),
YALE_BLUE = vec4(4/255, 75/255, 127/255, 1), YALE_BLUE = vec4(4/255, 75/255, 127/255, 1),

64
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

56
src/grid.lua

@ -6,9 +6,10 @@ require "hexyz"
HEX_SIZE = 20 HEX_SIZE = 20
-- with 1920x1080, this is the minimal dimensions to cover the screen (65x33) -- 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 -- 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) HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT)
-- leaving y == 0 makes this the center in hex coordinates -- leaving y == 0 makes this the center in hex coordinates
@ -29,7 +30,6 @@ end
GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions() GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions()
HEX_GRID_INTERACTABLE_REGION_PADDING = 2 HEX_GRID_INTERACTABLE_REGION_PADDING = 2
function is_interactable(tile, evenq) function is_interactable(tile, evenq)
return point_in_rect(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 return tile.elevation > -0.5 and tile.elevation < 0.5
end end
-- map elevation to appropriate tile color.
-- map elevation to appropriate color
function color_at(elevation) 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 else
log('bad elevation'); return vec4(0) log('bad elevation'); return vec4(0)
end end
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) function grid_neighbours(map, hex)
return table.filter(hex_neighbours(hex), function(_hex) return table.filter(hex_neighbours(hex), function(_hex)
return map.get(_hex.x, _hex.y) return map.get(_hex.x, _hex.y)
@ -76,9 +78,24 @@ function random_map(seed)
local world = am.group():tag"world" local world = am.group():tag"world"
for i,_ in pairs(map) do for i,_ in pairs(map) do
for j,noise in pairs(map[i]) 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 color = color_at(noise) - mask
local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6) local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6)
@ -92,15 +109,6 @@ function random_map(seed)
end end
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)) world:append(am.circle(hex_to_pixel(HEX_GRID_CENTER), HEX_SIZE/2, COLORS.MAGENTA, 4))
WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2 WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2

63
src/hexyz.lua

@ -426,59 +426,32 @@ end
-- PATHFINDING -- 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 while not (#frontier == 0) do
local current = table.remove(frontier, 1) 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 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
end end
came_from.get = function(x, y) return map_get(came_from, x, y) end
return came_from
end
]]
-- generic A* pathfinding -- generic A* pathfinding
-- --
@ -511,7 +484,7 @@ function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f)
end end
for _,next_ in pairs(neighbour_f(map, current.hex)) do 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) local next_cost = map_get(path_so_far, next_.x, next_.y)
if not next_cost or new_cost < next_cost then if not next_cost or new_cost < next_cost then

57
src/main.lua

@ -6,16 +6,20 @@ math.random()
math.random() math.random()
require "color" require "color"
require "entity"
require "grid" require "grid"
require "mob" require "mob"
require "projectile"
require "tower" require "tower"
-- Globals -- 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 TIME = 0
SCORE = 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 = { local COORDINATE_DISPLAY_TYPES = {
CENTERED_EVENQ = 0, CENTERED_EVENQ = 0,
@ -25,31 +29,37 @@ local COORDINATE_DISPLAY_TYPES = {
local COORDINATE_DISPLAY_TYPE = COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ local COORDINATE_DISPLAY_TYPE = COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ
local function game_action(scene) 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 tile = HEX_MAP.get(hex.x, hex.y)
local hot = is_interactable(tile, evenq{ y = -evenq.y }) local hot = is_interactable(tile, evenq{ y = -evenq.y })
do_mob_spawning()
do_entity_updates()
if WIN:mouse_pressed"left" then if WIN:mouse_pressed"left" then
if hot and is_buildable(hex, tile, nil) then if hot and is_buildable(hex, tile, nil) then
make_tower(hex)
make_and_register_tower(hex)
end end
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) 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 if tile and hot then
WIN.scene"hex_cursor".center = rounded_mouse WIN.scene"hex_cursor".center = rounded_mouse
@ -94,7 +104,8 @@ local function toolbelt()
return toolbelt return toolbelt
end 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 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 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" 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 return scene
end 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() noglobals()

141
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 "extra"
require "sound" 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 end
}
end
-- check if a the tile at |hex| is passable by |mob| -- check if a the tile at |hex| is passable by |mob|
local function mob_can_pass_through(mob, hex) 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 return tile and tile.elevation < 0.5 and tile.elevation > -0.5
end 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 -- neighbour function
function(map, hex) function(map, hex)
return table.filter(grid_neighbours(map, hex), function(_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 -- cost function
function(from, to) 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
) )
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 -- @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 local spawn_hex
repeat repeat
-- ensure we spawn on an random tile along the map's edges -- 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)) spawn_hex = evenq_to_hex(vec2(x, -y))
local tile = HEX_MAP[spawn_hex.x][spawn_hex.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 return spawn_hex
end 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 end
local SPAWN_CHANCE = 50 local SPAWN_CHANCE = 50
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
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
end end

33
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

5
src/scratch

@ -0,0 +1,5 @@
x = y - floor(z/2)
j + floor(z/2) = j2

2
src/sound.lua

@ -2,6 +2,8 @@
SOUNDS = { SOUNDS = {
EXPLOSION1 = 49179102, -- this slowed sounds metal as fuck EXPLOSION1 = 49179102, -- this slowed sounds metal as fuck
EXPLOSION2 = 19725402, EXPLOSION2 = 19725402,
EXPLOSION3 = 69338002,
HIT1 = 25811004,
LASER1 = 79859301, LASER1 = 79859301,
PUSH1 = 30455908, PUSH1 = 30455908,
BIRD1 = 50838307, BIRD1 = 50838307,

3
src/texture.lua

@ -1,4 +1,5 @@
function load_textures() function load_textures()
TEX_MARQUIS = am.texture2d("../res/marquis.png") TEX_MARQUIS = am.texture2d("../res/marquis.png")
@ -6,8 +7,10 @@ function load_textures()
TEX_WALL_CLOSED = am.texture2d("../res/wall_closed.png") TEX_WALL_CLOSED = am.texture2d("../res/wall_closed.png")
TEX_TOWER1 = am.texture2d("../res/tower1.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_MOB1_1 = am.texture2d("../res/mob1_1.png")
TEX_MOB2_1 = am.texture2d("../res/mob2_1.png")
end end
function pack_texture_into_sprite(texture, width, height) function pack_texture_into_sprite(texture, width, height)

80
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) function is_buildable(hex, tile, tower)
local blocked = mob_on_hex(hex) local blocked = mob_on_hex(hex)
return not blocked and is_passable(tile) return not blocked and is_passable(tile)
end 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 -- 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 end
Loading…
Cancel
Save