Browse Source

load towers from specfile, build broken

master
Nicholas Hayashi 3 years ago
parent
commit
aae662bef1
  1. 80
      data/towers.lua
  2. 4
      main.lua
  3. 4
      src/entity.lua
  4. 2
      src/extra.lua
  5. 17
      src/game.lua
  6. 2
      src/memory.lua
  7. 224
      src/tower.lua

80
data/towers.lua

@ -23,12 +23,16 @@
| | | order matters - two weapons share a 'choke' value, and both | | | | order matters - two weapons share a 'choke' value, and both |
| | | could acquire a target in a frame, the first one is choosen. | | | | could acquire a target in a frame, the first one is choosen. |
| | | | | | | |
| placement_rules | table | @TODO |
| placement_f | function |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| visual_range | number | when the tower has multiple weapons, what range represents the |
| | | overall range of the tower. default is calculated on load as |
| | | the largest range among the weapons the tower has. |
| min_visual_range | number | same as above but the largest minimum range among weapons |
| | | | | | | |
| update_f | function | default value is complicated @TODO | | update_f | function | default value is complicated @TODO |
| grow_f | function | default value is false/nil. @TODO | | grow_f | function | default value is false/nil. @TODO |
@ -73,8 +77,6 @@ return {
range = 0, range = 0,
fire_rate = 2, fire_rate = 2,
update = false, update = false,
placement_rules = {
}
}, },
{ {
name = "Gattler", name = "Gattler",
@ -83,20 +85,17 @@ return {
texture = TEXTURES.TOWER_GATTLER, texture = TEXTURES.TOWER_GATTLER,
icon_texture = TEXTURES.TOWER_GATTLER_ICON, icon_texture = TEXTURES.TOWER_GATTLER_ICON,
cost = 20, cost = 20,
weapons = {
{
range = 4, range = 4,
fire_rate = 0.5, fire_rate = 0.5,
projectile_type = 3,
}
},
update = function(tower, tower_index) update = function(tower, tower_index)
if not tower.target_index then if not tower.target_index then
-- we should try and acquire a target -- we should try and acquire a target
for index,mob in pairs(game_state.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
-- passive animation -- passive animation
tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2)
@ -138,8 +137,42 @@ return {
texture = TEXTURES.TOWER_HOWITZER, texture = TEXTURES.TOWER_HOWITZER,
icon_texture = TEXTURES.TOWER_HOWITZER_ICON, icon_texture = TEXTURES.TOWER_HOWITZER_ICON,
cost = 50, cost = 50,
weapons = {
{
range = 6, range = 6,
fire_rate = 4, fire_rate = 4,
projectile_type = 1,
}
},
placement_f = function(blocked, has_water, has_mountain, has_ground, hex)
local has_mountain_neighbour = false
local has_non_wall_non_moat_tower_neighbour = false
for _,h in pairs(hex_neighbours(hex)) do
local towers = towers_on_hex(h)
local wall_on_hex = false
has_non_wall_non_moat_tower_neighbour = table.find(towers, function(tower)
if tower.type == TOWER_TYPE.WALL then
wall_on_hex = true
return false
elseif tower.type == TOWER_TYPE.MOAT then
return false
end
return true
end)
if has_non_wall_non_moat_tower_neighbour then
break
end
local tile = hex_map_get(game_state.map, h)
if not wall_on_hex and tile and tile.elevation >= 0.5 then
has_mountain_neighbour = true
break
end
end
return not (blocked or has_water or has_mountain or has_mountain_neighbour or has_non_wall_non_moat_tower_neighbour)
end,
update = function(tower, tower_index) update = function(tower, tower_index)
if not tower.target_index then if not tower.target_index then
-- we don't have a target -- we don't have a target
@ -199,8 +232,16 @@ return {
texture = TEXTURES.TOWER_REDEYE, texture = TEXTURES.TOWER_REDEYE,
icon_texture = TEXTURES.TOWER_REDEYE_ICON, icon_texture = TEXTURES.TOWER_REDEYE_ICON,
cost = 75, cost = 75,
weapons = {
{
range = 9, range = 9,
fire_rate = 3, fire_rate = 3,
projectile_type = 2,
}
},
placement_f = function(blocked, has_water, has_mountain, has_ground, hex)
return not blocked and has_mountain
end,
update = function(tower, tower_index) update = function(tower, tower_index)
if not tower.target_index then if not tower.target_index then
for index,mob in pairs(game_state.mobs) do for index,mob in pairs(game_state.mobs) do
@ -263,8 +304,20 @@ return {
cost = 150, cost = 150,
range = 7, range = 7,
fire_rate = 1, fire_rate = 1,
target_aquisition_f = function(tower, tower_index)
placement_f = function(blocked, has_water, has_mountain, has_ground, hex)
local has_water_neighbour = false
for _,h in pairs(hex_neighbours(hex)) do
local tile = hex_map_get(game_state.map, h)
if tile and tile.elevation < -0.5 then
has_water_neighbour = true
break
end
end
return not blocked
and not has_mountain
and not has_water
and has_water_neighbour
end, end,
update = function(tower, tower_index) update = function(tower, tower_index)
-- check if there's a mob on a hex in our perimeter -- check if there's a mob on a hex in our perimeter
@ -289,3 +342,4 @@ return {
end end
}, },
} }

4
main.lua

@ -98,6 +98,7 @@ require "texture"
-- --
require "src/entity" require "src/entity"
require "src/extra" require "src/extra"
require "src/memory"
require "src/geometry" require "src/geometry"
require "src/hexyz" require "src/hexyz"
require "src/game" require "src/game"
@ -108,8 +109,7 @@ require "src/projectile"
require "src/tower" require "src/tower"
require "src/map-editor" require "src/map-editor"
------------------------------------------------------------------
local sound_toggle_node_tag = "sound_on_off_icon" local sound_toggle_node_tag = "sound_on_off_icon"
local function make_sound_toggle_node(on) local function make_sound_toggle_node(on)
local sprite local sprite

4
src/entity.lua

@ -14,7 +14,7 @@ entity structure:
... - any - a bunch of other shit depending on what entity type it is ... - any - a bunch of other shit depending on what entity type it is
} }
--]] --]]
function make_basic_entity(hex, update, position)
function make_basic_entity(hex, update_f, position)
local entity = {} local entity = {}
entity.TOB = game_state.time entity.TOB = game_state.time
@ -31,7 +31,7 @@ function make_basic_entity(hex, update, position)
entity.position = hex_to_pixel(hex, vec2(HEX_SIZE)) entity.position = hex_to_pixel(hex, vec2(HEX_SIZE))
end end
entity.update = update
entity.update = update_f
entity.node = false -- set by caller entity.node = false -- set by caller
entity.type = false -- set by caller entity.type = false -- set by caller
entity.props = {} entity.props = {}

2
src/extra.lua

@ -1,6 +1,6 @@
-- utility functions that don't below elsewhere go here, -- utility functions that don't below elsewhere go here,
-- especially if they would be at home on the global 'math' or 'table' variables, or are otherwise extensions of standard lua features -- especially if they would be at home on the global 'math' or 'table' variables, or are otherwise extensions of standard lua features
-- try to avoid too much amulet specific stuff, but vector types are probably ok.
-- try to avoid *too* much amulet specific stuff, but vector types are probably ok.
function fprofile(f, ...) function fprofile(f, ...)
local t1 = am.current_time() local t1 = am.current_time()

17
src/game.lua

@ -496,7 +496,7 @@ local function make_game_toolbelt()
toolbelt:append(tower_select_square) toolbelt:append(tower_select_square)
local toolbelt_buttons = {} local toolbelt_buttons = {}
for i = 0, #TOWER_SPECS do
for i = 1, #TOWER_SPECS do
local button, rect = toolbelt_button(i) local button, rect = toolbelt_button(i)
table.insert(toolbelt_buttons, { node = button, rect = rect }) table.insert(toolbelt_buttons, { node = button, rect = rect })
toolbelt:append(button) toolbelt:append(button)
@ -677,15 +677,26 @@ end
-- this is a stupid name, it just returns a scene node group of hexagons in a hexagonal shape centered at 0,0, of size |radius| -- this is a stupid name, it just returns a scene node group of hexagons in a hexagonal shape centered at 0,0, of size |radius|
-- |color_f| can be a function that takes a hex and returns a color, or just a color -- |color_f| can be a function that takes a hex and returns a color, or just a color
-- optionally, |action_f| is a function that operates on the group node every frame -- optionally, |action_f| is a function that operates on the group node every frame
function make_hex_cursor_node(radius, color_f, action_f)
function make_hex_cursor_node(radius, color_f, action_f, min_radius)
local color = type(color_f) == "userdata" and color_f or nil local color = type(color_f) == "userdata" and color_f or nil
local map = hex_spiral_map(vec2(0), radius)
local group = am.group() local group = am.group()
if not min_radius then
local map = hex_spiral_map(vec2(0), radius)
for _,h in pairs(map) do
local hexagon = am.circle(hex_to_pixel(h, vec2(HEX_SIZE)), HEX_SIZE, color or color_f(h), 6)
group:append(hexagon)
end
else
for i = min_radius, radius do
local map = hex_ring_map(vec2(0), i)
for _,h in pairs(map) do for _,h in pairs(map) do
local hexagon = am.circle(hex_to_pixel(h, vec2(HEX_SIZE)), HEX_SIZE, color or color_f(h), 6) local hexagon = am.circle(hex_to_pixel(h, vec2(HEX_SIZE)), HEX_SIZE, color or color_f(h), 6)
group:append(hexagon) group:append(hexagon)
end end
end
end
if action_f then if action_f then
group:action(action_f) group:action(action_f)

2
src/memory.lua

@ -13,7 +13,7 @@ function run_garbage_collector_cycle()
garbage_collector_average_cycle_time = total / #garbage_collector_cycle_timing_history garbage_collector_average_cycle_time = total / #garbage_collector_cycle_timing_history
end end
function check_if_can_collect_garbage_for_free(frame_start_time, min_goal_fps)
function check_if_can_collect_garbage_for_free(frame_start_time, min_fps)
-- often this will be polled at the end of a frame to see if we're running fast or slow, -- often this will be polled at the end of a frame to see if we're running fast or slow,
-- and if we have some time to kill before the start of the next frame, we could maybe run gc. -- and if we have some time to kill before the start of the next frame, we could maybe run gc.
-- --

224
src/tower.lua

@ -1,38 +1,129 @@
do
-- load tower data
local tfile = am.load_script("data/towers.lua")
function default_tower_placement_f(blocked, has_water, has_mountain, has_ground, hex)
return not (blocked or has_water or has_mountain)
end
function default_weapon_target_acquisition_f(tower, tower_index)
for index,mob in pairs(game_state.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
end
function default_tower_target_acquisition_f(tower, tower_index)
-- first, find out if a tower even *should*, acquire a target.
-- a tower should try and acquire a target if atleast one of its weapons that could be shooting, isn't
if not tower.target_index then
for index,mob in pairs(game_state.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
end
end
function default_tower_update_f(tower, tower_index)
end
function resolve_tower_specs(spec_file_path)
local spec_file = am.load_script(spec_file_path)
local error_message local error_message
if tfile then
local status, result = pcall(tfile)
if spec_file then
local status, tower_specs = pcall(spec_file)
if status then if status then
-- lua managed to run the file without syntax/runtime errors -- lua managed to run the file without syntax/runtime errors
-- it's not garunteed to be what we want yet. check: -- it's not garunteed to be what we want yet. check:
local type_ = type(result)
local type_ = type(tower_specs)
if type_ ~= "table" then if type_ ~= "table" then
error_message = "tower spec file should return a table, but we got " .. type_ error_message = "tower spec file should return a table, but we got " .. type_
goto cleanup
end end
TOWER_SPECS = result
-- if we're here, then we're going to assume the spec file is valid, no matter how weird it is
-- last thing to do before returning is fill in missing default values
for i,tower_spec in pairs(tower_specs) do
if not tower_spec.size then
tower_spec.size = 1
end
if not tower_spec.height then
tower_spec.height = 1
end
if not tower_spec.update_f then
tower_spec.update_f = default_tower_update_f
end
if not tower_spec.weapons then
tower_spec.weapons = {}
end
for i,w in pairs(tower_spec.weapons) do
if not w.min_range then
w.min_range = 0
end
if not w.target_acquisition_f then
w.target_acquisition_f = default_weapon_target_acquisition_f
end
end
if not tower_spec.placement_f then
tower_spec.placement_f = default_tower_placement_f
end
-- resolve a tower's visual range - if not provided we should use the largest range among weapons it has
if not tower_spec.visual_range then
local largest_range = 0
for i,w in pairs(tower_spec.weapons) do
if w.range > largest_range then
largest_range = w.range
end
end
tower_spec.visual_range = largest_range
end
-- do the same for the minimum visual range
if not tower_spec.min_visual_range then
local largest_minimum_range = 0
for i,w in pairs(tower_spec.weapons) do
if w.min_range > largest_minimum_range then
largest_minimum_range = w.min_range
end
end
tower_spec.min_visual_range = largest_minimum_range
end
end
return tower_specs
else else
-- runtime error - including syntax errors -- runtime error - including syntax errors
error_message = result error_message = result
goto cleanup
end end
else else
-- file system related error - couldn't load the file
-- filesystem/permissions related error - couldn't load the file
error_message = "couldn't load the file" error_message = "couldn't load the file"
end end
::cleanup::
if error_message then
log(error_message) log(error_message)
-- @TODO no matter what fucked up, we should load defaults -- @TODO no matter what fucked up, we should load defaults
end
return {}
end end
-- load tower spec file
TOWER_SPECS = resolve_tower_specs("data/towers.lua")
function get_tower_spec(tower_type) function get_tower_spec(tower_type)
return TOWER_SPECS[tower_type] return TOWER_SPECS[tower_type]
end end
@ -130,7 +221,7 @@ end
do do
local tower_cursors = {} local tower_cursors = {}
for i = 1, #TOWER_SPECS do
for i,tower_spec in pairs(TOWER_SPECS) do
local tower_sprite = make_tower_node(i) local tower_sprite = make_tower_node(i)
tower_sprite.color = COLORS.TRANSPARENT tower_sprite.color = COLORS.TRANSPARENT
@ -150,7 +241,7 @@ do
end) end)
tower_cursors[i] = am.group{ tower_cursors[i] = am.group{
make_hex_cursor_node(get_tower_range(i), vec4(0), coroutine_),
make_hex_cursor_node(tower_spec.visual_range, vec4(0), coroutine_, tower_spec.min_visual_range),
tower_sprite tower_sprite
} }
end end
@ -210,7 +301,7 @@ function tower_type_is_buildable_on(hex, tile, tower_type)
-- this function gets polled a lot, and sometimes with nil/false tower types -- this function gets polled a lot, and sometimes with nil/false tower types
if not tower_type then return false end if not tower_type then return false end
-- you can't build anything in the center
-- you can't build anything in the center, probably ever?
if hex == HEX_GRID_CENTER then return false end if hex == HEX_GRID_CENTER then return false end
local blocking_towers = {} local blocking_towers = {}
@ -218,6 +309,7 @@ function tower_type_is_buildable_on(hex, tile, tower_type)
local has_water = false local has_water = false
local has_mountain = false local has_mountain = false
local has_ground = false local has_ground = false
local tower_spec = get_tower_spec(tower_type)
for _,h in pairs(hex_spiral_map(hex, get_tower_size(tower_type))) do for _,h in pairs(hex_spiral_map(hex, get_tower_size(tower_type))) do
table.merge(blocking_towers, towers_on_hex(h)) table.merge(blocking_towers, towers_on_hex(h))
@ -243,113 +335,37 @@ function tower_type_is_buildable_on(hex, tile, tower_type)
local mobs_blocking = table.count(blocking_mobs) ~= 0 local mobs_blocking = table.count(blocking_mobs) ~= 0
local blocked = mobs_blocking or towers_blocking local blocked = mobs_blocking or towers_blocking
if tower_type == TOWER_TYPE.GATTLER then
local has_mountain_neighbour = false
local has_non_wall_non_moat_tower_neighbour = false
for _,h in pairs(hex_neighbours(hex)) do
local tile = hex_map_get(game_state.map, h)
if tile and tile.elevation >= 0.5 then
has_mountain_neighbour = true
break
end
end
return not (blocked or has_water or has_mountain)
elseif tower_type == TOWER_TYPE.HOWITZER then
local has_mountain_neighbour = false
local has_non_wall_non_moat_tower_neighbour = false
for _,h in pairs(hex_neighbours(hex)) do
local towers = towers_on_hex(h)
local wall_on_hex = false
has_non_wall_non_moat_tower_neighbour = table.find(towers, function(tower)
if tower.type == TOWER_TYPE.WALL then
wall_on_hex = true
return false
elseif tower.type == TOWER_TYPE.MOAT then
return false
end
return true
end)
if has_non_wall_non_moat_tower_neighbour then
break
end
local tile = hex_map_get(game_state.map, h)
if not wall_on_hex and tile and tile.elevation >= 0.5 then
has_mountain_neighbour = true
break
end
end
return not (blocked or has_water or has_mountain or has_mountain_neighbour or has_non_wall_non_moat_tower_neighbour)
elseif tower_type == TOWER_TYPE.REDEYE then
return not blocked
and has_mountain
elseif tower_type == TOWER_TYPE.LIGHTHOUSE then
local has_water_neighbour = false
for _,h in pairs(hex_neighbours(hex)) do
local tile = hex_map_get(game_state.map, h)
if tile and tile.elevation < -0.5 then
has_water_neighbour = true
break
end
end
return not blocked
and not has_mountain
and not has_water
and has_water_neighbour
elseif tower_type == TOWER_TYPE.WALL then
return not blocked and tile_is_medium_elevation(tile)
elseif tower_type == TOWER_TYPE.MOAT then
return not blocked and tile_is_medium_elevation(tile)
end
return tower_spec.placement_f(blocked, has_water, has_mountain, has_ground, hex)
end end
function make_and_register_tower(hex, tower_type) function make_and_register_tower(hex, tower_type)
local spec = get_tower_spec(tower_type)
local tower = make_basic_entity( local tower = make_basic_entity(
hex, hex,
get_tower_update_function(tower_type)
spec.update_f
) )
table.merge(tower, spec)
tower.type = tower_type tower.type = tower_type
tower.node = am.translate(tower.position) ^ make_tower_node(tower_type) tower.node = am.translate(tower.position) ^ make_tower_node(tower_type)
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 -- lets the tower fire immediately upon being placed
tower.size = spec.size
if tower.size == 0 then
for i,w in pairs(tower.weapons) do
w.last_shot_time = -tower.weapons[i].fire_rate -- lets the tower fire immediately upon being placed
end
if tower.size == 1 then
tower.hexes = { tower.hex } tower.hexes = { tower.hex }
else else
tower.hexes = hex_spiral_map(tower.hex, tower.size)
tower.hexes = hex_spiral_map(tower.hex, tower.size - 1)
end end
tower.height = spec.height
-- should we be permuting the map here?
for _,h in pairs(tower.hexes) do for _,h in pairs(tower.hexes) do
local tile = hex_map_get(game_state.map, h.x, h.y) local tile = hex_map_get(game_state.map, h.x, h.y)
tile.elevation = tile.elevation + tower.height tile.elevation = tile.elevation + tower.height
end end
if tower.type == TOWER_TYPE.HOWITZER then
tower.props.z = tower.height
elseif tower.type == TOWER_TYPE.GATTLER then
tower.props.z = tower.height
elseif tower.type == TOWER_TYPE.LIGHTHOUSE then
tower.perimeter = hex_ring_map(tower.hex, tower.range)
end
register_entity(game_state.towers, tower) register_entity(game_state.towers, tower)
return tower return tower
end end

Loading…
Cancel
Save