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.
398 lines
14 KiB
398 lines
14 KiB
|
|
|
|
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,
|
|
CENTERED_EVENQ = 1,
|
|
EVENQ = 2,
|
|
HEX = 3,
|
|
PLATFORM = 4,
|
|
PERF = 5,
|
|
SEED = 6,
|
|
TILE = 7,
|
|
}
|
|
|
|
local function get_initial_game_state(seed)
|
|
local STARTING_MONEY = 10000
|
|
local map, world = random_map(seed)
|
|
|
|
return {
|
|
map = map, -- map of hex coords map[x][y] to some stuff at that location
|
|
world = world, -- the root scene graph node for the game 'world'
|
|
|
|
perf = {}, -- result of call to am.perf_stats, called every frame
|
|
time = 0, -- time since game started in seconds
|
|
score = 0, -- current game score
|
|
money = STARTING_MONEY, -- current money
|
|
|
|
selected_tower_type = false,
|
|
selected_toolbelt_button = 9,
|
|
selected_top_right_display_type = TRDTS.SEED,
|
|
}
|
|
end
|
|
|
|
local function get_top_right_display_text(hex, evenq, centered_evenq, display_type)
|
|
local str = ""
|
|
if display_type == TRDTS.CENTERED_EVENQ then
|
|
str = centered_evenq.x .. "," .. centered_evenq.y .. " (cevenq)"
|
|
|
|
elseif display_type == TRDTS.EVENQ then
|
|
str = evenq.x .. "," .. evenq.y .. " (evenq)"
|
|
|
|
elseif display_type == TRDTS.HEX then
|
|
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())
|
|
|
|
elseif display_type == TRDTS.PERF then
|
|
str = table.tostring(state.perf)
|
|
|
|
elseif display_type == TRDTS.SEED then
|
|
str = "SEED: " .. state.map.seed
|
|
|
|
elseif display_type == TRDTS.TILE then
|
|
str = table.tostring(state.map.get(hex.x, hex.y))
|
|
end
|
|
return str
|
|
end
|
|
|
|
local function handle_left_click(hex, tile, tower_type, toolbelt_button)
|
|
|
|
end
|
|
|
|
-- initialized later, as part of the init of the toolbelt
|
|
function select_tower_type(tower_type) end
|
|
|
|
function select_toolbelt_button(i)
|
|
state.selected_toolbelt_button = i
|
|
if i <= 10 then
|
|
select_tower_type(i)
|
|
else
|
|
select_tower_type(nil)
|
|
end
|
|
end
|
|
|
|
function do_day_night_cycle()
|
|
local tstep = (math.sin(state.time / 100) + 1) / state.perf.avg_fps
|
|
state.world"negative_mask".color = vec4(tstep){a=1}
|
|
end
|
|
|
|
local function game_pause()
|
|
WIN.scene"game".paused = true
|
|
WIN.scene"game":append(am.group{
|
|
am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRANSPARENT),
|
|
am.scale(3) ^ am.text("Paused.\nEscape to Resume\nf4 to start a new game", COLORS.BLACK)
|
|
}
|
|
:tag"pause_menu")
|
|
WIN.scene:action(function()
|
|
if WIN:key_pressed"escape" then
|
|
WIN.scene:remove"pause_menu"
|
|
WIN.scene"game".paused = false
|
|
return true
|
|
|
|
elseif WIN:key_pressed"f4" then
|
|
game_end()
|
|
return true
|
|
end
|
|
end)
|
|
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
|
|
|
|
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))
|
|
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 interactable then
|
|
if buildable then
|
|
local broken, flow_field = making_hex_unwalkable_breaks_flow_field(hex, tile)
|
|
local cost = get_tower_cost(state.selected_tower_type)
|
|
|
|
if broken then
|
|
local node = WIN.scene("cursor"):child(2)
|
|
node.color = COLORS.CLARET
|
|
node:action(am.tween(0.1, { color = COLORS.TRANSPARENT }))
|
|
play_sfx(SOUNDS.BIRD2)
|
|
|
|
elseif cost > state.money then
|
|
local node = WIN.scene("cursor"):child(2)
|
|
node.color = COLORS.CLARET
|
|
node:action(am.tween(0.1, { color = COLORS.TRANSPARENT }))
|
|
play_sfx(SOUNDS.BIRD2)
|
|
|
|
else
|
|
update_money(-cost)
|
|
build_tower(hex, state.selected_tower_type, flow_field)
|
|
|
|
if flow_field then
|
|
apply_flow_field(state.map, flow_field, state.world)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if WIN:key_pressed"escape" then
|
|
game_pause()
|
|
|
|
elseif WIN:key_pressed"f1" then
|
|
state.top_right_display_type = (state.top_right_display_type + 1) % #table.keys(TRDTS)
|
|
|
|
elseif WIN:key_pressed"f2" then
|
|
state.world"flow_field".hidden = not state.world"flow_field".hidden
|
|
|
|
elseif WIN:key_pressed"tab" then
|
|
if WIN:key_down"lshift" then
|
|
select_toolbelt_button((state.selected_toolbelt_button + 12 - 2) % 12 + 1)
|
|
else
|
|
select_toolbelt_button((state.selected_toolbelt_button) % 12 + 1)
|
|
end
|
|
elseif WIN:key_pressed"1" then select_toolbelt_button(1)
|
|
elseif WIN:key_pressed"2" then select_toolbelt_button(2)
|
|
elseif WIN:key_pressed"3" then select_toolbelt_button(3)
|
|
elseif WIN:key_pressed"4" then select_toolbelt_button(4)
|
|
elseif WIN:key_pressed"q" then select_toolbelt_button(5)
|
|
elseif WIN:key_pressed"w" then select_toolbelt_button(6)
|
|
elseif WIN:key_pressed"e" then select_toolbelt_button(7)
|
|
elseif WIN:key_pressed"r" then select_toolbelt_button(8)
|
|
elseif WIN:key_pressed"a" then select_toolbelt_button(9)
|
|
elseif WIN:key_pressed"s" then select_toolbelt_button(10)
|
|
elseif WIN:key_pressed"d" then select_toolbelt_button(11)
|
|
elseif WIN:key_pressed"f" then select_toolbelt_button(12)
|
|
end
|
|
|
|
do_entity_updates()
|
|
do_mob_spawning()
|
|
do_gui_updates()
|
|
do_day_night_cycle()
|
|
|
|
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
|
|
end
|
|
end
|
|
|
|
local function make_game_toolbelt()
|
|
local function toolbelt_button(size, half_size, tower_texture, padding, i, offset, key_name)
|
|
local x1 = (size + padding) * i + offset.x
|
|
local y1 = offset.y
|
|
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)
|
|
|
|
return am.translate(vec2(size + padding, 0) * i + offset)
|
|
^ am.group{
|
|
am.translate(0, half_size)
|
|
^ pack_texture_into_sprite(TEXTURES.BUTTON1, size, size),
|
|
|
|
am.translate(0, half_size)
|
|
^ pack_texture_into_sprite(tower_texture, size, size),
|
|
|
|
am.translate(vec2(half_size))
|
|
^ am.group{
|
|
pack_texture_into_sprite(TEXTURES.BUTTON1, half_size, half_size),
|
|
am.scale(2)
|
|
^ am.text(key_name, COLORS.BLACK)
|
|
}
|
|
}
|
|
end
|
|
|
|
local toolbelt_height = hex_height(HEX_SIZE) * 2
|
|
local function get_tower_tooltip_text_node(tower_type)
|
|
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 cost = get_tower_cost(tower_type)
|
|
|
|
if not (name or placement_rules or short_desc or cost) then
|
|
return am.group():tag"tower_tooltip_text"
|
|
end
|
|
|
|
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",
|
|
am.rect(WIN.left, WIN.bottom, WIN.right, WIN.bottom + toolbelt_height, COLORS.TRANSPARENT)
|
|
}:tag"toolbelt"
|
|
|
|
local padding = 15
|
|
local size = toolbelt_height - padding
|
|
local half_size = size/2
|
|
local offset = vec2(WIN.left + padding*3, WIN.bottom + padding/3)
|
|
local tab_button = am.translate(vec2(0, half_size) + offset) ^ am.group{
|
|
pack_texture_into_sprite(TEXTURES.WIDER_BUTTON1, 54, 32),
|
|
pack_texture_into_sprite(TEXTURES.TAB_ICON, 25, 25)
|
|
}
|
|
toolbelt:append(tab_button)
|
|
local tower_select_square = (
|
|
am.translate(vec2(size + padding, half_size) + offset)
|
|
^ am.rect(-size/2-3, -size/2-3, size/2+3, size/2+3, COLORS.SUNRAY)
|
|
):tag"tower_select_square"
|
|
tower_select_square.hidden = true
|
|
toolbelt:append(tower_select_square)
|
|
|
|
local keys = { '1', '2', '3', '4', 'q', 'w', 'e', 'r', 'a', 's', 'd', 'f' }
|
|
-- order of this array is the order of towers on the toolbelt.
|
|
local tower_type_values = {
|
|
TOWER_TYPE.WALL,
|
|
TOWER_TYPE.HOWITZER,
|
|
TOWER_TYPE.REDEYE,
|
|
TOWER_TYPE.MOAT,
|
|
TOWER_TYPE.RADAR,
|
|
TOWER_TYPE.LIGHTHOUSE
|
|
}
|
|
for i = 1, #keys do
|
|
local icon_texture = get_tower_icon_texture(tower_type_values[i])
|
|
toolbelt:append(
|
|
toolbelt_button(
|
|
size,
|
|
half_size,
|
|
icon_texture,
|
|
padding,
|
|
i,
|
|
offset,
|
|
keys[i]
|
|
)
|
|
)
|
|
end
|
|
|
|
select_tower_type = function(tower_type)
|
|
state.selected_tower_type = tower_type
|
|
|
|
if get_tower_spec(tower_type) then
|
|
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 toolbelt("tower_select_square").hidden then
|
|
toolbelt("tower_select_square").position2d = new_position
|
|
toolbelt("tower_select_square").hidden = false
|
|
else
|
|
toolbelt("tower_select_square"):action(am.tween(0.1, { position2d = new_position }))
|
|
end
|
|
|
|
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
|
|
toolbelt("tower_select_square").hidden = true
|
|
|
|
WIN.scene:replace("cursor", make_hex_cursor(0, COLORS.TRANSPARENT))
|
|
end
|
|
end
|
|
|
|
return toolbelt
|
|
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(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()
|
|
|
|
for _,h in pairs(map) do
|
|
local hexagon = am.circle(hex_to_pixel(h), HEX_SIZE, color or color_f(h), 6)
|
|
group:append(hexagon)
|
|
end
|
|
|
|
if action_f then
|
|
group:action(action_f)
|
|
end
|
|
|
|
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 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,
|
|
am.translate(OFF_SCREEN):tag"cursor_translate" ^ make_hex_cursor(0, COLORS.TRANSPARENT),
|
|
score,
|
|
money,
|
|
top_right_display,
|
|
make_game_toolbelt(),
|
|
curtain,
|
|
}:tag"game"
|
|
|
|
scene:action(game_action)
|
|
|
|
return scene
|
|
end
|
|
|
|
function game_init()
|
|
state = get_initial_game_state()
|
|
build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR)
|
|
|
|
WIN.scene:remove("game")
|
|
WIN.scene:append(game_scene())
|
|
end
|
|
|