From ea0d10c93e3506841cb6685b418bba2987f2e621 Mon Sep 17 00:00:00 2001 From: Nicholas Hayashi Date: Fri, 19 Feb 2021 16:04:59 -0500 Subject: [PATCH] basic serialization --- main.lua | 16 ++--- src/entity.lua | 30 ++++++++-- src/game.lua | 115 +++++++++++++++++++++++++++++++++--- src/grid.lua | 143 +++++++++++++++++++++++++++++---------------- src/hexyz.lua | 92 +++++++++-------------------- src/mob.lua | 52 +++++++++++------ src/projectile.lua | 40 +++++++++---- src/tower.lua | 63 +++++++++++++------- 8 files changed, 367 insertions(+), 184 deletions(-) diff --git a/main.lua b/main.lua index 785ff5b..906da57 100644 --- a/main.lua +++ b/main.lua @@ -16,13 +16,13 @@ math.random() do win = am.window{ - width = settings.window_width, - height = settings.window_height, - title = "hexyz", - mode = settings.fullscreen and "fullscreen" or "windowed", - highdpi = true, - letterbox = true, - resizable = true, -- user should probably set their resolution instead of resizing the window, but hey. + width = settings.window_width, + height = settings.window_height, + title = "hexyz", + mode = settings.fullscreen and "fullscreen" or "windowed", + highdpi = true, + letterbox = true, + resizable = true, -- user should probably set their resolution instead of resizing the window, but hey. } end @@ -46,6 +46,6 @@ function main_action() end function main_scene() end win.scene = am.group() -game_init() +game_init(nil) noglobals() diff --git a/src/entity.lua b/src/entity.lua index a45617b..e818233 100644 --- a/src/entity.lua +++ b/src/entity.lua @@ -7,7 +7,7 @@ entity structure: hex - vec2 - current occupied hex, if any position - vec2 - current pixel position of it's translate (forced parent) node update - function - runs every frame with itself and its index in some array as an argument - node - node - scene graph node + node - node - scene graph node - should be initialized by caller after, though all entities have a node type - enum - sub type - unset if 'basic' entity props - table - table of properties specific to this entity subtype @@ -15,7 +15,7 @@ entity structure: ... - any - a bunch of other shit depending on what entity type it is } --]] -function make_basic_entity(hex, node, update, position) +function make_basic_entity(hex, update, position) local entity = {} entity.TOB = state.time @@ -33,8 +33,8 @@ function make_basic_entity(hex, node, update, position) end entity.update = update - entity.node = am.translate(entity.position) ^ node - entity.type = false + entity.node = false -- set by caller + entity.type = false -- set by caller entity.props = {} return entity @@ -45,9 +45,11 @@ function register_entity(t, entity) state.world:append(entity.node) end --- |t| is the source table, probably MOBS, TOWERS, or PROJECTILES +-- |t| is the source table, probably state.mobs, state.towers, or state.projectiles function delete_entity(t, index) - if not t then log("splat!") end + if not t then + error("splat!") + end state.world:remove(t[index].node) t[index] = false -- leave empty indexes so other entities can learn that this entity was deleted @@ -65,3 +67,19 @@ function do_entity_updates() do_projectile_updates() end +function entity_basic_devectored_copy(entity) + local copy = table.shallow_copy(entity) + copy.position = { copy.position.x, copy.position.y } + copy.hex = { copy.hex.x, copy.hex.y } + + return copy +end + +function entity_basic_json_parse(json_string) + local entity = am.parse_json(json_string) + entity.position = vec2(entity.position[0], entity.position[1]) + entity.hex = vec2(entity.hex[0], entity.hex[1]) + + return entity +end + diff --git a/src/game.lua b/src/game.lua index 6eaf290..5cf136d 100644 --- a/src/game.lua +++ b/src/game.lua @@ -2,9 +2,9 @@ state = {} -function game_end() +function game_end(saved_state) delete_all_entities() - game_init() + game_init(saved_state) end function update_score(diff) state.score = state.score + diff end @@ -37,6 +37,10 @@ local function get_initial_game_state(seed) score = 0, -- current game score money = STARTING_MONEY, -- current money + towers = {}, + mobs = {}, + projectiles = {}, + current_wave = 1, time_until_next_wave = 0, time_until_next_break = 0, @@ -78,7 +82,7 @@ local function get_top_right_display_text(hex, evenq, centered_evenq, display_ty str = "SEED: " .. state.map.seed elseif display_type == TRDTS.TILE then - str = table.tostring(state.map.get(hex.x, hex.y)) + str = table.tostring(map_get(state.map, hex)) end return str end @@ -119,7 +123,7 @@ local function game_pause() } :tag"pause_menu") - win.scene:action(function() + win.scene:action(function(self) if win:key_pressed("escape") then win.scene:remove("pause_menu") win.scene("game").paused = false @@ -132,6 +136,84 @@ local function game_pause() end) end +local function game_deserialize(json_string) + -- @TODO decode from some compressed format or whatever + local new_state = am.parse_json(json_string) + new_state.map, new_state.world = random_map(new_state.seed) + new_state.seed = nil + + new_state.towers = {} + for i,t in pairs(new_state.towers) do + if t then + new_state.towers[i] = tower_deserialize(t) + + -- @STATEFUL, shouldn't be done here + new_state.world:append(new_state.towers[i].node) + end + end + + new_state.mobs = {} + for i,m in pairs(new_state.mobs) do + if m then + new_state.mobs[i] = mob_deserialize(m) + + -- @STATEFUL, shouldn't be done here + new_state.world:append(new_state.mobs[i].node) + end + end + + new_state.projectiles = {} + for i,p in pairs(new_state.projectiles) do + if p then + new_state.projectiles[i] = projectile_deserialize(p) + + -- @STATEFUL, shouldn't be done here + new_state.world:append(new_state.projectiles[i].node) + end + end + + return new_state +end + +local function game_serialize() + local serialized = table.shallow_copy(state) + serialized.seed = state.map.seed + serialized.map = nil -- we re-generate the entire map from the seed on de-serialize + + -- in order to serialize the game state, we have to convert all relevant userdata into + -- something else. this practically only means vectors need to become arrays of floats. + -- this is dumb and if i forsaw this i would have probably used float arrays the whole time. + + serialized.towers = {} + for i,t in pairs(state.towers) do + if t then + serialized.towers[i] = tower_serialize(t) + end + end + + serialized.mobs = {} + for i,m in pairs(state.mobs) do + if m then + serialized.mobs[i] = mob_serialize(m) + end + end + + serialized.projectiles = {} + for i,p in pairs(state.projectiles) do + if p then + serialized.projectiles[i] = projectile_serialize(p) + end + end + + -- @TODO b64 encode or otherwise scramble/compress + return am.to_json(serialized) +end + +local function game_save() + am.save_state("save", game_serialize(), "json") + log("succesfully saved!") +end + local function game_action(scene) if state.score < 0 then game_end() return true end @@ -168,7 +250,7 @@ local function game_action(scene) 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 tile = map_get(state.map, hex) local interactable = evenq_is_in_interactable_region(evenq{ y = -evenq.y }) local buildable = tower_type_is_buildable_on(hex, tile, state.selected_tower_type) @@ -223,6 +305,13 @@ local function game_action(scene) elseif win:key_pressed"f2" then state.world"flow_field".hidden = not state.world"flow_field".hidden + elseif win:key_pressed"f3" then + game_save() + + elseif win:key_pressed"f4" then + game_end(am.load_state("save", "json")) + return true + elseif win:key_pressed"tab" then if win:key_down"lshift" then select_toolbelt_button((state.selected_toolbelt_button + table.count(TOWER_TYPE) - 2) % table.count(TOWER_TYPE) + 1) @@ -468,14 +557,24 @@ function game_scene() return scene end -function game_init() - state = get_initial_game_state() +function game_init(saved_state) + if saved_state then + state = game_deserialize(saved_state) + + -- scene nodes aren't (can't be?) serialized, so we re-generate them if we're loading from a save + + + else + state = get_initial_game_state() + end + --[[ local home_tower = build_tower(HEX_GRID_CENTER, TOWER_TYPE.RADAR) for _,h in pairs(home_tower.hexes) do -- @HACK to make the center tile(s) passable even though there's a tower on it - state.map.get(h.x, h.y).elevation = 0 + map_get(state.map, h).elevation = 0 end + ]] win.scene:remove("game") win.scene:append(game_scene()) diff --git a/src/grid.lua b/src/grid.lua index e5e7975..2f43e96 100644 --- a/src/grid.lua +++ b/src/grid.lua @@ -75,27 +75,12 @@ function tile_is_medium_elevation(tile) return tile.elevation >= -0.5 and tile.elevation < 0.5 end -function color_at(elevation) - if elevation < -0.5 then -- lowest elevation - return COLORS.WATER{ a = (elevation + 1.4) / 2 + 0.2 } - - elseif elevation < 0 then -- med-low elevation - return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.3 } - - elseif elevation < 0.5 then -- med-high elevation - return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.3 } - - elseif elevation < 1 then -- high elevation - return COLORS.MOUNTAIN{ ra = elevation } - end -end - function grid_heuristic(source, target) return math.distance(source, target) end function grid_cost(map, from, to) - local t1, t2 = map.get(from.x, from.y), map.get(to.x, to.y) + local t1, t2 = map_get(map, from), map_get(map, to) -- i have no fucking clue why, but adding +0.2 to the end of this fixes a bug where sometimes two (or more) -- equivalent paths are found and mobs backpedal trying to decide between them @@ -107,11 +92,18 @@ function grid_cost(map, from, to) local epsilon = elevation_epsilon local cost = elevation_cost - return epsilon - cost + return 1 +end + +function grid_neighbours(map, hex) + return table.filter(hex_neighbours(hex), function(_hex) + local tile = map_get(map, _hex) + return tile and tile_is_medium_elevation(tile) + end) end function generate_flow_field(map, start) - return dijkstra(map, start, nil, grid_cost) + return dijkstra(map, start, nil, grid_cost, grid_neighbours) end function apply_flow_field(map, flow_field, world) @@ -123,7 +115,7 @@ function apply_flow_field(map, flow_field, world) local overlay_group = am.group():tag"flow_field" for i,_ in pairs(map) do for j,f in pairs(map[i]) do - local flow = hex_map_get(flow_field, i, j) + local flow = map_get(flow_field, i, j) if flow then map[i][j].priority = flow.priority @@ -142,12 +134,38 @@ function apply_flow_field(map, flow_field, world) end end +-- some convenience functions for setting and retrieving values from a 2d sparse array +-- where the first index might return a nil value, causing the second second to crash the game +-- and where it's often the case that the indexer is a vec2 +function map_get(map, hex, y) + if y then return map[hex] and map[hex][y] end + return map[hex.x] and map[hex.x][hex.y] +end + +function map_set(map, hex, y, v) + if v then + if map[hex] then + map[hex][y] = v + else + map[hex] = {} + map[hex][y] = v + end + else + if map[hex.x] then + map[hex.x][hex.y] = y + else + map[hex.x] = {} + map[hex.x][hex.y] = y + end + end +end + function building_tower_breaks_flow_field(tower_type, hex) local original_elevations = {} local all_impassable = true local hexes = spiral_map(hex, get_tower_size(tower_type)) for _,h in pairs(hexes) do - local tile = state.map.get(h.x, h.y) + local tile = map_get(state.map, h) if all_impassable and mob_can_pass_through(nil, h) then all_impassable = false @@ -164,25 +182,41 @@ function building_tower_breaks_flow_field(tower_type, hex) -- (besides return all the tile's elevations back to their original state) if all_impassable then for i,h in pairs(hexes) do - state.map.get(h.x, h.y).elevation = original_elevations[i] + map_get(state.map, h).elevation = original_elevations[i] end return false end local flow_field = generate_flow_field(state.map, HEX_GRID_CENTER) - local result = not hex_map_get(flow_field, 0, 0) + local result = not map_get(flow_field, 0, 0) for i,h in pairs(hexes) do - state.map.get(h.x, h.y).elevation = original_elevations[i] + map_get(state.map, h).elevation = original_elevations[i] end return result, flow_field end -function random_map(seed) - local map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, seed) - math.randomseed(map.seed) +function make_hex_grid_scene(map) + local function color_at(elevation) + if elevation < -0.5 then -- lowest elevation + return COLORS.WATER{ a = (elevation + 1.4) / 2 + 0.2 } + + elseif elevation < 0 then -- med-low elevation + return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.3 } + + elseif elevation < 0.5 then -- med-high elevation + return math.lerp(COLORS.DIRT, COLORS.GRASS, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.3 } + elseif elevation < 1 then -- high elevation + return COLORS.MOUNTAIN{ ra = elevation } + + else + -- @TODO probably fix... this only happens when loading a save, and the tile has an elevation that's + -- higher that anything here + return vec4(0.1) + end + end -- the world's appearance relies largely on a backdrop which can be scaled in -- tone to give the appearance of light or darkness -- @NOTE replace this with a shader program @@ -197,6 +231,38 @@ function random_map(seed) :tag"negative_mask" local world = am.group(neg_mask):tag"world" + for i,_ in pairs(map) do + for j,tile in pairs(map[i]) do + local evenq = hex_to_evenq(vec2(i, j)) + + -- light shading on edge cells + local mask = vec4(0, 0, 0, math.max(((evenq.x - HEX_GRID_WIDTH/2) / HEX_GRID_WIDTH) ^ 2 + , ((-evenq.y - HEX_GRID_HEIGHT/2) / HEX_GRID_HEIGHT) ^ 2)) + + local color = color_at(tile.elevation) - mask + + local node = am.translate(hex_to_pixel(vec2(i, j), vec2(HEX_SIZE))) + ^ am.circle(vec2(0), HEX_SIZE, color, 6) + + map_set(map, i, j, { + elevation = tile.elevation, + node = node + }) + + world:append(node) + end + end + + apply_flow_field(map, generate_flow_field(map, HEX_GRID_CENTER), world) + + return am.translate(WORLDSPACE_COORDINATE_OFFSET) ^ world +end + +function random_map(seed) + local map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, seed) + math.randomseed(map.seed) + + -- there are some things about the generated map we'd like to change... for i,_ in pairs(map) do for j,noise in pairs(map[i]) do local evenq = hex_to_evenq(vec2(i, j)) @@ -220,33 +286,12 @@ function random_map(seed) noise = noise * d^0.125 -- arbitrary, seems to work good end - -- light shading on edge cells - local mask = vec4(0, 0, 0, math.max(((evenq.x - HEX_GRID_WIDTH/2) / HEX_GRID_WIDTH) ^ 2 - , ((-evenq.y - HEX_GRID_HEIGHT/2) / HEX_GRID_HEIGHT) ^ 2)) - local color = color_at(noise) - mask - - local node = am.translate(hex_to_pixel(vec2(i, j), vec2(HEX_SIZE))) - ^ am.circle(vec2(0), HEX_SIZE, color, 6) - - map.set(i, j, { + map_set(map, i, j, { elevation = noise, - node = node }) - - world:append(node) end end - getmetatable(map).__index.neighbours = function(hex) - return table.filter(hex_neighbours(hex), function(_hex) - --local interactable = evenq_is_in_interactable_region(hex_to_evenq(_hex)) - local tile = map.get(_hex.x, _hex.y) - return tile and tile_is_medium_elevation(tile) - end) - end - - apply_flow_field(map, generate_flow_field(map, HEX_GRID_CENTER), world) - - return map, am.translate(WORLDSPACE_COORDINATE_OFFSET) ^ world + return map, make_hex_grid_scene(map) end diff --git a/src/hexyz.lua b/src/hexyz.lua index df93d3b..87cdc82 100644 --- a/src/hexyz.lua +++ b/src/hexyz.lua @@ -244,47 +244,27 @@ function spiral_map(center, radius) return setmetatable(map, {__index={center=center, radius=radius}}) end -local function map_get(t, x, y) - return t[x] and t[x][y] -end -function hex_map_get(t, x, y) - return map_get(t, x, y) -end - -local function map_set(t, x, y, v) - if t[x] then - t[x][y] = v - else - t[x] = {} - t[x][y] = v - end - - return t -end -function hex_map_set(t, x, y, v) - return map_set(t, x, y, v) -end - -local function map_traverse(t, callback) - for i,_ in pairs(t) do - for _,entry in pairs(t[i]) do - callback(entry) +local function map_get(map, hex, y) + if y then return map[hex] and map[hex][y] end + return map[hex.x] and map[hex.x][hex.y] +end + +local function map_set(map, hex, y, v) + if v then + if map[hex] then + map[hex][y] = v + else + map[hex] = {} + map[hex][y] = v end - end -end - --- @NOTE probably shouldn't use this... -local function map_partial_set(t, x, y, k, v) - local entry = map_get(t, x, y) - - if not entry then - map_set(t, x, y, { k = v }) - else - entry.k = v + if map[hex.x] then + map[hex.x][hex.y] = y + else + map[hex.x] = {} + map[hex.x][hex.y] = y + end end - - return t end -- Returns Unordered Parallelogram-Shaped Map of |width| and |height| with Simplex Noise @@ -314,13 +294,9 @@ function parallelogram_map(width, height, seed) width = width, height = height, seed = seed, - get = function(x, y) return map_get(map, x, y) end, - set = function(x, y, v) return map_set(map, x, y, v) end, - partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end, - traverse = function(callback) return map_traverse(map, callback) end, neighbours = function(hex) return table.filter(hex_neighbours(hex), function(_hex) - return map.get(_hex.x, _hex.y) + return map_get(map, _hex) end) end }}) @@ -352,13 +328,9 @@ function triangular_map(size, seed) return setmetatable(map, { __index = { size = size, seed = seed, - get = function(x, y) return map_get(map, x, y) end, - set = function(x, y, v) return map_set(map, x, y, v) end, - partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end, - traverse = function(callback) return map_traverse(map, callback) end, neighbours = function(hex) return table.filter(hex_neighbours(hex), function(_hex) - return map.get(_hex.x, _hex.y) + return map_get(map, _hex) end) end }}) @@ -395,13 +367,9 @@ function hexagonal_map(radius, seed) return setmetatable(map, { __index = { radius = radius, seed = seed, - get = function(x, y) return map_get(map, x, y) end, - set = function(x, y, v) return map_set(map, x, y, v) end, - partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end, - traverse = function(callback) return map_traverse(map, callback) end, neighbours = function(hex) return table.filter(hex_neighbours(hex), function(_hex) - return map.get(_hex.x, _hex.y) + return map_get(map, _hex.x, _hex.y) end) end }}) @@ -437,13 +405,9 @@ function rectangular_map(width, height, seed) width = width, height = height, seed = seed, - get = function(x, y) return map_get(map, x, y) end, - set = function(x, y, v) return map_set(map, x, y, v) end, - partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end, - traverse = function(callback) return map_traverse(map, callback) end, neighbours = function(hex) return table.filter(hex_neighbours(hex), function(_hex) - return map.get(_hex.x, _hex.y) + return map_get(map, _hex) end) end }}) @@ -477,7 +441,7 @@ function breadth_first(map, start) return distance end -function dijkstra(map, start, goal, cost_f) +function dijkstra(map, start, goal, cost_f, neighbour_f) local frontier = {} frontier[1] = { hex = start, priority = 0 } @@ -496,15 +460,15 @@ function dijkstra(map, start, goal, cost_f) break end - for _,neighbour in pairs(map.neighbours(current.hex)) do - local new_cost = map_get(cost_so_far, current.hex.x, current.hex.y) + cost_f(map, current.hex, neighbour) - local neighbour_cost = map_get(cost_so_far, neighbour.x, neighbour.y) + for _,neighbour in pairs(neighbour_f(map, current.hex)) do + local new_cost = map_get(cost_so_far, current.hex) + cost_f(map, current.hex, neighbour) + local neighbour_cost = map_get(cost_so_far, neighbour) if not neighbour_cost or (new_cost < neighbour_cost) then - map_set(cost_so_far, neighbour.x, neighbour.y, new_cost) + map_set(cost_so_far, neighbour, new_cost) local priority = new_cost + math.distance(start, neighbour) table.insert(frontier, { hex = neighbour, priority = priority }) - map_set(came_from, neighbour.x, neighbour.y, current) + map_set(came_from, neighbour, current) end end end diff --git a/src/mob.lua b/src/mob.lua index ef828fc..b0aa007 100644 --- a/src/mob.lua +++ b/src/mob.lua @@ -1,6 +1,6 @@ -MOBS = {} +state.mobs = {} MOB_TYPE = { BEEPER = 1, @@ -46,7 +46,7 @@ end function mobs_on_hex(hex) local t = {} - for mob_index,mob in pairs(MOBS) do + for mob_index,mob in pairs(state.mobs) do if mob and mob.hex == hex then table.insert(t, mob_index, mob) end @@ -56,20 +56,20 @@ end function mob_on_hex(hex) -- table.find returns i,v in the table - return table.find(MOBS, function(mob) + return table.find(state.mobs, function(mob) return mob and mob.hex == hex end) end -- check if a the tile at |hex| is passable by |mob| function mob_can_pass_through(mob, hex) - local tile = state.map.get(hex.x, hex.y) + local tile = map_get(state.map, hex) return tile_is_medium_elevation(tile) end function mob_die(mob, mob_index) vplay_sfx(SOUNDS.EXPLOSION1) - delete_entity(MOBS, mob_index) + delete_entity(state.mobs, mob_index) end local HEALTHBAR_WIDTH = HEX_PIXEL_WIDTH/2 @@ -158,7 +158,7 @@ local function resolve_frame_target_for_mob(mob, mob_index) local frame_target, tile = false, false if mob.path then -- we (should) have an explicitly stored target - local path_entry = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y] + local path_entry = map_get(mob.path, mob.hex) if not path_entry then -- we should be just about to reach the target, delete the path. @@ -176,16 +176,16 @@ local function resolve_frame_target_for_mob(mob, mob_index) end else -- use the map's flow field - gotta find the the best neighbour - local neighbours = state.map.neighbours(mob.hex) + local neighbours = grid_neighbours(state.map, mob.hex) if #neighbours > 0 then local first_neighbour = neighbours[1] - tile = state.map.get(first_neighbour.x, first_neighbour.y) + tile = map_get(state.map, first_neighbour) local lowest_cost_hex = first_neighbour local lowest_cost = tile.priority or 0 for _,n in pairs(neighbours) do - tile = state.map.get(n.x, n.y) + tile = map_get(state.map, n) if not tile.priority then -- if there's no stored priority, that should mean it's the center tile @@ -247,8 +247,8 @@ local function update_mob_beeper(mob, mob_index) -- or between when we last calculated this target and now -- check for that now if mob_can_pass_through(mob, mob.frame_target) then - local from = state.map.get(mob.hex.x, mob.hex.y) - local to = state.map.get(mob.frame_target.x, mob.frame_target.y) + local from = map_get(state.map, mob.hex) + local to = map_get(state.map, mob.frame_target) local rate = (4 * mob.speed - math.abs(to.elevation - from.elevation)) * am.delta_time mob.position = mob.position + math.normalize(hex_to_pixel(mob.frame_target, vec2(HEX_SIZE)) - mob.position) * rate @@ -257,7 +257,7 @@ local function update_mob_beeper(mob, mob_index) mob.frame_target = false end else - log('no target') + --log('no target') end -- passive animation @@ -280,11 +280,11 @@ end local function make_and_register_mob(mob_type) local mob = make_basic_entity( get_spawn_hex(), - make_mob_node(mob_type), get_mob_update_function(mob_type) ) mob.type = mob_type + mob.node = am.translate(mob.position) ^ make_mob_node(mob_type, mob) local spec = get_mob_spec(mob_type) mob.health = grow_mob_health(mob_type, spec.health, state.time) @@ -293,26 +293,42 @@ local function make_and_register_mob(mob_type) mob.hurtbox_radius = spec.hurtbox_radius mob.healthbar = mob.node:child(1):child(2):child(1) -- lmao - register_entity(MOBS, mob) + register_entity(state.mobs, mob) + return mob +end + +function mob_serialize(mob) + local serialized = entity_basic_devectored_copy(mob) + + return am.to_json(serialized) +end + +function mob_deserialize(json_string) + local mob = entity_basic_json_parse(json_string) + + mob.update = get_mob_update_function(mob.type) + mob.node = am.translate(mob.position) ^ make_mob_node(mob.type, mob) + mob.healthbar = mob.node:child(1):child(2):child(1) -- lmaoooo + return mob end function do_mob_spawning(spawn_chance) --if win:key_pressed"space" then if state.spawning and math.random(spawn_chance) == 1 then - --if #MOBS < 1 then + --if #state.mobs < 1 then make_and_register_mob(MOB_TYPE.BEEPER) end end function delete_all_mobs() - for mob_index,mob in pairs(MOBS) do - if mob then delete_entity(MOBS, mob_index) end + for mob_index,mob in pairs(state.mobs) do + if mob then delete_entity(state.mobs, mob_index) end end end function do_mob_updates() - for mob_index,mob in pairs(MOBS) do + for mob_index,mob in pairs(state.mobs) do if mob and mob.update then mob.update(mob, mob_index) end diff --git a/src/projectile.lua b/src/projectile.lua index f82a270..b716b95 100644 --- a/src/projectile.lua +++ b/src/projectile.lua @@ -1,6 +1,6 @@ -PROJECTILES = {} +state.projectiles = {} PROJECTILE_TYPE = { SHELL = 1, @@ -72,7 +72,7 @@ local function update_projectile_shell(projectile, projectile_index) x2 = win.right, y2 = win.top }) then - delete_entity(PROJECTILES, projectile_index) + delete_entity(state.projectiles, projectile_index) return true end @@ -103,7 +103,7 @@ local function update_projectile_shell(projectile, projectile_index) end end - local tile = state.map.get(projectile.hex.x, projectile.hex.y) + local tile = map_get(state.map, projectile.hex) if tile and tile.elevation >= projectile.props.z then --do_explode = true @@ -117,7 +117,7 @@ local function update_projectile_shell(projectile, projectile_index) do_hit_mob(mob, damage, index) end win.scene:append(make_shell_explosion_node(projectile.position)) - delete_entity(PROJECTILES, projectile_index) + delete_entity(state.projectiles, projectile_index) return true end end @@ -132,7 +132,7 @@ local function update_projectile_laser(projectile, projectile_index) x2 = win.right, y2 = win.top }) then - delete_entity(PROJECTILES, projectile_index) + delete_entity(state.projectiles, projectile_index) return true end @@ -177,7 +177,7 @@ local function update_projectile_laser(projectile, projectile_index) -- hit the mob, delete ourselves, affect the world do_hit_mob(closest_mob, projectile.damage, closest_mob_index) - delete_entity(PROJECTILES, projectile_index) + delete_entity(state.projectiles, projectile_index) vplay_sfx(SOUNDS.HIT1, 0.5) end @@ -202,11 +202,11 @@ end function make_and_register_projectile(hex, projectile_type, vector) local projectile = make_basic_entity( hex, - make_projectile_node(projectile_type, vector), 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) @@ -214,18 +214,36 @@ function make_and_register_projectile(hex, projectile_type, vector) projectile.damage = spec.damage projectile.hitbox_radius = spec.hitbox_radius - register_entity(PROJECTILES, projectile) + 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[0], projectile.vector[1]) + + 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 delete_all_projectiles() - for projectile_index,projectile in pairs(PROJECTILES) do - if projectile then delete_entity(PROJECTILES, projectile_index) end + for projectile_index,projectile in pairs(state.projectiles) do + if projectile then delete_entity(state.projectiles, projectile_index) end end end function do_projectile_updates() - for projectile_index,projectile in pairs(PROJECTILES) do + for projectile_index,projectile in pairs(state.projectiles) do if projectile and projectile.update then projectile.update(projectile, projectile_index) end diff --git a/src/tower.lua b/src/tower.lua index c575068..ea19089 100644 --- a/src/tower.lua +++ b/src/tower.lua @@ -1,6 +1,6 @@ -TOWERS = {} +state.towers = {} TOWER_TYPE = { WALL = 1, @@ -125,8 +125,7 @@ local function make_tower_sprite(tower_type) return pack_texture_into_sprite(get_tower_texture(tower_type), HEX_PIXEL_WIDTH, HEX_PIXEL_HEIGHT) end -local HEX_FLOWER_DIMENSIONS = vec2(115, 125) -local function make_tower_node(tower_type) +function make_tower_node(tower_type) if tower_type == TOWER_TYPE.REDEYE then return make_tower_sprite(tower_type) @@ -218,9 +217,33 @@ local function get_tower_update_function(tower_type) end end +function tower_serialize(tower) + local serialized = entity_basic_devectored_copy(tower) + + for i,h in pairs(serialized.hexes) do + serialized.hexes[i] = { h.x, h.y } + end + + return am.to_json(serialized) +end + +function tower_deserialize(json_string) + local tower = entity_basic_json_parse(json_string) + + tower.hexes = {} + for i,h in pairs(tower.hexes) do + tower.hexes[i] = vec2(tower.hexes[i][0], tower.hexes[i][1]) + end + + tower.update = get_tower_update_function(tower.type) + tower.node = am.translate(tower.position) ^ make_tower_node(tower_type) + + return tower +end + function towers_on_hex(hex) local t = {} - for tower_index,tower in pairs(TOWERS) do + for tower_index,tower in pairs(state.towers) do if tower then for _,h in pairs(tower.hexes) do if h == hex then @@ -234,7 +257,7 @@ function towers_on_hex(hex) end function tower_on_hex(hex) - return table.find(TOWERS, function(tower) + return table.find(state.towers, function(tower) for _,h in pairs(tower.hexes) do if h == hex then return true end end @@ -257,7 +280,7 @@ function tower_type_is_buildable_on(hex, tile, tower_type) table.merge(blocking_towers, towers_on_hex(h)) table.merge(blocking_mobs, mobs_on_hex(h)) - local tile = state.map.get(h.x, h.y) + local tile = map_get(state.map, h) -- this should always be true, unless it is possible to place a tower -- where part of the tower overflows the edge of the map if tile then @@ -309,7 +332,7 @@ function tower_type_is_buildable_on(hex, tile, tower_type) elseif tower_type == TOWER_TYPE.LIGHTHOUSE then local has_water_neighbour = false for _,h in pairs(hex_neighbours(hex)) do - local tile = state.map.get(h.x, h.y) + local tile = map_get(state.map, h) if tile and tile.elevation < -0.5 then has_water_neighbour = true @@ -331,7 +354,7 @@ end function update_tower_redeye(tower, tower_index) if not tower.target_index then - for index,mob in pairs(MOBS) do + for index,mob in pairs(state.mobs) do if mob then local d = math.distance(mob.hex, tower.hex) if d <= tower.range then @@ -341,11 +364,11 @@ function update_tower_redeye(tower, tower_index) end end else - if MOBS[tower.target_index] == false then + if state.mobs[tower.target_index] == false then tower.target_index = false elseif (state.time - tower.last_shot_time) > tower.fire_rate then - local mob = MOBS[tower.target_index] + local mob = state.mobs[tower.target_index] make_and_register_projectile( tower.hex, @@ -362,7 +385,7 @@ end function update_tower_howitzer(tower, tower_index) if not tower.target_index then -- we don't have a target - for index,mob in pairs(MOBS) do + for index,mob in pairs(state.mobs) do if mob then local d = math.distance(mob.hex, tower.hex) if d <= tower.range then @@ -374,13 +397,13 @@ function update_tower_howitzer(tower, tower_index) tower.node("rotate").angle = math.wrapf(tower.node("rotate").angle + 0.1 * am.delta_time, math.pi*2) else -- we should have a target - if MOBS[tower.target_index] == false then + if state.mobs[tower.target_index] == false then -- the target we have was invalidated tower.target_index = false else -- the target we have is valid - local mob = MOBS[tower.target_index] + local mob = state.mobs[tower.target_index] local vector = math.normalize(mob.position - tower.position) if (state.time - tower.last_shot_time) > tower.fire_rate then @@ -446,17 +469,17 @@ end function make_and_register_tower(hex, tower_type) local tower = make_basic_entity( hex, - make_tower_node(tower_type), get_tower_update_function(tower_type) ) tower.type = 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 + tower.last_shot_time = -spec.fire_rate -- lets the tower fire immediately upon being placed tower.size = spec.size if tower.size == 0 then tower.hexes = { tower.hex } @@ -466,7 +489,7 @@ function make_and_register_tower(hex, tower_type) tower.height = spec.height for _,h in pairs(tower.hexes) do - local tile = state.map.get(h.x, h.y) + local tile = map_get(state.map, h.x, h.y) tile.elevation = tile.elevation + tower.height end @@ -474,7 +497,7 @@ function make_and_register_tower(hex, tower_type) tower.props.z = tower.height end - register_entity(TOWERS, tower) + register_entity(state.towers, tower) return tower end @@ -486,13 +509,13 @@ function build_tower(hex, tower_type) end function delete_all_towers() - for tower_index,tower in pairs(TOWERS) do - if tower then delete_entity(TOWERS, tower_index) end + for tower_index,tower in pairs(state.towers) do + if tower then delete_entity(state.towers, tower_index) end end end function do_tower_updates() - for tower_index,tower in pairs(TOWERS) do + for tower_index,tower in pairs(state.towers) do if tower and tower.update then tower.update(tower, tower_index) end