hexyz is tower defense game, and a lua library for dealing with hexagonal grids
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.

205 lines
6.3 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. MOBS = {}
  2. MAX_MOB_SIZE = hex_height(HEX_SIZE, ORIENTATION.FLAT) / 2
  3. MOB_SIZE = MAX_MOB_SIZE
  4. function mobs_on_hex(hex)
  5. local t = {}
  6. for mob_index,mob in pairs(MOBS) do
  7. if mob and mob.hex == hex then
  8. table.insert(t, mob_index, mob)
  9. end
  10. end
  11. return t
  12. end
  13. -- @NOTE returns i,v in the table
  14. function mob_on_hex(hex)
  15. return table.find(MOBS, function(mob)
  16. return mob and mob.hex == hex
  17. end)
  18. end
  19. -- check if a the tile at |hex| is passable by |mob|
  20. function mob_can_pass_through(mob, hex)
  21. local tile = state.map.get(hex.x, hex.y)
  22. return tile_is_medium_elevation(tile)
  23. end
  24. function mob_die(mob, mob_index)
  25. vplay_sfx(SOUNDS.EXPLOSION1)
  26. delete_entity(MOBS, mob_index)
  27. end
  28. function do_hit_mob(mob, damage, mob_index)
  29. mob.health = mob.health - damage
  30. if mob.health <= 0 then
  31. update_score(mob.bounty)
  32. update_money(mob.bounty)
  33. mob_die(mob, mob_index)
  34. end
  35. end
  36. -- @TODO performance.
  37. -- try reducing map size by identifying key nodes (inflection points)
  38. -- there are performance hits everytime we spawn a mob and it's Astar's fault
  39. function get_mob_path(mob, map, start, goal)
  40. return Astar(map, goal, start, grid_heuristic, grid_cost)
  41. end
  42. -- @FIXME there's a bug here where the position of the spawn hex is sometimes 1 closer to the center than we want
  43. local function get_spawn_hex()
  44. -- ensure we spawn on an random tile along the map's edges
  45. local roll = math.random(HEX_GRID_WIDTH * 2 + HEX_GRID_HEIGHT * 2) - 1
  46. local x, y
  47. if roll < HEX_GRID_HEIGHT then
  48. x, y = 0, roll
  49. elseif roll < (HEX_GRID_WIDTH + HEX_GRID_HEIGHT) then
  50. x, y = roll - HEX_GRID_HEIGHT, HEX_GRID_HEIGHT - 1
  51. elseif roll < (HEX_GRID_HEIGHT * 2 + HEX_GRID_WIDTH) then
  52. x, y = HEX_GRID_WIDTH - 1, roll - HEX_GRID_WIDTH - HEX_GRID_HEIGHT
  53. else
  54. x, y = roll - (HEX_GRID_HEIGHT * 2) - HEX_GRID_WIDTH, 0
  55. end
  56. -- @NOTE negate 'y' because hexyz algorithms assume south is positive, in amulet north is positive
  57. return evenq_to_hex(vec2(x, -y))
  58. end
  59. local function update_mob(mob, mob_index)
  60. local last_frame_hex = mob.hex
  61. mob.hex = pixel_to_hex(mob.position)
  62. if mob.hex == HEX_GRID_CENTER then
  63. update_score(-mob.health)
  64. mob_die(mob, mob_index)
  65. return true
  66. end
  67. -- figure out movement
  68. if last_frame_hex ~= mob.hex or not mob.frame_target then
  69. local frame_target, tile = false, false
  70. if mob.path then
  71. --log('A*')
  72. -- we have an explicitly stored target
  73. local path_entry = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y]
  74. if not path_entry then
  75. -- we should be just about to reach the target, delete the path.
  76. mob.path = false
  77. mob.frame_target = false
  78. return
  79. end
  80. mob.frame_target = path_entry.hex
  81. -- check if our target is valid, and if it's not we aren't going to move this frame.
  82. -- recalculate our path.
  83. if last_frame_hex ~= mob.hex and not mob_can_pass_through(mob, mob.frame_target) then
  84. log('recalc')
  85. mob.path = get_mob_path(mob, HEX_MAP, mob.hex, HEX_GRID_CENTER)
  86. mob.frame_target = false
  87. end
  88. else
  89. -- use the map's flow field - gotta find the the best neighbour
  90. local neighbours = state.map.neighbours(mob.hex)
  91. if #neighbours > 0 then
  92. local first_neighbour = neighbours[1]
  93. tile = state.map.get(first_neighbour.x, first_neighbour.y)
  94. local lowest_cost_hex = first_neighbour
  95. local lowest_cost = tile.priority or 0
  96. for _,n in pairs(neighbours) do
  97. tile = state.map.get(n.x, n.y)
  98. local current_cost = tile.priority
  99. if current_cost and current_cost < lowest_cost then
  100. lowest_cost_hex = n
  101. lowest_cost = current_cost
  102. end
  103. end
  104. mob.frame_target = lowest_cost_hex
  105. else
  106. log('no neighbours')
  107. end
  108. end
  109. end
  110. if mob.frame_target and mob.frame_target == last_frame_hex then
  111. --log('backpedaling')
  112. end
  113. -- do movement
  114. if mob.frame_target then
  115. -- it's totally possible that the target we have was invalidated by a tower placed this frame,
  116. -- or between when we last calculated this target and now
  117. -- check for that now
  118. if mob_can_pass_through(mob, mob.frame_target) then
  119. -- this is supposed to achieve frame rate independence, but i have no idea if it actually does
  120. -- the constant multiplier at the beginning is how many pixels we want a mob with speed 1 to move in one frame
  121. local rate = 4 * mob.speed / state.perf.avg_fps
  122. mob.position = mob.position + math.normalize(hex_to_pixel(mob.frame_target) - mob.position) * rate
  123. mob.node.position2d = mob.position
  124. else
  125. mob.frame_target = false
  126. end
  127. else
  128. log('no target')
  129. end
  130. -- passive animation
  131. if math.random() < 0.01 then
  132. mob.node"rotate":action(am.tween(0.3, { angle = mob.node"rotate".angle + math.pi*3 }))
  133. else
  134. mob.node"rotate".angle = math.wrapf(mob.node"rotate".angle + am.delta_time, math.pi*2)
  135. end
  136. end
  137. local function make_and_register_mob(mob_type)
  138. local mob = make_basic_entity(
  139. get_spawn_hex(),
  140. am.rotate(state.time) ^ pack_texture_into_sprite(TEXTURES.MOB_BEEPER, MOB_SIZE, MOB_SIZE),
  141. update_mob
  142. )
  143. --mob.path = get_mob_path(mob, HEX_MAP, mob.hex, HEX_GRID_CENTER)
  144. mob.health = 10
  145. mob.speed = 10
  146. mob.bounty = 5
  147. mob.hurtbox_radius = MOB_SIZE/2
  148. register_entity(MOBS, mob)
  149. end
  150. local SPAWN_CHANCE = 45
  151. function do_mob_spawning()
  152. --if WIN:key_pressed"space" then
  153. if math.random(SPAWN_CHANCE) == 1 then
  154. --if #MOBS < 1 then
  155. make_and_register_mob()
  156. end
  157. end
  158. function delete_all_mobs()
  159. for mob_index,mob in pairs(MOBS) do
  160. if mob then delete_entity(MOBS, mob_index) end
  161. end
  162. end
  163. function do_mob_updates()
  164. for mob_index,mob in pairs(MOBS) do
  165. if mob and mob.update then
  166. mob.update(mob, mob_index)
  167. end
  168. end
  169. end