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
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
|
|
|