mirror of
https://github.com/Refactorio/RedMew.git
synced 2025-01-05 22:53:39 +02:00
Merge remote-tracking branch 'upstream/develop' into diggy_cutscene
This commit is contained in:
commit
2b9ca4013a
@ -362,7 +362,9 @@ global.config = {
|
||||
},
|
||||
-- enables a command which allows for an end-game event
|
||||
apocalypse = {
|
||||
enabled = true
|
||||
enabled = true,
|
||||
-- chance behemoth biters and spitters will double on death.
|
||||
duplicate_chance = 0.05
|
||||
},
|
||||
-- gradually informs players of features such as chat, toasts, etc.
|
||||
player_onboarding = {
|
||||
|
@ -151,6 +151,8 @@ if config.redmew_settings.enabled then
|
||||
require 'features.gui.redmew_settings'
|
||||
end
|
||||
|
||||
require 'features.snake.control'
|
||||
|
||||
-- Debug-only modules
|
||||
if _DEBUG then
|
||||
require 'features.scenario_data_manipulation'
|
||||
|
@ -5,15 +5,13 @@ local Global = require 'utils.global'
|
||||
local Command = require 'utils.command'
|
||||
local Toast = require 'features.gui.toast'
|
||||
local RS = require 'map_gen.shared.redmew_surface'
|
||||
local HailHydra = require 'map_gen.shared.hail_hydra'
|
||||
local Color = require 'resources.color_presets'
|
||||
local Ranks = require 'resources.ranks'
|
||||
local Event = require 'utils.event'
|
||||
local random = math.random
|
||||
local Config = require 'config'
|
||||
|
||||
-- Constants
|
||||
local hail_hydra_data = {
|
||||
['behemoth-spitter'] = {['behemoth-spitter'] = 0.01},
|
||||
['behemoth-biter'] = {['behemoth-biter'] = 0.01}
|
||||
}
|
||||
local duplicate_chance = Config.apocalypse.duplicate_chance
|
||||
|
||||
-- Local var
|
||||
local Public = {}
|
||||
@ -35,6 +33,54 @@ Global.register(
|
||||
end
|
||||
)
|
||||
|
||||
local name_map = {['behemoth-biter'] = true, ['behemoth-spitter'] = true}
|
||||
|
||||
local biter_died_token =
|
||||
Token.register(
|
||||
function(event)
|
||||
local entity = event.entity
|
||||
if not entity.valid then
|
||||
return
|
||||
end
|
||||
|
||||
local name = entity.name
|
||||
if not name_map[name] then
|
||||
return
|
||||
end
|
||||
|
||||
local force_name = entity.force.name
|
||||
if force_name ~= 'enemy' then
|
||||
return
|
||||
end
|
||||
|
||||
local surface = entity.surface
|
||||
local create_entity = surface.create_entity
|
||||
local position = entity.position
|
||||
local spawn = {name = entity.name, force = 'enemy', position = position}
|
||||
|
||||
create_entity(spawn)
|
||||
|
||||
if random() > duplicate_chance then
|
||||
return
|
||||
end
|
||||
|
||||
local spawn_position = surface.find_non_colliding_position(name, position, 8, 1)
|
||||
if not spawn_position then
|
||||
return
|
||||
end
|
||||
|
||||
spawn.position = spawn_position
|
||||
create_entity(spawn)
|
||||
end
|
||||
)
|
||||
|
||||
local aliens = {
|
||||
'behemoth-biter',
|
||||
'behemoth-biter',
|
||||
'behemoth-spitter',
|
||||
'behemoth-spitter'
|
||||
}
|
||||
|
||||
local biter_spawn_token =
|
||||
Token.register(
|
||||
function()
|
||||
@ -45,9 +91,6 @@ local biter_spawn_token =
|
||||
surface = RS.get_surface()
|
||||
player_force = game.forces.player
|
||||
|
||||
HailHydra.set_hydras(hail_hydra_data)
|
||||
HailHydra.set_evolution_scale(1)
|
||||
HailHydra.enable_hail_hydra()
|
||||
enemy_force.evolution_factor = 1
|
||||
|
||||
local p_spawn = player_force.get_spawn_position(surface)
|
||||
@ -55,13 +98,6 @@ local biter_spawn_token =
|
||||
|
||||
local create_entity = surface.create_entity
|
||||
|
||||
local aliens = {
|
||||
'behemoth-biter',
|
||||
'behemoth-biter',
|
||||
'behemoth-spitter',
|
||||
'behemoth-spitter'
|
||||
}
|
||||
|
||||
for i = 1, #aliens do
|
||||
local spawn_pos = surface.find_non_colliding_position('behemoth-biter', p_spawn, 300, 1)
|
||||
if spawn_pos then
|
||||
@ -72,6 +108,8 @@ local biter_spawn_token =
|
||||
|
||||
group.set_command({type = defines.command.attack_area, destination = {0, 0}, radius = 500})
|
||||
Toast.toast_all_players(500, {'apocalypse.toast_message'})
|
||||
|
||||
Event.add_removable(defines.events.on_entity_died, biter_died_token)
|
||||
end
|
||||
)
|
||||
|
||||
@ -97,7 +135,7 @@ function Public.begin_apocalypse(_, player)
|
||||
|
||||
primitives.apocalypse_now = true
|
||||
game.print({'apocalypse.apocalypse_begins'}, Color.pink)
|
||||
Task.set_timeout(15, biter_spawn_token, {})
|
||||
Task.set_timeout(1, biter_spawn_token, {})
|
||||
end
|
||||
|
||||
Command.add(
|
||||
|
28
features/snake/control.lua
Normal file
28
features/snake/control.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local GameGui = require 'features.snake.gui'
|
||||
local Game = require 'features.snake.game'
|
||||
|
||||
local Public = {}
|
||||
|
||||
--- Starts snake game.
|
||||
-- Note when players join the game they will lose thier character.
|
||||
-- @param surface <LuaSurface> Surface that the board is placed on.
|
||||
-- @param top_left_position <Position> Position where board is placed. Defaults to {x = 1, y = 1}.
|
||||
-- @param size <int> size of board in board tiles. Note that the actual size of the board will be (2 * size) + 1 in
|
||||
-- factorio tiles. Defaults to 15.
|
||||
-- @param update_rate <int> number of ticks between updates. Defaults to 30.
|
||||
-- @param <int> maximun food on the board. Defaults to 6.
|
||||
function Public.start_game(surface, top_left_position, size, update_rate, max_food)
|
||||
Game.start_game(surface, top_left_position, size, update_rate, max_food)
|
||||
GameGui.show()
|
||||
end
|
||||
|
||||
--- Ends the snake game. This will clean up any snake and food entities but will not restore the tiles nor
|
||||
-- give players thier character back.
|
||||
function Public.end_game()
|
||||
Game.end_game()
|
||||
GameGui.destroy()
|
||||
end
|
||||
|
||||
remote.add_interface('snake', Public)
|
||||
|
||||
return Public
|
411
features/snake/game.lua
Normal file
411
features/snake/game.lua
Normal file
@ -0,0 +1,411 @@
|
||||
local Global = require 'utils.global'
|
||||
local Event = require 'utils.event'
|
||||
local Token = require 'utils.token'
|
||||
local Queue = require 'utils.queue'
|
||||
|
||||
local random = math.random
|
||||
local queue_new = Queue.new
|
||||
local push = Queue.push
|
||||
local push_to_end = Queue.push_to_end
|
||||
local pop = Queue.pop
|
||||
local peek = Queue.peek
|
||||
local peek_start = Queue.peek_start
|
||||
local peek_index = Queue.peek_index
|
||||
local queue_size = Queue.size
|
||||
local queue_pairs = Queue.pairs
|
||||
local pairs = pairs
|
||||
|
||||
local snakes = {} -- player_index -> snake_data {is_marked_for_destroy:bool, queue :Queue of {entity, cord} }
|
||||
local board = {
|
||||
size = 0,
|
||||
surface = nil,
|
||||
position = nil,
|
||||
food_count = 0,
|
||||
is_running = false,
|
||||
update_rate = 30,
|
||||
max_food = 6
|
||||
}
|
||||
local cords_map = {} -- cords -> positions
|
||||
|
||||
Global.register(
|
||||
{snakes = snakes, board = board, cords_map = cords_map},
|
||||
function(tbl)
|
||||
snakes = tbl.snakes
|
||||
board = tbl.board
|
||||
cords_map = tbl.cords_map
|
||||
end
|
||||
)
|
||||
|
||||
local vectors = {
|
||||
[0] = {x = 0, y = -1},
|
||||
[1] = {x = 0, y = -1},
|
||||
[2] = {x = 1, y = 0},
|
||||
[3] = {x = 1, y = 0},
|
||||
[4] = {x = 0, y = 1},
|
||||
[5] = {x = 0, y = 1},
|
||||
[6] = {x = -1, y = 0},
|
||||
[7] = {x = -1, y = 0}
|
||||
}
|
||||
|
||||
local function destroy_snake(index, snake)
|
||||
for _, element in queue_pairs(snake.queue) do
|
||||
local e = element.entity
|
||||
if e and e.valid then
|
||||
e.destroy()
|
||||
end
|
||||
end
|
||||
snakes[index] = nil
|
||||
|
||||
local player = game.get_player(index)
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
game.print({'snake.snake_destroyed', player.name, queue_size(snake.queue)})
|
||||
end
|
||||
|
||||
local function destroy_dead_snakes()
|
||||
for index, snake in pairs(snakes) do
|
||||
if snake.is_marked_for_destroy then
|
||||
destroy_snake(index, snake)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function spawn_food()
|
||||
local size = board.size
|
||||
local center = math.ceil(size / 2)
|
||||
local surface = board.surface
|
||||
local find_entity = surface.find_entity
|
||||
|
||||
local food_count = board.food_count
|
||||
local max_food = board.max_food
|
||||
local tries = max_food - food_count + 10
|
||||
|
||||
while food_count < max_food and tries > 0 do
|
||||
while tries > 0 do
|
||||
tries = tries - 1
|
||||
local x, y = random(size), random(size)
|
||||
|
||||
if x == center and y == center then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local pos = cords_map[x][y]
|
||||
|
||||
local entity = find_entity('character', pos) or find_entity('compilatron', pos)
|
||||
if entity then
|
||||
goto continue
|
||||
end
|
||||
|
||||
entity =
|
||||
surface.create_entity({name = 'compilatron', position = pos, force = 'neutral', direction = random(7)})
|
||||
entity.active = false
|
||||
food_count = food_count + 1
|
||||
|
||||
break
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
board.food_count = food_count
|
||||
end
|
||||
|
||||
local function destroy_food()
|
||||
local position = board.position
|
||||
local size = board.size
|
||||
local food =
|
||||
board.surface.find_entities_filtered(
|
||||
{
|
||||
name = 'compilatron',
|
||||
area = {left_top = position, right_bottom = {position.x + size * 2, position.y + size * 2}}
|
||||
}
|
||||
)
|
||||
|
||||
for i = 1, #food do
|
||||
local e = food[i]
|
||||
if e.valid then
|
||||
e.destroy()
|
||||
end
|
||||
end
|
||||
|
||||
board.food_count = 0
|
||||
end
|
||||
|
||||
local function get_new_head_cord(head_cord, direction)
|
||||
local vector = vectors[direction]
|
||||
local vec_x, vec_y = vector.x, vector.y
|
||||
local x, y = head_cord.x + vec_x, head_cord.y + vec_y
|
||||
|
||||
return x, y
|
||||
end
|
||||
|
||||
local function tick_snake(index, snake)
|
||||
local player = game.get_player(index)
|
||||
|
||||
if not player or not player.valid then
|
||||
snake.is_marked_for_destroy = true
|
||||
return
|
||||
end
|
||||
|
||||
local character = player.character
|
||||
if not character or not character.valid then
|
||||
snake.is_marked_for_destroy = true
|
||||
return
|
||||
end
|
||||
|
||||
local surface = board.surface
|
||||
local find_entity = surface.find_entity
|
||||
local snake_queue = snake.queue
|
||||
local snake_size = queue_size(snake_queue)
|
||||
local head = peek_start(snake_queue)
|
||||
local tail = peek(snake_queue)
|
||||
local head_cord = head.cord
|
||||
local tail_entity = tail.entity
|
||||
local tail_cord = tail.cord
|
||||
local size = board.size
|
||||
|
||||
local walking_state = character.walking_state
|
||||
walking_state.walking = true
|
||||
local direction = walking_state.direction
|
||||
local x, y = get_new_head_cord(head_cord, direction)
|
||||
|
||||
if x <= 0 or x > size or y <= 0 or y > size then
|
||||
snake.is_marked_for_destroy = true
|
||||
tail_entity.destroy()
|
||||
return
|
||||
end
|
||||
|
||||
local new_head_position = cords_map[x][y]
|
||||
|
||||
if snake_size > 1 and find_entity('character', new_head_position) == peek_index(snake_queue, snake_size - 1).entity then
|
||||
direction = (direction + 4) % 8
|
||||
walking_state.direction = direction
|
||||
x, y = get_new_head_cord(head_cord, direction)
|
||||
end
|
||||
|
||||
if x <= 0 or x > size or y <= 0 or y > size then
|
||||
snake.is_marked_for_destroy = true
|
||||
tail_entity.destroy()
|
||||
return
|
||||
end
|
||||
|
||||
new_head_position = cords_map[x][y]
|
||||
|
||||
tail_entity.teleport(new_head_position)
|
||||
tail.cord = {x = x, y = y}
|
||||
|
||||
pop(snake_queue)
|
||||
push(snake_queue, tail)
|
||||
|
||||
player.character = nil
|
||||
player.character = tail_entity
|
||||
tail_entity.walking_state = walking_state
|
||||
head.entity.active = false
|
||||
tail_entity.active = true
|
||||
|
||||
local entity = find_entity('compilatron', new_head_position)
|
||||
if entity and entity.valid then
|
||||
entity.destroy()
|
||||
|
||||
entity =
|
||||
surface.create_entity {name = 'character', position = cords_map[tail_cord.x][tail_cord.y], force = 'player'}
|
||||
entity.character_running_speed_modifier = -1
|
||||
entity.color = player.color
|
||||
entity.active = false
|
||||
push_to_end(snake_queue, {entity = entity, cord = tail_cord})
|
||||
|
||||
board.food_count = board.food_count - 1
|
||||
end
|
||||
end
|
||||
|
||||
local function tick_snakes()
|
||||
for index, snake in pairs(snakes) do
|
||||
tick_snake(index, snake)
|
||||
end
|
||||
end
|
||||
|
||||
local function check_snakes_for_collisions()
|
||||
local count_entities_filtered = board.surface.count_entities_filtered
|
||||
for index, snake in pairs(snakes) do
|
||||
if snake.is_marked_for_destroy then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if count_entities_filtered({name = 'character', position = peek_start(snake.queue).entity.position}) > 1 then
|
||||
snake.is_marked_for_destroy = true
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
local tick =
|
||||
Token.register(
|
||||
function()
|
||||
tick_snakes()
|
||||
check_snakes_for_collisions()
|
||||
destroy_dead_snakes()
|
||||
spawn_food()
|
||||
end
|
||||
)
|
||||
|
||||
local function make_board()
|
||||
local size = board.size
|
||||
local position = board.position
|
||||
local surface = board.surface
|
||||
|
||||
local pos_x, pos_y = position.x, position.y
|
||||
|
||||
for x = 1, size do
|
||||
local col = {}
|
||||
cords_map[x] = col
|
||||
for y = 1, size do
|
||||
col[y] = {pos_x + 2 * x - 0.5, pos_y + 2 * y - 0.5}
|
||||
end
|
||||
end
|
||||
|
||||
size = size * 2
|
||||
local tiles = {}
|
||||
|
||||
for x = 0, size do
|
||||
for y = 0, size do
|
||||
local pos = {pos_x + x, pos_y + y}
|
||||
local tile_name
|
||||
|
||||
if x == 0 or x == size or y == 0 or y == size then
|
||||
tile_name = 'deepwater'
|
||||
elseif x % 2 == 1 and y % 2 == 1 then
|
||||
tile_name = 'grass-1'
|
||||
else
|
||||
tile_name = 'water'
|
||||
end
|
||||
|
||||
tiles[#tiles + 1] = {position = pos, name = tile_name}
|
||||
end
|
||||
end
|
||||
|
||||
surface.set_tiles(tiles)
|
||||
end
|
||||
|
||||
local function find_new_snake_position()
|
||||
local size = board.size
|
||||
local find_entity = board.surface.find_entity
|
||||
|
||||
local min = math.min(4, size)
|
||||
local max = math.max(1, size - 4)
|
||||
|
||||
if min > max then
|
||||
min, max = max, min
|
||||
elseif min == max then
|
||||
min = 1
|
||||
max = size
|
||||
end
|
||||
|
||||
local tries = 10
|
||||
|
||||
while tries > 0 do
|
||||
tries = tries - 1
|
||||
|
||||
local x, y = random(min, max), random(min, max)
|
||||
local pos = cords_map[x][y]
|
||||
|
||||
local entity = find_entity('character', pos) or find_entity('compilatron', pos)
|
||||
if not entity then
|
||||
return {x = x, y = y}, pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function new_snake(player)
|
||||
if not board.is_running then
|
||||
return
|
||||
end
|
||||
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
if snakes[player.index] then
|
||||
return
|
||||
end
|
||||
|
||||
local character = player.character
|
||||
if character and character.valid then
|
||||
character.destroy()
|
||||
end
|
||||
|
||||
local cord, pos = find_new_snake_position()
|
||||
|
||||
if not cord then
|
||||
player.print({'snake.spawn_snake_fail'})
|
||||
return
|
||||
end
|
||||
|
||||
player.teleport(pos, board.surface)
|
||||
player.create_character()
|
||||
character = player.character
|
||||
character.character_running_speed_modifier = -1
|
||||
|
||||
local queue = queue_new()
|
||||
push(queue, {entity = character, cord = cord})
|
||||
local snake = {queue = queue}
|
||||
|
||||
snakes[player.index] = snake
|
||||
end
|
||||
|
||||
local function new_game(surface, position, size, update_rate, max_food)
|
||||
board.size = size or 15
|
||||
board.surface = surface
|
||||
position = position or {x = 1, y = 1}
|
||||
position.x = position.x or position[1]
|
||||
position.y = position.y or position[2]
|
||||
board.position = position
|
||||
board.update_rate = update_rate or 30
|
||||
board.max_food = max_food or 6
|
||||
|
||||
make_board()
|
||||
destroy_food()
|
||||
spawn_food()
|
||||
|
||||
board.is_running = true
|
||||
|
||||
Event.add_removable_nth_tick(board.update_rate, tick)
|
||||
end
|
||||
|
||||
local Public = {}
|
||||
|
||||
function Public.start_game(surface, top_left_position, size, update_rate, max_food)
|
||||
if board.is_running then
|
||||
error('Snake game is already running you must end the game first.', 2)
|
||||
end
|
||||
|
||||
if not surface then
|
||||
error('Surface must be set.', 2)
|
||||
end
|
||||
|
||||
new_game(surface, top_left_position, size, update_rate or board.update_rate, max_food or board.max_food)
|
||||
end
|
||||
|
||||
function Public.end_game()
|
||||
for index, snake in pairs(snakes) do
|
||||
destroy_snake(index, snake)
|
||||
end
|
||||
|
||||
destroy_food()
|
||||
|
||||
Event.remove_removable_nth_tick(board.update_rate, tick)
|
||||
|
||||
board.is_running = false
|
||||
end
|
||||
|
||||
function Public.new_snake(player)
|
||||
new_snake(player)
|
||||
end
|
||||
|
||||
function Public.is_running()
|
||||
return board.is_running
|
||||
end
|
||||
|
||||
return Public
|
51
features/snake/gui.lua
Normal file
51
features/snake/gui.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local Game = require 'features.snake.game'
|
||||
local Gui = require 'utils.gui'
|
||||
local Event = require 'utils.event'
|
||||
|
||||
local Public = {}
|
||||
|
||||
local main_button_name = Gui.uid_name()
|
||||
|
||||
local function show_gui_for_player(player)
|
||||
if not player or not player.valid then
|
||||
return
|
||||
end
|
||||
|
||||
local top = player.gui.top
|
||||
if not top[main_button_name] then
|
||||
top.add {type = 'button', name = main_button_name, caption = {'snake.name'}}
|
||||
end
|
||||
end
|
||||
|
||||
local function player_created(event)
|
||||
if Game.is_running() then
|
||||
local player = game.get_player(event.player_index)
|
||||
show_gui_for_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
Event.add(defines.events.on_player_created, player_created)
|
||||
|
||||
function Public.show()
|
||||
for _, player in pairs(game.players) do
|
||||
show_gui_for_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
function Public.destroy()
|
||||
for _, player in pairs(game.players) do
|
||||
local button = player.gui.top[main_button_name]
|
||||
if button and button.valid then
|
||||
button.destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gui.on_click(
|
||||
main_button_name,
|
||||
function(event)
|
||||
Game.new_snake(event.player)
|
||||
end
|
||||
)
|
||||
|
||||
return Public
|
@ -161,3 +161,8 @@ ammo_count=Autofill ammo count
|
||||
invalid_ammo_count=ammo count must be a positive integer
|
||||
main_button_tooltip=Autofill settings
|
||||
frame_name=Autofill
|
||||
|
||||
[snake]
|
||||
name=Snake
|
||||
spawn_snake_fail=Unable to spawn snake, please try again.
|
||||
snake_destroyed=__1__ has been destroyed with a score of __2__.
|
||||
|
@ -1,20 +1,46 @@
|
||||
local Debug = require 'utils.debug'
|
||||
local is_closure = Debug.is_closure
|
||||
local floor = math.floor
|
||||
|
||||
local PriorityQueue = {}
|
||||
|
||||
function PriorityQueue.new()
|
||||
return {}
|
||||
end
|
||||
|
||||
local function default_comp(a, b)
|
||||
local function default_comparator(a, b)
|
||||
return a < b
|
||||
end
|
||||
|
||||
local function HeapifyFromEndToStart(queue, comp)
|
||||
comp = comp or default_comp
|
||||
local pos = #queue
|
||||
--- Min heap implementation of a priority queue. Smaller elements, as determined by the comparator,
|
||||
-- have a higher priority.
|
||||
-- @param comparator <function|nil> the comparator function used to compare elements, if nil the
|
||||
-- deafult comparator is used.
|
||||
-- @usage
|
||||
-- local PriorityQueue = require 'utils.priority_queue'
|
||||
--
|
||||
-- local queue = PriorityQueue.new()
|
||||
-- PriorityQueue.push(queue, 4)
|
||||
-- PriorityQueue.push(queue, 7)
|
||||
-- PriorityQueue.push(queue, 2)
|
||||
--
|
||||
-- game.print(PriorityQueue.pop(queue)) -- 2
|
||||
-- game.print(PriorityQueue.pop(queue)) -- 4
|
||||
-- game.print(PriorityQueue.pop(queue)) -- 7
|
||||
function PriorityQueue.new(comparator)
|
||||
if comparator == nil then
|
||||
comparator = default_comparator
|
||||
elseif is_closure(comparator) then
|
||||
error('comparator cannot be a closure.', 2)
|
||||
end
|
||||
|
||||
return {_comparator = comparator}
|
||||
end
|
||||
|
||||
local function heapify_from_end_to_start(self)
|
||||
local comparator = self._comparator
|
||||
local pos = #self
|
||||
while pos > 1 do
|
||||
local parent = bit32.rshift(pos, 1) -- integer division by 2
|
||||
if comp(queue[pos], queue[parent]) then
|
||||
queue[pos], queue[parent] = queue[parent], queue[pos]
|
||||
local parent = floor(pos * 0.5)
|
||||
local a, b = self[pos], self[parent]
|
||||
if comparator(a, b) then
|
||||
self[pos], self[parent] = b, a
|
||||
pos = parent
|
||||
else
|
||||
break
|
||||
@ -22,25 +48,26 @@ local function HeapifyFromEndToStart(queue, comp)
|
||||
end
|
||||
end
|
||||
|
||||
local function HeapifyFromStartToEnd(queue, comp)
|
||||
comp = comp or default_comp
|
||||
local function heapify_from_start_to_end(self)
|
||||
local comparator = self._comparator
|
||||
local parent = 1
|
||||
local smallest = 1
|
||||
local count = #self
|
||||
while true do
|
||||
local child = parent * 2
|
||||
if child > #queue then
|
||||
if child > count then
|
||||
break
|
||||
end
|
||||
if comp(queue[child], queue[parent]) then
|
||||
if comparator(self[child], self[parent]) then
|
||||
smallest = child
|
||||
end
|
||||
child = child + 1
|
||||
if child <= #queue and comp(queue[child], queue[smallest]) then
|
||||
if child <= count and comparator(self[child], self[smallest]) then
|
||||
smallest = child
|
||||
end
|
||||
|
||||
if parent ~= smallest then
|
||||
queue[parent], queue[smallest] = queue[smallest], queue[parent]
|
||||
self[parent], self[smallest] = self[smallest], self[parent]
|
||||
parent = smallest
|
||||
else
|
||||
break
|
||||
@ -48,27 +75,33 @@ local function HeapifyFromStartToEnd(queue, comp)
|
||||
end
|
||||
end
|
||||
|
||||
function PriorityQueue.size(queue)
|
||||
return #queue
|
||||
--- Returns the number of the number of elements in the priority queue.
|
||||
function PriorityQueue.size(self)
|
||||
return #self
|
||||
end
|
||||
|
||||
function PriorityQueue.push(queue, element, comp)
|
||||
table.insert(queue, element)
|
||||
HeapifyFromEndToStart(queue, comp)
|
||||
-- Inserts an element into the priority queue.
|
||||
function PriorityQueue.push(self, element)
|
||||
self[#self + 1] = element
|
||||
heapify_from_end_to_start(self)
|
||||
end
|
||||
|
||||
function PriorityQueue.pop(queue, comp)
|
||||
local element = queue[1]
|
||||
-- Removes and returns the highest priority element from the priority queue.
|
||||
-- If the priority queue is empty returns nil.
|
||||
function PriorityQueue.pop(self)
|
||||
local element = self[1]
|
||||
|
||||
queue[1] = queue[#queue]
|
||||
queue[#queue] = nil
|
||||
HeapifyFromStartToEnd(queue, comp)
|
||||
self[1] = self[#self]
|
||||
self[#self] = nil
|
||||
heapify_from_start_to_end(self)
|
||||
|
||||
return element
|
||||
end
|
||||
|
||||
function PriorityQueue.peek(queue)
|
||||
return queue[1]
|
||||
-- Returns, without removing, the highest priority element from the priority queue.
|
||||
-- If the priority queue is empty returns nil.
|
||||
function PriorityQueue.peek(self)
|
||||
return self[1]
|
||||
end
|
||||
|
||||
return PriorityQueue
|
||||
|
@ -1,24 +1,39 @@
|
||||
local Queue = {}
|
||||
|
||||
function Queue.new()
|
||||
local queue = {_head = 0, _tail = 0}
|
||||
local queue = {_head = 1, _tail = 1}
|
||||
return queue
|
||||
end
|
||||
|
||||
function Queue.size(queue)
|
||||
return queue._tail - queue._head
|
||||
return queue._head - queue._tail
|
||||
end
|
||||
|
||||
function Queue.push(queue, element)
|
||||
local index = queue._head
|
||||
queue[index] = element
|
||||
queue._head = index - 1
|
||||
queue._head = index + 1
|
||||
end
|
||||
|
||||
--- Pushes the element such that it would be the next element pop'ed.
|
||||
function Queue.push_to_end(queue, element)
|
||||
local index = queue._tail - 1
|
||||
queue[index] = element
|
||||
queue._tail = index
|
||||
end
|
||||
|
||||
function Queue.peek(queue)
|
||||
return queue[queue._tail]
|
||||
end
|
||||
|
||||
function Queue.peek_start(queue)
|
||||
return queue[queue._head - 1]
|
||||
end
|
||||
|
||||
function Queue.peek_index(queue, index)
|
||||
return queue[queue._tail + index - 1]
|
||||
end
|
||||
|
||||
function Queue.pop(queue)
|
||||
local index = queue._tail
|
||||
|
||||
@ -26,9 +41,37 @@ function Queue.pop(queue)
|
||||
queue[index] = nil
|
||||
|
||||
if element then
|
||||
queue._tail = index - 1
|
||||
queue._tail = index + 1
|
||||
end
|
||||
|
||||
return element
|
||||
end
|
||||
|
||||
function Queue.to_array(queue)
|
||||
local n = 1
|
||||
local res = {}
|
||||
|
||||
for i = queue._tail, queue._head - 1 do
|
||||
res[n] = queue[i]
|
||||
n = n + 1
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
function Queue.pairs(queue)
|
||||
local index = queue._tail
|
||||
return function()
|
||||
local element = queue[index]
|
||||
|
||||
if element then
|
||||
local old = index
|
||||
index = index + 1
|
||||
return old, element
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Queue
|
||||
|
105
utils/task.lua
105
utils/task.lua
@ -9,38 +9,65 @@ local PriorityQueue = require 'utils.priority_queue'
|
||||
local Event = require 'utils.event'
|
||||
local Token = require 'utils.token'
|
||||
local ErrorLogging = require 'utils.error_logging'
|
||||
local Global = require 'utils.global'
|
||||
|
||||
local floor = math.floor
|
||||
local log10 = math.log10
|
||||
local Token_get = Token.get
|
||||
local pcall = pcall
|
||||
local Queue_peek = Queue.peek
|
||||
local Queue_pop = Queue.pop
|
||||
local Queue_push = Queue.push
|
||||
local PriorityQueue_peek = PriorityQueue.peek
|
||||
local PriorityQueue_pop = PriorityQueue.pop
|
||||
local PriorityQueue_push = PriorityQueue.push
|
||||
|
||||
local Task = {}
|
||||
|
||||
global.callbacks = global.callbacks or PriorityQueue.new()
|
||||
global.next_async_callback_time = -1
|
||||
global.task_queue = global.task_queue or Queue.new()
|
||||
global.total_task_weight = 0
|
||||
global.task_queue_speed = 1
|
||||
|
||||
local function comp(a, b)
|
||||
local function comparator(a, b)
|
||||
return a.time < b.time
|
||||
end
|
||||
|
||||
global.tpt = global.task_queue_speed
|
||||
local function get_task_per_tick()
|
||||
if game.tick % 300 == 0 then
|
||||
local size = global.total_task_weight
|
||||
global.tpt = math.floor(math.log10(size + 1)) * global.task_queue_speed
|
||||
if global.tpt < 1 then
|
||||
global.tpt = 1
|
||||
end
|
||||
local callbacks = PriorityQueue.new(comparator)
|
||||
local task_queue = Queue.new()
|
||||
local primitives = {
|
||||
next_async_callback_time = -1,
|
||||
total_task_weight = 0,
|
||||
task_queue_speed = 1,
|
||||
task_per_tick = 1
|
||||
}
|
||||
|
||||
Global.register(
|
||||
{callbacks = callbacks, task_queue = task_queue, primitives = primitives},
|
||||
function(tbl)
|
||||
callbacks = tbl.callbacks
|
||||
task_queue = tbl.task_queue
|
||||
primitives = tbl.primitives
|
||||
end
|
||||
return global.tpt
|
||||
)
|
||||
|
||||
local function get_task_per_tick(tick)
|
||||
if tick % 300 == 0 then
|
||||
local size = primitives.total_task_weight
|
||||
local task_per_tick = floor(log10(size + 1)) * primitives.task_queue_speed
|
||||
if task_per_tick < 1 then
|
||||
task_per_tick = 1
|
||||
end
|
||||
|
||||
primitives.task_per_tick = task_per_tick
|
||||
return task_per_tick
|
||||
end
|
||||
return primitives.task_per_tick
|
||||
end
|
||||
|
||||
local function on_tick()
|
||||
local queue = global.task_queue
|
||||
for i = 1, get_task_per_tick() do
|
||||
local task = Queue.peek(queue)
|
||||
local tick = game.tick
|
||||
|
||||
for i = 1, get_task_per_tick(tick) do
|
||||
local task = Queue_peek(task_queue)
|
||||
if task ~= nil then
|
||||
-- result is error if not success else result is a boolean for if the task should stay in the queue.
|
||||
local success, result = pcall(Token.get(task.func_token), task.params)
|
||||
local success, result = pcall(Token_get(task.func_token), task.params)
|
||||
if not success then
|
||||
if _DEBUG then
|
||||
error(result)
|
||||
@ -48,19 +75,18 @@ local function on_tick()
|
||||
log(result)
|
||||
ErrorLogging.generate_error_report(result)
|
||||
end
|
||||
Queue.pop(queue)
|
||||
global.total_task_weight = global.total_task_weight - task.weight
|
||||
Queue_pop(task_queue)
|
||||
primitives.total_task_weight = primitives.total_task_weight - task.weight
|
||||
elseif not result then
|
||||
Queue.pop(queue)
|
||||
global.total_task_weight = global.total_task_weight - task.weight
|
||||
Queue_pop(task_queue)
|
||||
primitives.total_task_weight = primitives.total_task_weight - task.weight
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local callbacks = global.callbacks
|
||||
local callback = PriorityQueue.peek(callbacks)
|
||||
while callback ~= nil and game.tick >= callback.time do
|
||||
local success, result = pcall(Token.get(callback.func_token), callback.params)
|
||||
local callback = PriorityQueue_peek(callbacks)
|
||||
while callback ~= nil and tick >= callback.time do
|
||||
local success, result = pcall(Token_get(callback.func_token), callback.params)
|
||||
if not success then
|
||||
if _DEBUG then
|
||||
error(result)
|
||||
@ -69,8 +95,8 @@ local function on_tick()
|
||||
ErrorLogging.generate_error_report(result)
|
||||
end
|
||||
end
|
||||
PriorityQueue.pop(callbacks, comp)
|
||||
callback = PriorityQueue.peek(callbacks)
|
||||
PriorityQueue_pop(callbacks)
|
||||
callback = PriorityQueue_peek(callbacks)
|
||||
end
|
||||
end
|
||||
|
||||
@ -85,7 +111,7 @@ function Task.set_timeout_in_ticks(ticks, func_token, params)
|
||||
end
|
||||
local time = game.tick + ticks
|
||||
local callback = {time = time, func_token = func_token, params = params}
|
||||
PriorityQueue.push(global.callbacks, callback, comp)
|
||||
PriorityQueue_push(callbacks, callback)
|
||||
end
|
||||
|
||||
--- Allows you to set a timer (in seconds) after which the tokened function will be run with params given as an argument
|
||||
@ -109,8 +135,21 @@ end
|
||||
-- Ex. if the task is expected to repeat multiple times (ie. the function returns true and loops several ticks)
|
||||
function Task.queue_task(func_token, params, weight)
|
||||
weight = weight or 1
|
||||
global.total_task_weight = global.total_task_weight + weight
|
||||
Queue.push(global.task_queue, {func_token = func_token, params = params, weight = weight})
|
||||
primitives.total_task_weight = primitives.total_task_weight + weight
|
||||
Queue_push(task_queue, {func_token = func_token, params = params, weight = weight})
|
||||
end
|
||||
|
||||
function Task.get_queue_speed()
|
||||
return primitives.task_queue_speed
|
||||
end
|
||||
|
||||
function Task.set_queue_speed(value)
|
||||
value = value or 1
|
||||
if value < 0 then
|
||||
value = 0
|
||||
end
|
||||
|
||||
primitives.task_queue_speed = value
|
||||
end
|
||||
|
||||
Event.add(defines.events.on_tick, on_tick)
|
||||
|
Loading…
Reference in New Issue
Block a user