Nicholas Hayashi
3 years ago
7 changed files with 356 additions and 306 deletions
-
2conf.lua
-
291data/towers.lua
-
1main.lua
-
8src/extra.lua
-
23src/game.lua
-
4src/map-editor.lua
-
331src/tower.lua
@ -0,0 +1,291 @@ |
|||||
|
|
||||
|
--[[ |
||||
|
the following is a list of tower specifications, which are declarations of a variety of properties describing what a tower is, and how it functions |
||||
|
this a lua file. a quick run-down of what writing code in lua looks like: https://www.amulet.xyz/doc/#lua-primer |
||||
|
|
||||
|
each tower spec is a lua table. lua tables are the thing that you use to represent bundles of data (both arrays and hashtables are represented by tables) |
||||
|
|
||||
|
the format of the bundles in our case are described below. |
||||
|
some propreties are optional. required properties are marked with an asterisk (*), and are generally included at the top of the list. |
||||
|
|
||||
|
# TOWER SPEC TABLE |
||||
|
| --------------------------| -------- | -------------------------------------------------------------- | |
||||
|
| property name, required* | datatype | general description / details | |
||||
|
| --------------------------| -------- | -------------------------------------------------------------- | |
||||
|
| name* | string | exact one-line display name text of the tower | |
||||
|
| placement_rules_text* | string | one-line description of the placement rules for this tower | |
||||
|
| short_description* | string | one-line description of the nature of this tower | |
||||
|
| texture* | userdata | @TODO | |
||||
|
| icon_texture* | userdata | @TODO | |
||||
|
| cost* | number | the starting cost of placing this tower | |
||||
|
| | | | |
||||
|
| weapons* | table | an array of weapons. | |
||||
|
| | | order matters - two weapons share a 'choke' value, and both | |
||||
|
| | | could acquire a target in a frame, the first one is choosen. | |
||||
|
| | | | |
||||
|
| placement_rules | table | @TODO | |
||||
|
| | | | |
||||
|
| | | | |
||||
|
| | | | |
||||
|
| | | | |
||||
|
| | | | |
||||
|
| | | | |
||||
|
| update_f | function | default value is complicated @TODO | |
||||
|
| grow_f | function | default value is false/nil. @TODO | |
||||
|
| size | number | default value of 1, which means the tower occupies one hex. | |
||||
|
| height | number | default value of 1. height is relevant for mob pathing and | |
||||
|
| | | projectile collision | |
||||
|
| | | | |
||||
|
| --------------------------| -------- | -------------------------------------------------------------- | |
||||
|
|
||||
|
# WEAPON TABLE |
||||
|
| --------------------------| -------- | -------------------------------------------------------------- | |
||||
|
| property name, required* | datatype | general description / details | |
||||
|
| --------------------------| -------- | -------------------------------------------------------------- | |
||||
|
| type | number | sometimes, instead of specifying everything for a weapon, it's | |
||||
|
| | | convenient to refer to a base type. if this is provided all of | |
||||
|
| | | the weapon's other fields will be initialized to preset values | |
||||
|
| | | and any other values you provide with the weapon spec will | |
||||
|
| | | overwrite those preset values. | |
||||
|
| | | if you provide a value here, all other properties become | |
||||
|
| | | optional. | |
||||
|
| | | values you can provide, and what they mean: | |
||||
|
| | | @TODO | |
||||
|
| | | | |
||||
|
| fire_rate* | number | 'shots' per second, if the weapon has a valid target | |
||||
|
| range* | number | max distance (in hexes) at which this weapon acquires targets | |
||||
|
| | | | |
||||
|
| min-range | number | default of 0. min distance (in hexes) at which this weapon acquires targets | |
||||
|
| target_acquisition_f | function | default value is complicated @TODO | |
||||
|
| choke | number | default of false/nil. @TODO | |
||||
|
| | | | |
||||
|
| --------------------------| -------- | -------------------------------------------------------------- | |
||||
|
]] |
||||
|
|
||||
|
return { |
||||
|
{ |
||||
|
name = "Wall", |
||||
|
placement_rules_text = "Place on Ground", |
||||
|
short_description = "Restricts movement, similar to a mountain.", |
||||
|
texture = TEXTURES.TOWER_WALL, |
||||
|
icon_texture = TEXTURES.TOWER_WALL_ICON, |
||||
|
cost = 10, |
||||
|
range = 0, |
||||
|
fire_rate = 2, |
||||
|
update = false, |
||||
|
placement_rules = { |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name = "Gattler", |
||||
|
placement_rules_text = "Place on Ground", |
||||
|
short_description = "Short-range, fast-fire rate single-target tower.", |
||||
|
texture = TEXTURES.TOWER_GATTLER, |
||||
|
icon_texture = TEXTURES.TOWER_GATTLER_ICON, |
||||
|
cost = 20, |
||||
|
range = 4, |
||||
|
fire_rate = 0.5, |
||||
|
update = function(tower, tower_index) |
||||
|
if not tower.target_index then |
||||
|
-- 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 |
||||
|
tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) |
||||
|
else |
||||
|
-- should have a target, so we should try and shoot it |
||||
|
if not game_state.mobs[tower.target_index] then |
||||
|
-- the target we have was invalidated |
||||
|
tower.target_index = false |
||||
|
|
||||
|
else |
||||
|
-- the target we have is valid |
||||
|
local mob = game_state.mobs[tower.target_index] |
||||
|
local vector = math.normalize(mob.position - tower.position) |
||||
|
|
||||
|
if (game_state.time - tower.last_shot_time) > tower.fire_rate then |
||||
|
local projectile = make_and_register_projectile( |
||||
|
tower.hex, |
||||
|
PROJECTILE_TYPE.BULLET, |
||||
|
vector |
||||
|
) |
||||
|
|
||||
|
tower.last_shot_time = game_state.time |
||||
|
play_sfx(SOUNDS.HIT1) |
||||
|
end |
||||
|
|
||||
|
-- point the cannon at the dude |
||||
|
local theta = math.rad(90) - math.atan((tower.position.y - mob.position.y)/(tower.position.x - mob.position.x)) |
||||
|
local diff = tower.node("rotate").angle - theta |
||||
|
|
||||
|
tower.node("rotate").angle = -theta + math.pi/2 |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
}, |
||||
|
{ |
||||
|
name = "Howitzer", |
||||
|
placement_rules_text = "Place on Ground, with a 1 space gap between other towers and mountains - walls/moats don't count.", |
||||
|
short_description = "Medium-range, medium fire-rate area of effect artillery tower.", |
||||
|
texture = TEXTURES.TOWER_HOWITZER, |
||||
|
icon_texture = TEXTURES.TOWER_HOWITZER_ICON, |
||||
|
cost = 50, |
||||
|
range = 6, |
||||
|
fire_rate = 4, |
||||
|
update = function(tower, tower_index) |
||||
|
if not tower.target_index then |
||||
|
-- we don't have 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 |
||||
|
tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) |
||||
|
else |
||||
|
-- we should have a target |
||||
|
-- @NOTE don't compare to false, empty indexes appear on game reload |
||||
|
if not game_state.mobs[tower.target_index] then |
||||
|
-- the target we have was invalidated |
||||
|
tower.target_index = false |
||||
|
|
||||
|
else |
||||
|
-- the target we have is valid |
||||
|
local mob = game_state.mobs[tower.target_index] |
||||
|
local vector = math.normalize(mob.position - tower.position) |
||||
|
|
||||
|
if (game_state.time - tower.last_shot_time) > tower.fire_rate then |
||||
|
local projectile = make_and_register_projectile( |
||||
|
tower.hex, |
||||
|
PROJECTILE_TYPE.SHELL, |
||||
|
vector |
||||
|
) |
||||
|
|
||||
|
-- @HACK, the projectile will explode if it encounters something taller than it, |
||||
|
-- but the tower it spawns on quickly becomes taller than it, so we just pad it |
||||
|
-- if it's not enough the shell explodes before it leaves its spawning hex |
||||
|
projectile.props.z = tower.props.z + 0.1 |
||||
|
|
||||
|
tower.last_shot_time = game_state.time |
||||
|
play_sfx(SOUNDS.EXPLOSION2) |
||||
|
end |
||||
|
|
||||
|
-- point the cannon at the dude |
||||
|
local theta = math.rad(90) - math.atan((tower.position.y - mob.position.y)/(tower.position.x - mob.position.x)) |
||||
|
local diff = tower.node("rotate").angle - theta |
||||
|
|
||||
|
tower.node("rotate").angle = -theta + math.pi/2 |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
}, |
||||
|
{ |
||||
|
name = "Redeye", |
||||
|
placement_rules_text = "Place on Mountains.", |
||||
|
short_description = "Long-range, penetrating high-velocity laser tower.", |
||||
|
texture = TEXTURES.TOWER_REDEYE, |
||||
|
icon_texture = TEXTURES.TOWER_REDEYE_ICON, |
||||
|
cost = 75, |
||||
|
range = 9, |
||||
|
fire_rate = 3, |
||||
|
update = function(tower, tower_index) |
||||
|
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 |
||||
|
else |
||||
|
if not game_state.mobs[tower.target_index] then |
||||
|
tower.target_index = false |
||||
|
|
||||
|
elseif (game_state.time - tower.last_shot_time) > tower.fire_rate then |
||||
|
local mob = game_state.mobs[tower.target_index] |
||||
|
|
||||
|
make_and_register_projectile( |
||||
|
tower.hex, |
||||
|
PROJECTILE_TYPE.LASER, |
||||
|
math.normalize(mob.position - tower.position) |
||||
|
) |
||||
|
|
||||
|
tower.last_shot_time = game_state.time |
||||
|
vplay_sfx(SOUNDS.LASER2) |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
}, |
||||
|
{ |
||||
|
name = "Moat", |
||||
|
placement_rules_text = "Place on Ground", |
||||
|
short_description = "Restricts movement, similar to water.", |
||||
|
texture = TEXTURES.TOWER_MOAT, |
||||
|
icon_texture = TEXTURES.TOWER_MOAT_ICON, |
||||
|
cost = 10, |
||||
|
range = 0, |
||||
|
fire_rate = 2, |
||||
|
height = -1, |
||||
|
update = false |
||||
|
}, |
||||
|
{ |
||||
|
name = "Radar", |
||||
|
placement_rules_text = "n/a", |
||||
|
short_description = "Doesn't do anything right now :(", |
||||
|
texture = TEXTURES.TOWER_RADAR, |
||||
|
icon_texture = TEXTURES.TOWER_RADAR_ICON, |
||||
|
cost = 100, |
||||
|
range = 0, |
||||
|
fire_rate = 1, |
||||
|
update = false |
||||
|
}, |
||||
|
{ |
||||
|
name = "Lighthouse", |
||||
|
placement_rules_text = "Place on Ground, adjacent to Water or Moats", |
||||
|
short_description = "Attracts nearby mobs; temporarily redirects their path", |
||||
|
texture = TEXTURES.TOWER_LIGHTHOUSE, |
||||
|
icon_texture = TEXTURES.TOWER_LIGHTHOUSE_ICON, |
||||
|
cost = 150, |
||||
|
range = 7, |
||||
|
fire_rate = 1, |
||||
|
target_aquisition_f = function(tower, tower_index) |
||||
|
|
||||
|
end, |
||||
|
update = function(tower, tower_index) |
||||
|
-- check if there's a mob on a hex in our perimeter |
||||
|
for _,h in pairs(tower.perimeter) do |
||||
|
local mobs = mobs_on_hex(h) |
||||
|
|
||||
|
for _,m in pairs(mobs) do |
||||
|
if not m.path and not m.seen_lighthouse then |
||||
|
-- @TODO only attract the mob if its frame target (direction vector) |
||||
|
-- is within some angle range...? if the mob is heading directly away from the tower, then |
||||
|
-- the lighthouse shouldn't do much |
||||
|
|
||||
|
local path, made_it = hex_Astar(game_state.map, tower.hex, m.hex, grid_neighbours, grid_cost, grid_heuristic) |
||||
|
|
||||
|
if made_it then |
||||
|
m.path = path |
||||
|
m.seen_lighthouse = true -- right now mobs don't care about lighthouses if they've already seen one. |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
}, |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue