You've already forked ComfyFactorio
							
							
				mirror of
				https://github.com/ComfyFactory/ComfyFactorio.git
				synced 2025-10-30 23:47:41 +02:00 
			
		
		
		
	server utils
This commit is contained in:
		
							
								
								
									
										46
									
								
								bot.lua
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								bot.lua
									
									
									
									
									
								
							| @@ -1,46 +0,0 @@ | ||||
| local Event = require "utils.event" | ||||
|  | ||||
| Event.add(defines.events.on_player_died, function (event) | ||||
| 	local player = event.player_index | ||||
| 	if game.players[player].name ~= nil then | ||||
| 		print("PLAYER$die," .. player .. "," .. game.players[player].name .. "," .. game.players[player].force.name) | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| Event.add(defines.events.on_player_respawned, function (event) | ||||
| 	local player = event.player_index | ||||
| 	if game.players[player].name ~= nil then | ||||
| 		print("PLAYER$respawn," .. player .. "," .. game.players[player].name .. "," .. game.players[player].force.name) | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| Event.add(defines.events.on_player_joined_game, function (event) | ||||
| 	local player = event.player_index | ||||
| 	if game.players[player].name ~= nil then | ||||
| 		print("PLAYER$join," .. player .. "," .. game.players[player].name .. "," .. game.players[player].force.name) | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| Event.add(defines.events.on_player_left_game, function (event) | ||||
| 	local player = event.player_index | ||||
| 	if game.players[player].name ~= nil then | ||||
| 		print("PLAYER$leave," .. player .. "," .. game.players[player].name .. "," .. game.players[player].force.name) | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| function heartbeat() | ||||
| 	--Do nothing, this is just so managepgm can call something as a heartbeat without any errors occurring | ||||
| end | ||||
|  | ||||
| function playerQuery() | ||||
| 	if #game.connected_players == 0 then | ||||
| 		print("output$pquery$none") | ||||
| 	else | ||||
| 		local response = "output&pquery$" | ||||
| 		for _,player in pairs(game.connected_players) do | ||||
| 			local playerdata = player.name .. "-" .. player.force.name | ||||
| 			response = response .. playerdata .. "," | ||||
| 		end | ||||
| 		print(response:sub(1,#str-1)) | ||||
| 	end | ||||
| end | ||||
| @@ -1,6 +1,7 @@ | ||||
| server_commands = require 'utils.server' | ||||
| require "utils.server_commands" | ||||
| require "utils.utils" | ||||
| require "utils.corpse_util" | ||||
| require "bot" | ||||
| require "chatbot" | ||||
| require "commands" | ||||
| require "session_tracker" | ||||
| @@ -39,8 +40,8 @@ require "score" | ||||
| --require "maps.tank_battles" | ||||
| --require "maps.spiral_troopers" | ||||
| --require "maps.fish_defender" | ||||
| require "maps.mountain_fortress" | ||||
| --require "maps.stoneblock" | ||||
| --require "maps.mountain_fortress" | ||||
| require "maps.stoneblock" | ||||
| --require "maps.deep_jungle" | ||||
| --require "maps.crossing" | ||||
| --require "maps.anarchy" | ||||
|   | ||||
| @@ -812,6 +812,21 @@ local function is_game_lost() | ||||
| 			local l = t.add({type = "label", caption = mvp.deaths.name .. " died " .. mvp.deaths.score .. " times"})						 | ||||
| 			l.style.font = "default-bold" | ||||
| 			l.style.font_color = {r=0.33, g=0.66, b=0.9} | ||||
| 			 | ||||
| 			if not global.results_sent then | ||||
| 				local result = {} | ||||
| 				insert(result, 'MVP Defender: \\n') | ||||
| 				insert(result, mvp.killscore.name .. " with a score of " .. mvp.killscore.score .. "\\n" ) | ||||
| 				insert(result, '\\n') | ||||
| 				insert(result, 'MVP Builder: \\n') | ||||
| 				insert(result, mvp.built_entities.name .. " built " .. mvp.built_entities.score .. " things\\n" ) | ||||
| 				insert(result, '\\n') | ||||
| 				insert(result, 'MVP Deaths: \\n') | ||||
| 				insert(result, mvp.deaths.name .. " died " .. mvp.deaths.score .. " times" )		 | ||||
| 				local message = table.concat(result) | ||||
| 				server_commands.to_discord_embed(message) | ||||
| 				global.results_sent = true | ||||
| 			end | ||||
| 		end | ||||
| 		 | ||||
| 		for _, player in pairs(game.connected_players) do | ||||
| @@ -1341,7 +1356,12 @@ local function on_tick() | ||||
| 				if global.game_restart_timer > 0 then game.print("Map will restart in " .. global.game_restart_timer / 60 .. " seconds!", { r=0.22, g=0.88, b=0.22}) end | ||||
| 				if global.game_restart_timer == 0 then | ||||
| 					game.print("Map is restarting!", { r=0.22, g=0.88, b=0.22}) | ||||
| 					game.write_file("commandPipe", ":loadscenario --force", false, 0) | ||||
| 					--game.write_file("commandPipe", ":loadscenario --force", false, 0) | ||||
| 					 | ||||
| 					local message = 'Map is restarting! ' | ||||
| 					server_commands.to_discord_bold(table.concat{'*** ', message, ' ***'}) | ||||
| 					server_commands.start_scenario('Fish_Defender') | ||||
| 					 | ||||
| 				end							 | ||||
| 			end | ||||
| 		end | ||||
|   | ||||
| @@ -3,9 +3,9 @@ | ||||
| local event = require 'utils.event' | ||||
|  | ||||
| local biter_values = { | ||||
| 		["medium-biter"] = {"blood-explosion-big", 25, 1.5}, | ||||
| 		["big-biter"] = {"blood-explosion-huge", 50, 2}, | ||||
| 		["behemoth-biter"] = {"blood-explosion-huge", 75, 2.5} | ||||
| 		["medium-biter"] = {"blood-explosion-big", 20, 1.5}, | ||||
| 		["big-biter"] = {"blood-explosion-huge", 40, 2}, | ||||
| 		["behemoth-biter"] = {"blood-explosion-huge", 60, 2.5} | ||||
| 	} | ||||
|  | ||||
| local function damage_entities_in_radius(surface, position, radius, damage) | ||||
| @@ -16,8 +16,7 @@ local function damage_entities_in_radius(surface, position, radius, damage) | ||||
| 				if entity.name == "player" then | ||||
| 					entity.damage(damage, "enemy") | ||||
| 				else | ||||
| 					entity.health = entity.health - damage | ||||
| 					--entity.surface.create_entity({name = "blood-explosion-big", position = entity.position}) | ||||
| 					entity.health = entity.health - damage					 | ||||
| 					if entity.health <= 0 then entity.die("enemy") end | ||||
| 				end | ||||
| 			end | ||||
| @@ -30,7 +29,12 @@ local function on_entity_died(event) | ||||
| 	if biter_values[event.entity.name] then | ||||
| 		local entity = event.entity | ||||
| 		entity.surface.create_entity({name = biter_values[entity.name][1], position = entity.position}) | ||||
| 		damage_entities_in_radius(entity.surface, entity.position, biter_values[entity.name][3], biter_values[entity.name][2])		 | ||||
| 		damage_entities_in_radius( | ||||
| 			entity.surface, | ||||
| 			entity.position, | ||||
| 			biter_values[entity.name][3], | ||||
| 			math.random(math.ceil(biter_values[entity.name][2] * 0.75), math.ceil(biter_values[entity.name][2] * 1.25)) | ||||
| 		)		 | ||||
| 	end | ||||
| end | ||||
| 	 | ||||
|   | ||||
| @@ -5,7 +5,7 @@ require "maps.modules.biters_double_damage" | ||||
| require "maps.modules.biters_double_hp" | ||||
| require "maps.modules.biters_yield_coins" | ||||
| require "maps.modules.dynamic_landfill" | ||||
| require "maps.modules.dynamic_player_spawn" | ||||
| --require "maps.modules.dynamic_player_spawn" | ||||
| require "maps.modules.explosive_biters" | ||||
| require "maps.modules.rocks_broken_paint_tiles" | ||||
| require "maps.modules.rocks_heal_over_time" | ||||
|   | ||||
| @@ -349,6 +349,32 @@ local function on_marked_for_deconstruction(event) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| local function on_tick(event)	 | ||||
| 	if game.tick % 3600 ~= 1 then return end | ||||
| 	if math_random(1,8) ~= 1 then return end | ||||
| 	 | ||||
| 	local surface = game.surfaces["mountain_fortress"] | ||||
| 	 | ||||
| 	local spawners = surface.find_entities_filtered({force = "enemy", type = "unit-spawner"}) | ||||
| 	if not spawners[1] then return end | ||||
| 	 | ||||
| 	local target = surface.find_nearest_enemy({position = spawners[math_random(1, #spawners)].position, max_distance=1500, force="enemy"}) | ||||
| 	if not target then return end | ||||
| 	 | ||||
| 	surface.set_multi_command({ | ||||
| 		command={ | ||||
| 				type=defines.command.attack_area, | ||||
| 				destination=target.position, | ||||
| 				radius=16, | ||||
| 				distraction=defines.distraction.by_anything | ||||
| 			}, | ||||
| 		unit_count = math_random(6,12), | ||||
| 		force = "enemy", | ||||
| 		unit_search_distance=1024 | ||||
| 	})	 | ||||
| end | ||||
|  | ||||
| event.add(defines.events.on_tick, on_tick) | ||||
| event.add(defines.events.on_chunk_charted, on_chunk_charted) | ||||
| event.add(defines.events.on_entity_damaged, on_entity_damaged) | ||||
| event.add(defines.events.on_marked_for_deconstruction, on_marked_for_deconstruction) | ||||
|   | ||||
							
								
								
									
										125
									
								
								utils/event_core.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								utils/event_core.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| -- This module exists to break the circular dependency between event.lua and global.lua. | ||||
| -- It is not expected that any user code would require this module instead event.lua should be required. | ||||
|  | ||||
| local Public = {} | ||||
|  | ||||
| local init_event_name = -1 | ||||
| local load_event_name = -2 | ||||
|  | ||||
| -- map of event_name to handlers[] | ||||
| local event_handlers = {} | ||||
| -- map of nth_tick to handlers[] | ||||
| local on_nth_tick_event_handlers = {} | ||||
|  | ||||
| local function call_handlers(handlers, event) | ||||
|     if _DEBUG then | ||||
|         for _, handler in ipairs(handlers) do | ||||
|             handler(event) | ||||
|         end | ||||
|     else | ||||
|         for _, handler in ipairs(handlers) do | ||||
|             local success, error = pcall(handler, event) | ||||
|             if not success then | ||||
|                 log(error) | ||||
|             end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| local function on_event(event) | ||||
|     local handlers = event_handlers[event.name] | ||||
|     call_handlers(handlers, event) | ||||
| end | ||||
|  | ||||
| local function on_init() | ||||
|     _LIFECYCLE = 5 -- on_init | ||||
|     local handlers = event_handlers[init_event_name] | ||||
|     call_handlers(handlers) | ||||
|  | ||||
|     event_handlers[init_event_name] = nil | ||||
|     event_handlers[load_event_name] = nil | ||||
|  | ||||
|     _LIFECYCLE = 8 -- Runtime | ||||
| end | ||||
|  | ||||
| local function on_load() | ||||
|     _LIFECYCLE = 6 -- on_load | ||||
|     local handlers = event_handlers[load_event_name] | ||||
|     call_handlers(handlers) | ||||
|  | ||||
|     event_handlers[init_event_name] = nil | ||||
|     event_handlers[load_event_name] = nil | ||||
|  | ||||
|     _LIFECYCLE = 8 -- Runtime | ||||
| end | ||||
|  | ||||
| local function on_nth_tick_event(event) | ||||
|     local handlers = on_nth_tick_event_handlers[event.nth_tick] | ||||
|     call_handlers(handlers, event) | ||||
| end | ||||
|  | ||||
| --- Do not use this function, use Event.add instead as it has safety checks. | ||||
| function Public.add(event_name, handler) | ||||
|     local handlers = event_handlers[event_name] | ||||
|     if not handlers then | ||||
|         event_handlers[event_name] = {handler} | ||||
|         script.on_event(event_name, on_event) | ||||
|     else | ||||
|         table.insert(handlers, handler) | ||||
|         if #handlers == 1 then | ||||
|             script.on_event(event_name, on_event) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Do not use this function, use Event.on_init instead as it has safety checks. | ||||
| function Public.on_init(handler) | ||||
|     local handlers = event_handlers[init_event_name] | ||||
|     if not handlers then | ||||
|         event_handlers[init_event_name] = {handler} | ||||
|         script.on_init(on_init) | ||||
|     else | ||||
|         table.insert(handlers, handler) | ||||
|         if #handlers == 1 then | ||||
|             script.on_init(on_init) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Do not use this function, use Event.on_load instead as it has safety checks. | ||||
| function Public.on_load(handler) | ||||
|     local handlers = event_handlers[load_event_name] | ||||
|     if not handlers then | ||||
|         event_handlers[load_event_name] = {handler} | ||||
|         script.on_load(on_load) | ||||
|     else | ||||
|         table.insert(handlers, handler) | ||||
|         if #handlers == 1 then | ||||
|             script.on_load(on_load) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Do not use this function, use Event.on_nth_tick instead as it has safety checks. | ||||
| function Public.on_nth_tick(tick, handler) | ||||
|     local handlers = on_nth_tick_event_handlers[tick] | ||||
|     if not handlers then | ||||
|         on_nth_tick_event_handlers[tick] = {handler} | ||||
|         script.on_nth_tick(tick, on_nth_tick_event) | ||||
|     else | ||||
|         table.insert(handlers, handler) | ||||
|         if #handlers == 1 then | ||||
|             script.on_nth_tick(tick, on_nth_tick_event) | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| function Public.get_event_handlers() | ||||
|     return event_handlers | ||||
| end | ||||
|  | ||||
| function Public.get_on_nth_tick_event_handlers() | ||||
|     return on_nth_tick_event_handlers | ||||
| end | ||||
|  | ||||
| return Public | ||||
							
								
								
									
										116
									
								
								utils/game.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								utils/game.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| local Global = require 'utils.global' | ||||
| local pairs = pairs | ||||
|  | ||||
| local Game = {} | ||||
|  | ||||
| local bad_name_players = {} | ||||
| Global.register( | ||||
|     bad_name_players, | ||||
|     function(tbl) | ||||
|         bad_name_players = tbl | ||||
|     end | ||||
| ) | ||||
|  | ||||
| --[[ | ||||
|     Due to a bug in the Factorio api the following expression isn't guaranteed to be true. | ||||
|     game.players[player.index] == player | ||||
|     get_player_by_index(index) will always return the correct player. | ||||
|     When looking up players by name or iterating through all players use game.players instead. | ||||
| ]] | ||||
| function Game.get_player_by_index(index) | ||||
|     local p = game.players[index] | ||||
|  | ||||
|     if not p then | ||||
|         return nil | ||||
|     end | ||||
|     if p.index == index then | ||||
|         return p | ||||
|     end | ||||
|  | ||||
|     p = bad_name_players[index] | ||||
|     if p then | ||||
|         if p.valid then | ||||
|             return p | ||||
|         else | ||||
|             return nil | ||||
|         end | ||||
|     end | ||||
|  | ||||
|     for k, v in pairs(game.players) do | ||||
|         if k == index then | ||||
|             bad_name_players[index] = v | ||||
|             return v | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Returns a valid LuaPlayer if given a number, string, or LuaPlayer. Returns nil otherwise. | ||||
| -- obj <number|string|LuaPlayer> | ||||
| function Game.get_player_from_any(obj) | ||||
|     local o_type = type(obj) | ||||
|     local p | ||||
|     if type == 'number' then | ||||
|         p = Game.get_player_by_index(obj) | ||||
|     elseif o_type == 'string' then | ||||
|         p = game.players[obj] | ||||
|     elseif o_type == 'table' and obj.valid and obj.is_player() then | ||||
|         return obj | ||||
|     end | ||||
|  | ||||
|     if p and p.valid then | ||||
|         return p | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Prints to player or console. | ||||
| function Game.player_print(str) | ||||
|     if game.player then | ||||
|         game.player.print(str) | ||||
|     else | ||||
|         print(str) | ||||
|     end | ||||
| end | ||||
|  | ||||
| --[[ | ||||
|     @param Position String to display at | ||||
|     @param text String to display | ||||
|     @param color table in {r = 0~1, g = 0~1, b = 0~1}, defaults to white. | ||||
|     @param surface LuaSurface | ||||
|  | ||||
|     @return the created entity | ||||
| ]] | ||||
| function Game.print_floating_text(surface, position, text, color) | ||||
|     color = color | ||||
|  | ||||
|     return surface.create_entity { | ||||
|         name = 'tutorial-flying-text', | ||||
|         color = color, | ||||
|         text = text, | ||||
|         position = position | ||||
|     } | ||||
| end | ||||
|  | ||||
| --[[ | ||||
|     Creates a floating text entity at the player location with the specified color in {r, g, b} format. | ||||
|     Example: "+10 iron" or "-10 coins" | ||||
|  | ||||
|     @param text String to display | ||||
|     @param color table in {r = 0~1, g = 0~1, b = 0~1}, defaults to white. | ||||
|  | ||||
|     @return the created entity | ||||
| ]] | ||||
| function Game.print_player_floating_text_position(player_index, text, color, x_offset, y_offset) | ||||
|     local player = Game.get_player_by_index(player_index) | ||||
|     if not player or not player.valid then | ||||
|         return | ||||
|     end | ||||
|  | ||||
|     local position = player.position | ||||
|     return Game.print_floating_text(player.surface, {x = position.x + x_offset, y = position.y + y_offset}, text, color) | ||||
| end | ||||
|  | ||||
| function Game.print_player_floating_text(player_index, text, color) | ||||
|     Game.print_player_floating_text_position(player_index, text, color, 0, -1.5) | ||||
| end | ||||
|  | ||||
| return Game | ||||
							
								
								
									
										13
									
								
								utils/print_override.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								utils/print_override.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| local Public = {} | ||||
|  | ||||
| local locale_string = {'', '[PRINT] ', nil} | ||||
| local raw_print = print | ||||
|  | ||||
| function print(str) | ||||
|     locale_string[3] = str | ||||
|     log(locale_string) | ||||
| end | ||||
|  | ||||
| Public.raw_print = raw_print | ||||
|  | ||||
| return Public | ||||
							
								
								
									
										539
									
								
								utils/server.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										539
									
								
								utils/server.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,539 @@ | ||||
| local Token = require 'utils.token' | ||||
| local Global = require 'utils.global' | ||||
| local Event = require 'utils.event' | ||||
| local Game = require 'utils.game' | ||||
| local Timestamp = require 'utils.timestamp' | ||||
| local Print = require('utils.print_override') | ||||
|  | ||||
| local serialize = serpent.serialize | ||||
| local concat = table.concat | ||||
| local remove = table.remove | ||||
| local tostring = tostring | ||||
| local raw_print = Print.raw_print | ||||
|  | ||||
| local serialize_options = {sparse = true, compact = true} | ||||
|  | ||||
| local Public = {} | ||||
|  | ||||
| local server_time = {secs = nil, tick = 0} | ||||
|  | ||||
| Global.register( | ||||
|     server_time, | ||||
|     function(tbl) | ||||
|         server_time = tbl | ||||
|     end | ||||
| ) | ||||
|  | ||||
| local discord_tag = '[DISCORD]' | ||||
| local discord_raw_tag = '[DISCORD-RAW]' | ||||
| local discord_bold_tag = '[DISCORD-BOLD]' | ||||
| local discord_admin_tag = '[DISCORD-ADMIN]' | ||||
| local discord_admin_raw_tag = '[DISCORD-ADMIN-RAW]' | ||||
| local discord_embed_tag = '[DISCORD-EMBED]' | ||||
| local discord_embed_raw_tag = '[DISCORD-EMBED-RAW]' | ||||
| local discord_admin_embed_tag = '[DISCORD-ADMIN-EMBED]' | ||||
| local discord_admin_embed_raw_tag = '[DISCORD-ADMIN-EMBED-RAW]' | ||||
| local start_scenario_tag = '[START-SCENARIO]' | ||||
| local ping_tag = '[PING]' | ||||
| local data_set_tag = '[DATA-SET]' | ||||
| local data_get_tag = '[DATA-GET]' | ||||
| local data_get_all_tag = '[DATA-GET-ALL]' | ||||
| local data_tracked_tag = '[DATA-TRACKED]' | ||||
| local ban_sync_tag = '[BAN-SYNC]' | ||||
| local unbanned_sync_tag = '[UNBANNED-SYNC]' | ||||
| local query_players_tag = '[QUERY-PLAYERS]' | ||||
| local player_join_tag = '[PLAYER-JOIN]' | ||||
| local player_leave_tag = '[PLAYER-LEAVE]' | ||||
|  | ||||
| Public.raw_print = raw_print | ||||
|  | ||||
| local data_set_handlers = {} | ||||
|  | ||||
| --- The event id for the on_server_started event. | ||||
| -- The event is raised whenever the server goes from the starting state to the running state. | ||||
| -- It provides a good opportunity to request data from the web server. | ||||
| -- Note that if the server is stopped then started again, this event will be raised again. | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- local Event = require 'utils.event' | ||||
| -- | ||||
| -- Event.add(Server.events.on_server_started, | ||||
| -- function() | ||||
| --      Server.try_get_all_data('regulars', callback) | ||||
| -- end) | ||||
| Public.events = {on_server_started = script.generate_event_name()} | ||||
|  | ||||
| --- Sends a message to the linked discord channel. The message is sanitized of markdown server side. | ||||
| -- @param  message<string> message to send. | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- Server.to_discord('Hello from scenario script!') | ||||
| function Public.to_discord(message) | ||||
|     raw_print(discord_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a message to the linked discord channel. The message is not sanitized of markdown. | ||||
| -- @param  message<string> message to send. | ||||
| function Public.to_discord_raw(message) | ||||
|     raw_print(discord_raw_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a message to the linked discord channel. The message is sanitized of markdown server side, then made bold. | ||||
| -- @param  message<string> message to send. | ||||
| function Public.to_discord_bold(message) | ||||
|     raw_print(discord_bold_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a message to the linked admin discord channel. The message is sanitized of markdown server side. | ||||
| -- @param  message<string> message to send. | ||||
| function Public.to_admin(message) | ||||
|     raw_print(discord_admin_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a message to the linked admin discord channel. The message is not sanitized of markdown. | ||||
| -- @param  message<string> message to send. | ||||
| function Public.to_admin_raw(message) | ||||
|     raw_print(discord_admin_raw_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a embed message to the linked discord channel. The message is sanitized of markdown server side. | ||||
| -- @param  message<string> the content of the embed. | ||||
| function Public.to_discord_embed(message) | ||||
|     raw_print(discord_embed_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a embed message to the linked discord channel. The message is not sanitized of markdown. | ||||
| -- @param  message<string> the content of the embed. | ||||
| function Public.to_discord_embed_raw(message) | ||||
|     raw_print(discord_embed_raw_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a embed message to the linked admin discord channel. The message is sanitized of markdown server side. | ||||
| -- @param  message<string> the content of the embed. | ||||
| function Public.to_admin_embed(message) | ||||
|     raw_print(discord_admin_embed_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Sends a embed message to the linked admin discord channel. The message is not sanitized of markdown. | ||||
| -- @param  message<string> the content of the embed. | ||||
| function Public.to_admin_embed_raw(message) | ||||
|     raw_print(discord_admin_embed_raw_tag .. message) | ||||
| end | ||||
|  | ||||
| --- Stops and saves the factorio server and starts the named scenario. | ||||
| -- @param  scenario_name<string> The name of the scenario as appears in the scenario table on the panel. | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- Server.start_scenario('my_scenario_name') | ||||
| function Public.start_scenario(scenario_name) | ||||
|     if type(scenario_name) ~= 'string' then | ||||
|         game.print('start_scenario - scenario_name ' .. tostring(scenario_name) .. ' must be a string.') | ||||
|         return | ||||
|     end | ||||
|  | ||||
|     local message = start_scenario_tag .. scenario_name | ||||
|  | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| local default_ping_token = | ||||
|     Token.register( | ||||
|     function(sent_tick) | ||||
|         local now = game.tick | ||||
|         local diff = now - sent_tick | ||||
|  | ||||
|         local message = concat({'Pong in ', diff, ' tick(s) ', 'sent tick: ', sent_tick, ' received tick: ', now}) | ||||
|         game.print(message) | ||||
|     end | ||||
| ) | ||||
|  | ||||
| --- Pings the web server. | ||||
| -- @param  func_token<token> The function that is called when the web server replies. | ||||
| -- The function is passed the tick that the ping was sent. | ||||
| function Public.ping(func_token) | ||||
|     local message = concat({ping_tag, func_token or default_ping_token, ' ', game.tick}) | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| local function double_escape(str) | ||||
|     -- Excessive escaping because the data is serialized twice. | ||||
|     return str:gsub('\\', '\\\\\\\\'):gsub('"', '\\\\\\"'):gsub('\n', '\\\\n') | ||||
| end | ||||
|  | ||||
| --- Sets the web server's persistent data storage. If you pass nil for the value removes the data. | ||||
| -- Data set this will by synced in with other server if they choose to. | ||||
| -- There can only be one key for each data_set. | ||||
| -- @param  data_set<string> | ||||
| -- @param  key<string> | ||||
| -- @param  value<nil|boolean|number|string|table> Any type that is not a function. set to nil to remove the data. | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- Server.set_data('my data set', 'key 1', 123) | ||||
| -- Server.set_data('my data set', 'key 2', 'abc') | ||||
| -- Server.set_data('my data set', 'key 3', {'some', 'data', ['is_set'] = true}) | ||||
| -- | ||||
| -- Server.set_data('my data set', 'key 1', nil) -- this will remove 'key 1' | ||||
| -- Server.set_data('my data set', 'key 2', 'def') -- this will change the value for 'key 2' to 'def' | ||||
| function Public.set_data(data_set, key, value) | ||||
|     if type(data_set) ~= 'string' then | ||||
|         error('data_set must be a string', 2) | ||||
|     end | ||||
|     if type(key) ~= 'string' then | ||||
|         error('key must be a string', 2) | ||||
|     end | ||||
|  | ||||
|     data_set = double_escape(data_set) | ||||
|     key = double_escape(key) | ||||
|  | ||||
|     local message | ||||
|     local vt = type(value) | ||||
|     if vt == 'nil' then | ||||
|         message = concat({data_set_tag, '{data_set:"', data_set, '",key:"', key, '"}'}) | ||||
|     elseif vt == 'string' then | ||||
|         value = double_escape(value) | ||||
|  | ||||
|         message = concat({data_set_tag, '{data_set:"', data_set, '",key:"', key, '",value:"\\"', value, '\\""}'}) | ||||
|     elseif vt == 'number' then | ||||
|         message = concat({data_set_tag, '{data_set:"', data_set, '",key:"', key, '",value:"', value, '"}'}) | ||||
|     elseif vt == 'boolean' then | ||||
|         message = concat({data_set_tag, '{data_set:"', data_set, '",key:"', key, '",value:"', tostring(value), '"}'}) | ||||
|     elseif vt == 'function' then | ||||
|         error('value cannot be a function', 2) | ||||
|     else -- table | ||||
|         value = serialize(value, serialize_options) | ||||
|  | ||||
|         -- Less escaping than the string case as serpent provides one level of escaping. | ||||
|         -- Need to escape single quotes as serpent uses double quotes for strings. | ||||
|         value = value:gsub('\\', '\\\\'):gsub("'", "\\'") | ||||
|  | ||||
|         message = concat({data_set_tag, '{data_set:"', data_set, '",key:"', key, "\",value:'", value, "'}"}) | ||||
|     end | ||||
|  | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| --- Gets data from the web server's persistent data storage. | ||||
| -- The callback is passed a table {data_set: string, key: string, value: any}. | ||||
| -- If the value is nil, it means there is no stored data for that data_set key pair. | ||||
| -- @param  data_set<string> | ||||
| -- @param  key<string> | ||||
| -- @param  callback_token<token> | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- local Token = require 'utils.token' | ||||
| -- | ||||
| -- local callback = | ||||
| --     Token.register( | ||||
| --     function(data) | ||||
| --         local data_set = data.data_set | ||||
| --         local key = data.key | ||||
| --         local value = data.value -- will be nil if no data | ||||
| -- | ||||
| --         game.print(data_set .. ':' .. key .. ':' .. tostring(value)) | ||||
| --     end | ||||
| -- ) | ||||
| -- | ||||
| -- Server.try_get_data('my data set', 'key 1', callback) | ||||
| function Public.try_get_data(data_set, key, callback_token) | ||||
|     if type(data_set) ~= 'string' then | ||||
|         error('data_set must be a string', 2) | ||||
|     end | ||||
|     if type(key) ~= 'string' then | ||||
|         error('key must be a string', 2) | ||||
|     end | ||||
|     if type(callback_token) ~= 'number' then | ||||
|         error('callback_token must be a number', 2) | ||||
|     end | ||||
|  | ||||
|     data_set = double_escape(data_set) | ||||
|     key = double_escape(key) | ||||
|  | ||||
|     local message = concat {data_get_tag, callback_token, ' {', 'data_set:"', data_set, '",key:"', key, '"}'} | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| --- Gets all the data for the data_set from the web server's persistent data storage. | ||||
| -- The callback is passed a table {data_set: string, entries: {dictionary key -> value}}. | ||||
| -- If there is no data stored for the data_set entries will be nil. | ||||
| -- @param  data_set<string> | ||||
| -- @param  callback_token<token> | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- local Token = require 'utils.token' | ||||
| -- | ||||
| -- local callback = | ||||
| --     Token.register( | ||||
| --     function(data) | ||||
| --         local data_set = data.data_set | ||||
| --         local entries = data.entries -- will be nil if no data | ||||
| --         local value2 = entries['key 2'] | ||||
| --         local value3 = entries['key 3'] | ||||
| --     end | ||||
| -- ) | ||||
| -- | ||||
| -- Server.try_get_all_data('my data set', callback) | ||||
| function Public.try_get_all_data(data_set, callback_token) | ||||
|     if type(data_set) ~= 'string' then | ||||
|         error('data_set must be a string', 2) | ||||
|     end | ||||
|     if type(callback_token) ~= 'number' then | ||||
|         error('callback_token must be a number', 2) | ||||
|     end | ||||
|  | ||||
|     data_set = double_escape(data_set) | ||||
|  | ||||
|     local message = concat {data_get_all_tag, callback_token, ' {', 'data_set:"', data_set, '"}'} | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| local function data_set_changed(data) | ||||
|     local handlers = data_set_handlers[data.data_set] | ||||
|     if handlers == nil then | ||||
|         return | ||||
|     end | ||||
|  | ||||
|     if _DEBUG then | ||||
|         for _, handler in ipairs(handlers) do | ||||
|             local success, err = pcall(handler, data) | ||||
|             if not success then | ||||
|                 log(err) | ||||
|                 error(err, 2) | ||||
|             end | ||||
|         end | ||||
|     else | ||||
|         for _, handler in ipairs(handlers) do | ||||
|             local success, err = pcall(handler, data) | ||||
|             if not success then | ||||
|                 log(err) | ||||
|             end | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Register a handler to be called when the data_set changes. | ||||
| -- The handler is passed a table {data_set:string, key:string, value:any} | ||||
| -- If value is nil that means the key was removed. | ||||
| -- The handler may be called even if the value hasn't changed. It's up to the implementer | ||||
| -- to determine if the value has changed, or not care. | ||||
| -- To prevent desyncs the same handlers must be registered for all clients. The easiest way to do this | ||||
| -- is in the control stage, i.e before on_init or on_load would be called. | ||||
| -- @param  data_set<string> | ||||
| -- @param  handler<function> | ||||
| -- @usage | ||||
| -- local Server = require 'utils.server' | ||||
| -- Server.on_data_set_changed( | ||||
| --     'my data set', | ||||
| --     function(data) | ||||
| --         local data_set = data.data_set | ||||
| --         local key = data.key | ||||
| --         local value = data.value -- will be nil if data was removed. | ||||
| --     end | ||||
| -- ) | ||||
| function Public.on_data_set_changed(data_set, handler) | ||||
|     if _LIFECYCLE == _STAGE.runtime then | ||||
|         error('cannot call during runtime', 2) | ||||
|     end | ||||
|     if type(data_set) ~= 'string' then | ||||
|         error('data_set must be a string', 2) | ||||
|     end | ||||
|  | ||||
|     local handlers = data_set_handlers[data_set] | ||||
|     if handlers == nil then | ||||
|         handlers = {handler} | ||||
|         data_set_handlers[data_set] = handlers | ||||
|     else | ||||
|         handlers[#handlers + 1] = handler | ||||
|     end | ||||
| end | ||||
|  | ||||
| --- Called by the web server to notify the client that a data_set has changed. | ||||
| Public.raise_data_set = data_set_changed | ||||
|  | ||||
| --- Called by the web server to determine which data_sets are being tracked. | ||||
| function Public.get_tracked_data_sets() | ||||
|     local message = {data_tracked_tag, '['} | ||||
|  | ||||
|     for k, _ in pairs(data_set_handlers) do | ||||
|         k = double_escape(k) | ||||
|  | ||||
|         local message_length = #message | ||||
|         message[message_length + 1] = '"' | ||||
|         message[message_length + 2] = k | ||||
|         message[message_length + 3] = '"' | ||||
|         message[message_length + 4] = ',' | ||||
|     end | ||||
|  | ||||
|     if message[#message] == ',' then | ||||
|         remove(message) | ||||
|     end | ||||
|  | ||||
|     message[#message + 1] = ']' | ||||
|  | ||||
|     message = concat(message) | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| local function escape(str) | ||||
|     return str:gsub('\\', '\\\\'):gsub('"', '\\"') | ||||
| end | ||||
|  | ||||
| --- If the player exists bans the player. | ||||
| -- Regardless of whether or not the player exists the name is synchronized with other servers | ||||
| -- and stored in the database. | ||||
| -- @param  username<string> | ||||
| -- @param  reason<string?> defaults to empty string. | ||||
| -- @param  admin<string?> admin's name, defaults to '<script>' | ||||
| function Public.ban_sync(username, reason, admin) | ||||
|     if type(username) ~= 'string' then | ||||
|         error('username must be a string', 2) | ||||
|     end | ||||
|  | ||||
|     if reason == nil then | ||||
|         reason = '' | ||||
|     elseif type(reason) ~= 'string' then | ||||
|         error('reason must be a string or nil', 2) | ||||
|     end | ||||
|  | ||||
|     if admin == nil then | ||||
|         admin = '<script>' | ||||
|     elseif type(admin) ~= 'string' then | ||||
|         error('admin must be a string or nil', 2) | ||||
|     end | ||||
|  | ||||
|     -- game.ban_player errors if player not found. | ||||
|     -- However we may still want to use this function to ban player names. | ||||
|     local player = game.players[username] | ||||
|     if player then | ||||
|         game.ban_player(player, reason) | ||||
|     end | ||||
|  | ||||
|     username = escape(username) | ||||
|     reason = escape(reason) | ||||
|     admin = escape(admin) | ||||
|  | ||||
|     local message = concat({ban_sync_tag, '{username:"', username, '",reason:"', reason, '",admin:"', admin, '"}'}) | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| --- If the player exists bans the player else throws error. | ||||
| -- The ban is not synchronized with other servers or stored in the database. | ||||
| -- @param  PlayerSpecification | ||||
| -- @param  reason<string?> defaults to empty string. | ||||
| function Public.ban_non_sync(PlayerSpecification, reason) | ||||
|     game.ban_player(PlayerSpecification, reason) | ||||
| end | ||||
|  | ||||
| --- If the player exists unbans the player. | ||||
| -- Regardless of whether or not the player exists the name is synchronized with other servers | ||||
| -- and removed from the database. | ||||
| -- @param  username<string> | ||||
| -- @param  admin<string?> admin's name, defaults to '<script>'. This name is stored in the logs for who removed the ban. | ||||
| function Public.unban_sync(username, admin) | ||||
|     if type(username) ~= 'string' then | ||||
|         error('username must be a string', 2) | ||||
|     end | ||||
|  | ||||
|     if admin == nil then | ||||
|         admin = '<script>' | ||||
|     elseif type(admin) ~= 'string' then | ||||
|         error('admin must be a string or nil', 2) | ||||
|     end | ||||
|  | ||||
|     -- game.unban_player errors if player not found. | ||||
|     -- However we may still want to use this function to unban player names. | ||||
|     local player = game.players[username] | ||||
|     if player then | ||||
|         game.unban_player(username) | ||||
|     end | ||||
|  | ||||
|     username = escape(username) | ||||
|     admin = escape(admin) | ||||
|  | ||||
|     local message = concat({unbanned_sync_tag, '{username:"', username, '",admin:"', admin, '"}'}) | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| --- If the player exists unbans the player else throws error. | ||||
| -- The ban is not synchronized with other servers or removed from the database. | ||||
| -- @param  PlayerSpecification | ||||
| function Public.unban_non_sync(PlayerSpecification) | ||||
|     game.unban_player(PlayerSpecification) | ||||
| end | ||||
|  | ||||
| --- Called by the web server to set the server time. | ||||
| -- @param  secs<number> unix epoch timestamp | ||||
| function Public.set_time(secs) | ||||
|     server_time.secs = secs | ||||
|     server_time.tick = game.tick | ||||
| end | ||||
|  | ||||
| --- Gets a table {secs:number?, tick:number} with secs being the unix epoch timestamp | ||||
| -- for the server time and ticks the number of game ticks ago it was set. | ||||
| -- @return table | ||||
| function Public.get_time_data_raw() | ||||
|     return server_time | ||||
| end | ||||
|  | ||||
| --- Gets an estimate of the current server time as a unix epoch timestamp. | ||||
| -- If the server time has not been set returns nil. | ||||
| -- The estimate may be slightly off if within the last minute the game has been paused, saving or overwise, | ||||
| -- or the game speed has been changed. | ||||
| -- @return number? | ||||
| function Public.get_current_time() | ||||
|     local secs = server_time.secs | ||||
|     if secs == nil then | ||||
|         return nil | ||||
|     end | ||||
|  | ||||
|     local diff = game.tick - server_time.tick | ||||
|     return math.floor(secs + diff / game.speed / 60) | ||||
| end | ||||
|  | ||||
| --- Called be the web server to re sync which players are online. | ||||
| function Public.query_online_players() | ||||
|     local message = {query_players_tag, '['} | ||||
|  | ||||
|     for _, p in ipairs(game.connected_players) do | ||||
|         message[#message + 1] = '"' | ||||
|         local name = escape(p.name) | ||||
|         message[#message + 1] = name | ||||
|         message[#message + 1] = '",' | ||||
|     end | ||||
|  | ||||
|     if message[#message] == '",' then | ||||
|         message[#message] = '"' | ||||
|     end | ||||
|  | ||||
|     message[#message + 1] = ']' | ||||
|  | ||||
|     message = concat(message) | ||||
|     raw_print(message) | ||||
| end | ||||
|  | ||||
| --- The [JOIN] nad [LEAVE] messages Factorio sends to stdout aren't sent in all cases of | ||||
| --  players joining or leaving. So we send our own [PLAYER-JOIN] and [PLAYER-LEAVE] tags. | ||||
| Event.add( | ||||
|     defines.events.on_player_joined_game, | ||||
|     function(event) | ||||
|         local player = Game.get_player_by_index(event.player_index) | ||||
|         if not player then | ||||
|             return | ||||
|         end | ||||
|  | ||||
|         raw_print(player_join_tag .. player.name) | ||||
|     end | ||||
| ) | ||||
|  | ||||
| Event.add( | ||||
|     defines.events.on_player_left_game, | ||||
|     function(event) | ||||
|         local player = Game.get_player_by_index(event.player_index) | ||||
|         if not player then | ||||
|             return | ||||
|         end | ||||
|  | ||||
|         raw_print(player_leave_tag .. player.name) | ||||
|     end | ||||
| ) | ||||
|  | ||||
| return Public | ||||
							
								
								
									
										28
									
								
								utils/server_commands.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								utils/server_commands.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| local Poll = {send_poll_result_to_discord = function () end} | ||||
| local Token = require 'utils.token' | ||||
| local Server = require 'utils.server' | ||||
|  | ||||
| --- This module is for the web server to call functions and raise events. | ||||
| -- Not intended to be called by scripts. | ||||
| -- Needs to be in the _G table so it can be accessed by the web server. | ||||
| ServerCommands = {} | ||||
|  | ||||
| ServerCommands.get_poll_result = Poll.send_poll_result_to_discord | ||||
|  | ||||
|  | ||||
| function ServerCommands.raise_callback(func_token, data) | ||||
|     local func = Token.get(func_token) | ||||
|     func(data) | ||||
| end | ||||
|  | ||||
| ServerCommands.raise_data_set = Server.raise_data_set | ||||
| ServerCommands.get_tracked_data_sets = Server.get_tracked_data_sets | ||||
|  | ||||
| function ServerCommands.server_started() | ||||
|     script.raise_event(Server.events.on_server_started, {}) | ||||
| end | ||||
|  | ||||
| ServerCommands.set_time = Server.set_time | ||||
| ServerCommands.query_online_players = Server.query_online_players | ||||
|  | ||||
| return ServerCommands | ||||
							
								
								
									
										152
									
								
								utils/timestamp.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								utils/timestamp.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| --- source https://github.com/daurnimator/luatz/blob/master/luatz/timetable.lua | ||||
| -- edited down to just what is needed. | ||||
|  | ||||
| local Public = {} | ||||
|  | ||||
| local floor = math.floor | ||||
| local strformat = string.format | ||||
|  | ||||
| local function borrow(tens, units, base) | ||||
|     local frac = tens % 1 | ||||
|     units = units + frac * base | ||||
|     tens = tens - frac | ||||
|     return tens, units | ||||
| end | ||||
|  | ||||
| local function carry(tens, units, base) | ||||
|     if units >= base then | ||||
|         tens = tens + floor(units / base) | ||||
|         units = units % base | ||||
|     elseif units < 0 then | ||||
|         tens = tens + floor(units / base) | ||||
|         units = (base + units) % base | ||||
|     end | ||||
|     return tens, units | ||||
| end | ||||
|  | ||||
| local function is_leap(y) | ||||
|     if (y % 4) ~= 0 then | ||||
|         return false | ||||
|     elseif (y % 100) ~= 0 then | ||||
|         return true | ||||
|     else | ||||
|         return (y % 400) == 0 | ||||
|     end | ||||
| end | ||||
|  | ||||
| local mon_lengths = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} | ||||
|  | ||||
| -- Number of days in year until start of month; not corrected for leap years | ||||
| local months_to_days_cumulative = {0} | ||||
| for i = 2, 12 do | ||||
|     months_to_days_cumulative[i] = months_to_days_cumulative[i - 1] + mon_lengths[i - 1] | ||||
| end | ||||
|  | ||||
| local function month_length(m, y) | ||||
|     if m == 2 then | ||||
|         return is_leap(y) and 29 or 28 | ||||
|     else | ||||
|         return mon_lengths[m] | ||||
|     end | ||||
| end | ||||
|  | ||||
| local function day_of_year(day, month, year) | ||||
|     local yday = months_to_days_cumulative[month] | ||||
|     if month > 2 and is_leap(year) then | ||||
|         yday = yday + 1 | ||||
|     end | ||||
|     return yday + day | ||||
| end | ||||
|  | ||||
| local function leap_years_since(year) | ||||
|     return floor(year / 4) - floor(year / 100) + floor(year / 400) | ||||
| end | ||||
|  | ||||
| local leap_years_since_1970 = leap_years_since(1970) | ||||
|  | ||||
| local function normalise(year, month, day, hour, min, sec) | ||||
|     -- `month` and `day` start from 1, need -1 and +1 so it works modulo | ||||
|     month, day = month - 1, day - 1 | ||||
|  | ||||
|     -- Convert everything (except seconds) to an integer | ||||
|     -- by propagating fractional components down. | ||||
|     year, month = borrow(year, month, 12) | ||||
|     -- Carry from month to year first, so we get month length correct in next line around leap years | ||||
|     year, month = carry(year, month, 12) | ||||
|     month, day = borrow(month, day, month_length(floor(month + 1), year)) | ||||
|     day, hour = borrow(day, hour, 24) | ||||
|     hour, min = borrow(hour, min, 60) | ||||
|     min, sec = borrow(min, sec, 60) | ||||
|  | ||||
|     -- Propagate out of range values up | ||||
|     -- e.g. if `min` is 70, `hour` increments by 1 and `min` becomes 10 | ||||
|     -- This has to happen for all columns after borrowing, as lower radixes may be pushed out of range | ||||
|     min, sec = carry(min, sec, 60) -- TODO: consider leap seconds? | ||||
|     hour, min = carry(hour, min, 60) | ||||
|     day, hour = carry(day, hour, 24) | ||||
|     -- Ensure `day` is not underflowed | ||||
|     -- Add a whole year of days at a time, this is later resolved by adding months | ||||
|     -- TODO[OPTIMIZE]: This could be slow if `day` is far out of range | ||||
|     while day < 0 do | ||||
|         month = month - 1 | ||||
|         if month < 0 then | ||||
|             year = year - 1 | ||||
|             month = 11 | ||||
|         end | ||||
|         day = day + month_length(month + 1, year) | ||||
|     end | ||||
|     year, month = carry(year, month, 12) | ||||
|  | ||||
|     -- TODO[OPTIMIZE]: This could potentially be slow if `day` is very large | ||||
|     while true do | ||||
|         local i = month_length(month + 1, year) | ||||
|         if day < i then | ||||
|             break | ||||
|         end | ||||
|         day = day - i | ||||
|         month = month + 1 | ||||
|         if month >= 12 then | ||||
|             month = 0 | ||||
|             year = year + 1 | ||||
|         end | ||||
|     end | ||||
|  | ||||
|     -- Now we can place `day` and `month` back in their normal ranges | ||||
|     -- e.g. month as 1-12 instead of 0-11 | ||||
|     month, day = month + 1, day + 1 | ||||
|  | ||||
|     return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} | ||||
| end | ||||
|  | ||||
| --- Converts unix epoch timestamp into table {year: number, month: number, day: number, hour: number, min: number, sec: number} | ||||
| -- @param  sec<number> unix epoch timestamp | ||||
| -- @return {year: number, month: number, day: number, hour: number, min: number, sec: number} | ||||
| function Public.to_timetable(secs) | ||||
|     return normalise(1970, 1, 1, 0, 0, secs) | ||||
| end | ||||
|  | ||||
| --- Converts timetable into unix epoch timestamp | ||||
| -- @param  timetable<table> {year: number, month: number, day: number, hour: number, min: number, sec: number} | ||||
| -- @return number | ||||
| function Public.from_timetable(timetable) | ||||
|     local tt = normalise(timetable.year, timetable.month, timetable.day, timetable.hour, timetable.min, timetable.sec) | ||||
|  | ||||
|     local year, month, day, hour, min, sec = tt.year, tt.month, tt.day, tt.hour, tt.min, tt.sec | ||||
|  | ||||
|     local days_since_epoch = | ||||
|         day_of_year(day, month, year) + 365 * (year - 1970) + -- Each leap year adds one day | ||||
|         (leap_years_since(year - 1) - leap_years_since_1970) - | ||||
|         1 | ||||
|  | ||||
|     return days_since_epoch * (60 * 60 * 24) + hour * (60 * 60) + min * 60 + sec | ||||
| end | ||||
|  | ||||
| --- Converts unix epoch timestamp into human readable string. | ||||
| -- @param  secs<type> unix epoch timestamp | ||||
| -- @return string | ||||
| function Public.to_string(secs) | ||||
|     local tt = normalise(1970, 1, 1, 0, 0, secs) | ||||
|     return strformat('%04u-%02u-%02u %02u:%02u:%02d', tt.year, tt.month, tt.day, tt.hour, tt.min, tt.sec) | ||||
| end | ||||
|  | ||||
| return Public | ||||
							
								
								
									
										55
									
								
								utils/token.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								utils/token.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| local Token = {} | ||||
|  | ||||
| local tokens = {} | ||||
|  | ||||
| local counter = 0 | ||||
|  | ||||
| --- Assigns a unquie id for the given var. | ||||
| -- This function cannot be called after on_init() or on_load() has run as that is a desync risk. | ||||
| -- Typically this is used to register functions, so the id can be stored in the global table | ||||
| -- instead of the function. This is becasue closures cannot be safely stored in the global table. | ||||
| -- @param  var<any> | ||||
| -- @return number the unique token for the variable. | ||||
| function Token.register(var) | ||||
|     if _LIFECYCLE == 8 then | ||||
|         error('Calling Token.register after on_init() or on_load() has run is a desync risk.', 2) | ||||
|     end | ||||
|  | ||||
|     counter = counter + 1 | ||||
|  | ||||
|     tokens[counter] = var | ||||
|  | ||||
|     return counter | ||||
| end | ||||
|  | ||||
| function Token.get(token_id) | ||||
|     return tokens[token_id] | ||||
| end | ||||
|  | ||||
| global.tokens = {} | ||||
|  | ||||
| function Token.register_global(var) | ||||
|     local c = #global.tokens + 1 | ||||
|  | ||||
|     global.tokens[c] = var | ||||
|  | ||||
|     return c | ||||
| end | ||||
|  | ||||
| function Token.get_global(token_id) | ||||
|     return global.tokens[token_id] | ||||
| end | ||||
|  | ||||
| function Token.set_global(token_id, var) | ||||
|     global.tokens[token_id] = var | ||||
| end | ||||
|  | ||||
| local uid_counter = 0 | ||||
|  | ||||
| function Token.uid() | ||||
|     uid_counter = uid_counter + 1 | ||||
|  | ||||
|     return uid_counter | ||||
| end | ||||
|  | ||||
| return Token | ||||
		Reference in New Issue
	
	Block a user