|
|
@ -2,6 +2,14 @@ |
|
|
|
|
|
|
|
state = {} |
|
|
|
|
|
|
|
function game_end() |
|
|
|
delete_all_entities() |
|
|
|
game_init() |
|
|
|
end |
|
|
|
|
|
|
|
function update_score(diff) state.score = state.score + diff end |
|
|
|
function update_money(diff) state.money = state.money + diff end |
|
|
|
|
|
|
|
-- top right display types |
|
|
|
local TRDTS = { |
|
|
|
NOTHING = 0, |
|
|
@ -26,31 +34,23 @@ local function get_initial_game_state(seed) |
|
|
|
time = 0, -- time since game started in seconds |
|
|
|
score = 0, -- current game score |
|
|
|
money = STARTING_MONEY, -- current money |
|
|
|
mouse = false, -- mouse position at the start of this frame |
|
|
|
|
|
|
|
hex = false, -- vec2 coordinates of hexagon under mouse this frame |
|
|
|
rounded_mouse = false, -- vec2 of pixel position of center of hexagon under mouse this frame |
|
|
|
evenq = false, -- same as state.hex, but in a different coordinate system |
|
|
|
centered_evenq = false, -- same as state.evenq, but with the middle of the map being 0,0 |
|
|
|
tile = false, -- tile at position of state.hex this frame |
|
|
|
hot = false, -- element being interacted with this frame |
|
|
|
|
|
|
|
selected_tower_type = false, |
|
|
|
selected_toolbelt_button = 9, |
|
|
|
top_right_display_type = TRDTS.SEED, |
|
|
|
selected_top_right_display_type = TRDTS.SEED, |
|
|
|
} |
|
|
|
end |
|
|
|
|
|
|
|
local function get_top_right_display_text(display_type) |
|
|
|
local function get_top_right_display_text(hex, evenq, centered_evenq, display_type) |
|
|
|
local str = "" |
|
|
|
if display_type == TRDTS.CENTERED_EVENQ then |
|
|
|
str = state.centered_evenq.x .. "," .. state.centered_evenq.y .. " (cevenq)" |
|
|
|
str = centered_evenq.x .. "," .. centered_evenq.y .. " (cevenq)" |
|
|
|
|
|
|
|
elseif display_type == TRDTS.EVENQ then |
|
|
|
str = state.evenq.x .. "," .. state.evenq.y .. " (evenq)" |
|
|
|
str = evenq.x .. "," .. evenq.y .. " (evenq)" |
|
|
|
|
|
|
|
elseif display_type == TRDTS.HEX then |
|
|
|
str = state.hex.x .. "," .. state.hex.y .. " (hex)" |
|
|
|
str = hex.x .. "," .. hex.y .. " (hex)" |
|
|
|
|
|
|
|
elseif display_type == TRDTS.PLATFORM then |
|
|
|
str = string.format("%s %s lang %s", am.platform, am.version, am.language()) |
|
|
@ -62,29 +62,20 @@ local function get_top_right_display_text(display_type) |
|
|
|
str = "SEED: " .. state.map.seed |
|
|
|
|
|
|
|
elseif display_type == TRDTS.TILE then |
|
|
|
str = table.tostring(state.map.get(state.hex.x, state.hex.y)) |
|
|
|
str = table.tostring(state.map.get(hex.x, hex.y)) |
|
|
|
end |
|
|
|
return str |
|
|
|
end |
|
|
|
|
|
|
|
local function can_do_build(hex, tile, tower_type) |
|
|
|
if making_hex_unwalkable_breaks_flow_field(hex, tile) then |
|
|
|
return false |
|
|
|
local function handle_left_click(hex, tile, tower_type, toolbelt_button) |
|
|
|
if play_error_sound then |
|
|
|
play_sfx(SOUNDS.BIRD2) |
|
|
|
end |
|
|
|
|
|
|
|
if not can_afford_tower(state.money, tower_type) then |
|
|
|
return false |
|
|
|
end |
|
|
|
|
|
|
|
if not tower_is_buildable_on(hex, tile, tower_type) then |
|
|
|
if flash_tower_cursor then |
|
|
|
local node = WIN.scene("cursor"):child(1):child(2) |
|
|
|
node.color = COLORS.CLARET |
|
|
|
node:action(am.tween(0.1, { color = COLORS.TRANSPARENT })) |
|
|
|
|
|
|
|
return false |
|
|
|
end |
|
|
|
|
|
|
|
return true |
|
|
|
end |
|
|
|
|
|
|
|
-- initialized later, as part of the init of the toolbelt |
|
|
@ -92,7 +83,7 @@ function select_tower_type(tower_type) end |
|
|
|
|
|
|
|
function select_toolbelt_button(i) |
|
|
|
state.selected_toolbelt_button = i |
|
|
|
if i < 9 then |
|
|
|
if i <= 10 then |
|
|
|
select_tower_type(i) |
|
|
|
else |
|
|
|
select_tower_type(nil) |
|
|
@ -124,34 +115,31 @@ local function game_pause() |
|
|
|
end) |
|
|
|
end |
|
|
|
|
|
|
|
function game_end() |
|
|
|
delete_all_entities() |
|
|
|
game_init() |
|
|
|
end |
|
|
|
|
|
|
|
function update_score(diff) state.score = state.score + diff end |
|
|
|
function update_money(diff) state.money = state.money + diff end |
|
|
|
|
|
|
|
local function game_action(scene) |
|
|
|
if state.score < 0 then game_end() return true end |
|
|
|
|
|
|
|
state.perf = am.perf_stats() |
|
|
|
state.time = state.time + am.delta_time |
|
|
|
state.score = state.score + am.delta_time |
|
|
|
state.mouse = WIN:mouse_position() |
|
|
|
|
|
|
|
state.hex = pixel_to_hex(state.mouse - WORLDSPACE_COORDINATE_OFFSET) |
|
|
|
state.rounded_mouse = hex_to_pixel(state.hex) + WORLDSPACE_COORDINATE_OFFSET |
|
|
|
state.evenq = hex_to_evenq(state.hex) |
|
|
|
state.centered_evenq = state.evenq{ y = -state.evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2) |
|
|
|
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)) |
|
|
|
state.tile = state.map.get(state.hex.x, state.hex.y) |
|
|
|
state.hot = evenq_is_interactable(state.evenq{ y = -state.evenq.y }) |
|
|
|
local tile = state.map.get(hex.x, hex.y) |
|
|
|
|
|
|
|
local interactable = evenq_is_in_interactable_region(evenq{ y = -evenq.y }) |
|
|
|
local buildable = tower_type_is_buildable_on(hex, tile, state.selected_tower_type) |
|
|
|
local firable = false |
|
|
|
|
|
|
|
if WIN:mouse_pressed"left" then |
|
|
|
if state.hot and state.selected_tower_type and can_do_build(state.hex, state.tile, state.selected_tower_type) then |
|
|
|
if interactable then |
|
|
|
if buildable then |
|
|
|
update_money(-get_tower_cost(state.selected_tower_type)) |
|
|
|
build_tower(state.hex, state.selected_tower_type) |
|
|
|
build_tower(hex, state.selected_tower_type) |
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
@ -189,14 +177,19 @@ local function game_action(scene) |
|
|
|
do_gui_updates() |
|
|
|
do_day_night_cycle() |
|
|
|
|
|
|
|
WIN.scene"top_right_display".text = get_top_right_display_text(state.top_right_display_type) |
|
|
|
WIN.scene"score".text = string.format("SCORE: %.2f", state.score) |
|
|
|
WIN.scene"money".text = string.format("MONEY: %d", state.money) |
|
|
|
if state.hot then |
|
|
|
WIN.scene"cursor".hidden = false |
|
|
|
WIN.scene"cursor".position2d = state.rounded_mouse |
|
|
|
WIN.scene("top_right_display").text = get_top_right_display_text(hex, evenq, centered_evenq, state.selected_top_right_display_type) |
|
|
|
WIN.scene("score").text = string.format("SCORE: %.2f", state.score) |
|
|
|
WIN.scene("money").text = string.format("MONEY: %d", state.money) |
|
|
|
if interactable then |
|
|
|
WIN.scene("cursor").hidden = false |
|
|
|
|
|
|
|
if buildable then |
|
|
|
WIN.scene("cursor_translate").position2d = rounded_mouse |
|
|
|
else |
|
|
|
WIN.scene("cursor").hidden = true |
|
|
|
end |
|
|
|
else |
|
|
|
WIN.scene"cursor".hidden = true |
|
|
|
WIN.scene("cursor").hidden = true |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
@ -207,7 +200,9 @@ local function make_game_toolbelt() |
|
|
|
local x2 = (size + padding) * i + offset.x + size |
|
|
|
local y2 = offset.y + size |
|
|
|
|
|
|
|
register_button_widget("toolbelt_tower_button" .. i, am.rect(x1, y1, x2, y2), function() select_tower_type(i) end) |
|
|
|
register_button_widget("toolbelt_tower_button" .. i |
|
|
|
, am.rect(x1, y1, x2, y2) |
|
|
|
, function() select_tower_type(i) end) |
|
|
|
|
|
|
|
return am.translate(vec2(size + padding, 0) * i + offset) |
|
|
|
^ am.group{ |
|
|
@ -231,19 +226,29 @@ local function make_game_toolbelt() |
|
|
|
local name = get_tower_name(tower_type) |
|
|
|
local placement_rules = get_tower_placement_rules_text(tower_type) |
|
|
|
local short_desc = get_tower_short_description(tower_type) |
|
|
|
local base_cost = get_tower_cost(tower_type) |
|
|
|
local cost = get_tower_cost(tower_type) |
|
|
|
|
|
|
|
if not (name or placement_rules or short_desc or base_cost) then |
|
|
|
if not (name or placement_rules or short_desc or cost) then |
|
|
|
return am.group():tag"tower_tooltip_text" |
|
|
|
end |
|
|
|
|
|
|
|
local white = COLORS.PALE_SILVER |
|
|
|
return (am.translate(WIN.left + 10, WIN.bottom + toolbelt_height + 20) ^ am.group{ |
|
|
|
am.translate(0, 60) ^ am.text(name, white, "left"):tag"tower_name", |
|
|
|
am.translate(0, 40) ^ am.text(placement_rules, white, "left"):tag"tower_placement_rules", |
|
|
|
am.translate(0, 20) ^ am.text(short_desc, white, "left"):tag"tower_short_description", |
|
|
|
am.translate(0, 0) ^ am.text(string.format("cost: %d", base_cost), white, "left"):tag"tower_base_cost" |
|
|
|
}):tag"tower_tooltip_text" |
|
|
|
local color = COLORS.PALE_SILVER |
|
|
|
return (am.translate(WIN.left + 10, WIN.bottom + toolbelt_height + 20) |
|
|
|
^ am.group{ |
|
|
|
am.translate(0, 60) |
|
|
|
^ am.text(name, color, "left"):tag"tower_name", |
|
|
|
|
|
|
|
am.translate(0, 40) |
|
|
|
^ am.text(placement_rules, color, "left"):tag"tower_placement_rules", |
|
|
|
|
|
|
|
am.translate(0, 20) |
|
|
|
^ am.text(short_desc, color, "left"):tag"tower_short_description", |
|
|
|
|
|
|
|
am.translate(0, 0) |
|
|
|
^ am.text(string.format("cost: %d", cost), color, "left"):tag"tower_cost" |
|
|
|
} |
|
|
|
) |
|
|
|
:tag"tower_tooltip_text" |
|
|
|
end |
|
|
|
local toolbelt = am.group{ |
|
|
|
am.group():tag"tower_tooltip_text", |
|
|
@ -298,20 +303,20 @@ local function make_game_toolbelt() |
|
|
|
WIN.scene:replace("tower_tooltip_text", get_tower_tooltip_text_node(tower_type)) |
|
|
|
|
|
|
|
local new_position = vec2((size + padding) * tower_type, size/2) + offset |
|
|
|
if WIN.scene"tower_select_square".hidden then |
|
|
|
WIN.scene"tower_select_square".position2d = new_position |
|
|
|
WIN.scene"tower_select_square".hidden = false |
|
|
|
if toolbelt("tower_select_square").hidden then |
|
|
|
toolbelt("tower_select_square").position2d = new_position |
|
|
|
toolbelt("tower_select_square").hidden = false |
|
|
|
else |
|
|
|
WIN.scene"tower_select_square":action(am.tween(0.1, { position2d = new_position })) |
|
|
|
toolbelt("tower_select_square"):action(am.tween(0.1, { position2d = new_position })) |
|
|
|
end |
|
|
|
|
|
|
|
WIN.scene:replace("cursor", (am.translate(state.rounded_mouse) ^ get_tower_cursor(tower_type)):tag"cursor") |
|
|
|
WIN.scene:replace("cursor", get_tower_cursor(tower_type):tag"cursor") |
|
|
|
|
|
|
|
WIN.scene:action(am.play(am.sfxr_synth(SOUNDS.SELECT1), false, 1, SFX_VOLUME)) |
|
|
|
else |
|
|
|
WIN.scene"tower_select_square".hidden = true |
|
|
|
toolbelt("tower_select_square").hidden = true |
|
|
|
|
|
|
|
WIN.scene:replace("cursor", make_hex_cursor(state.rounded_mouse, 0, COLORS.TRANSPARENT)) |
|
|
|
WIN.scene:replace("cursor", make_hex_cursor(0, COLORS.TRANSPARENT)) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
@ -320,7 +325,7 @@ end |
|
|
|
|
|
|
|
-- |color_f| can be a function that takes a hex and returns a color, or just a color |
|
|
|
-- |action_f| should be an action that operates on the group node or nil |
|
|
|
function make_hex_cursor(position, radius, color_f, action_f) |
|
|
|
function make_hex_cursor(radius, color_f, action_f) |
|
|
|
local color = type(color_f) == "userdata" and color_f or nil |
|
|
|
local map = spiral_map(vec2(0), radius) |
|
|
|
local group = am.group() |
|
|
@ -334,23 +339,29 @@ function make_hex_cursor(position, radius, color_f, action_f) |
|
|
|
group:action(action_f) |
|
|
|
end |
|
|
|
|
|
|
|
return (am.translate(position) ^ group):tag"cursor" |
|
|
|
return group:tag"cursor" |
|
|
|
end |
|
|
|
|
|
|
|
function game_scene() |
|
|
|
local score = am.translate(WIN.left + 10, WIN.top - 20) ^ am.text("", "left"):tag"score" |
|
|
|
local money = am.translate(WIN.left + 10, WIN.top - 40) ^ am.text("", "left"):tag"money" |
|
|
|
local top_right_display = am.translate(WIN.right - 10, WIN.top - 20) ^ am.text("", "right", "top"):tag"top_right_display" |
|
|
|
local score = am.translate(WIN.left + 10, WIN.top - 20) |
|
|
|
^ am.text("", "left"):tag"score" |
|
|
|
|
|
|
|
local money = am.translate(WIN.left + 10, WIN.top - 40) |
|
|
|
^ am.text("", "left"):tag"money" |
|
|
|
|
|
|
|
local top_right_display = am.translate(WIN.right - 10, WIN.top - 20) |
|
|
|
^ am.text("", "right", "top"):tag"top_right_display" |
|
|
|
|
|
|
|
local curtain = am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRUE_BLACK) |
|
|
|
curtain:action(coroutine.create(function() |
|
|
|
am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola))) |
|
|
|
WIN.scene:remove(curtain) |
|
|
|
return true |
|
|
|
end)) |
|
|
|
|
|
|
|
local scene = am.group{ |
|
|
|
state.world, |
|
|
|
make_hex_cursor(OFF_SCREEN, 0, COLORS.TRANSPARENT), |
|
|
|
am.translate(OFF_SCREEN):tag"cursor_translate" ^ make_hex_cursor(0, COLORS.TRANSPARENT), |
|
|
|
score, |
|
|
|
money, |
|
|
|
top_right_display, |
|
|
@ -367,7 +378,7 @@ function game_init() |
|
|
|
state = get_initial_game_state() |
|
|
|
build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR) |
|
|
|
|
|
|
|
WIN.scene:remove"game" |
|
|
|
WIN.scene:remove("game") |
|
|
|
WIN.scene:append(game_scene()) |
|
|
|
end |
|
|
|
|