hexyz is tower defense game, and a lua library for dealing with hexagonal grids
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
10 KiB

PROJECTILE_TYPE = {
SHELL = 1,
LASER = 2,
BULLET = 3,
}
local PROJECTILE_SPECS = {
[PROJECTILE_TYPE.SHELL] = {
velocity = 13,
damage = 10,
hitbox_radius = 20
},
[PROJECTILE_TYPE.LASER] = {
velocity = 35,
damage = 20,
hitbox_radius = 20
},
[PROJECTILE_TYPE.BULLET] = {
velocity = 25,
damage = 4,
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 make_shell_explosion_node(source_position)
return am.particles2d{
source_pos = source_position + WORLDSPACE_COORDINATE_OFFSET,
source_pos_var = vec2(4),
start_size = 2,
start_size_var = 1,
end_size = 0,
end_size_var = 0,
angle = 0,
angle_var = math.pi,
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 = 0,
start_particles = 100,
max_particles = 100,
gravity = vec2(0, -10),
warmup_time = 1
}
:action(coroutine.create(function(self)
am.wait(am.delay(3))
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(state.projectiles, projectile_index)
return true
end
projectile.node.position2d = projectile.position
projectile.hex = pixel_to_hex(projectile.position, vec2(HEX_SIZE))
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
-- 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 = hex_spiral_map(projectile.hex, 1)
local mobs = {}
for _,hex in pairs(search_hexes) do
for index,mob in pairs(mobs_on_hex(hex)) do
if mob then
table.insert(mobs, index, mob)
if circles_intersect(mob.position
, projectile.position
, mob.hurtbox_radius
, projectile.hitbox_radius) then
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
end
end
end
local tile = hex_map_get(state.map, projectile.hex)
if tile and tile.elevation >= projectile.props.z then
--do_explode = true
elseif projectile.props.z <= 0 then
--do_explode = true
end
if do_explode then
for index,mob in pairs(mobs) do
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(state.projectiles, projectile_index)
return true
end
end
local function update_projectile_laser(projectile, projectile_index)
projectile.position = projectile.position + projectile.vector * projectile.velocity
-- check if we're out of bounds
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(state.projectiles, projectile_index)
return true
end
projectile.node.position2d = projectile.position
projectile.hex = pixel_to_hex(projectile.position, vec2(HEX_SIZE))
-- 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 search_hexes = hex_spiral_map(projectile.hex, 1)
local hit_mob_count = 0
local hit_mobs = {}
for _,hex in pairs(search_hexes) do
-- check if there's a mob on the hex
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
table.insert(hit_mobs, mob_index, mob)
hit_mob_count = hit_mob_count + 1
end
end
end
-- we didn't hit anyone
if hit_mob_count == 0 then return end
-- we could have hit multiple, (optionally) find the closest
local closest_mob_index, closest_mob = next(hit_mobs, nil)
local closest_d = math.distance(closest_mob.position, projectile.position)
for _mob_index,mob in pairs(hit_mobs) do
local d = math.distance(mob.position, projectile.position)
if d < closest_d then
closest_mob_index = _mob_index
closest_mob = mob
closest_d = d
end
end
-- hit the mob, affect the world
do_hit_mob(closest_mob, projectile.damage, closest_mob_index)
vplay_sfx(SOUNDS.HIT1, 0.5)
end
local function update_projectile_bullet(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(state.projectiles, projectile_index)
return true
end
projectile.node.position2d = projectile.position
projectile.hex = pixel_to_hex(projectile.position, vec2(HEX_SIZE))
local search_hexes = hex_spiral_map(projectile.hex, 1)
local hit_mob_count = 0
local hit_mobs = {}
for _,hex in pairs(search_hexes) do
-- check if there's a mob on the hex
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
table.insert(hit_mobs, mob_index, mob)
hit_mob_count = hit_mob_count + 1
end
end
end
-- we didn't hit anyone
if hit_mob_count == 0 then return end
-- we could have hit multiple, (optionally) find the closest
local closest_mob_index, closest_mob = next(hit_mobs, nil)
local closest_d = math.distance(closest_mob.position, projectile.position)
for _mob_index,mob in pairs(hit_mobs) do
local d = math.distance(mob.position, projectile.position)
if d < closest_d then
closest_mob_index = _mob_index
closest_mob = mob
closest_d = d
end
end
-- hit the mob, affect the world
do_hit_mob(closest_mob, projectile.damage, closest_mob_index)
vplay_sfx(SOUNDS.HIT1, 0.5)
end
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.BULLET then
return am.circle(vec2(0), 2, COLORS.VERY_DARK_GRAY)
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.BULLET then
return update_projectile_bullet
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,
get_projectile_update_function(projectile_type)
)
projectile.type = projectile_type
projectile.node = am.translate(projectile.position) ^ make_projectile_node(projectile_type, vector)
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(state.projectiles, projectile)
return projectile
end
function projectile_serialize(projectile)
local serialized = entity_basic_devectored_copy(projectile)
serialized.vector = { serialized.vector.x, serialized.vector.y }
return am.to_json(serialized)
end
function projectile_deserialize(json_string)
local projectile = entity_basic_json_parse(json_string)
projectile.vector = vec2(projectile.vector[1], projectile.vector[2])
projectile.update = get_projectile_update_function(projectile.type)
projectile.node = am.translate(projectile.position)
^ make_projectile_node(projectile.type, projectile.vector)
return projectile
end
function do_projectile_updates()
for projectile_index,projectile in pairs(state.projectiles) do
if projectile and projectile.update then
projectile.update(projectile, projectile_index)
end
end
end