From bef4bfea555ac89902c39c7025b7296d9dfc25e1 Mon Sep 17 00:00:00 2001 From: Gerkiz Date: Sat, 24 Oct 2020 14:46:14 +0200 Subject: [PATCH] tweaks and fixes --- chatbot.lua | 17 +- control.lua | 1 + maps/mountain_fortress_v3/balance.lua | 2 +- maps/mountain_fortress_v3/basic_markets.lua | 23 +- maps/mountain_fortress_v3/breached_wall.lua | 33 +- maps/mountain_fortress_v3/difficulty_vote.lua | 366 ++++++++++++++ maps/mountain_fortress_v3/entities.lua | 2 +- maps/mountain_fortress_v3/locomotive.lua | 33 +- maps/mountain_fortress_v3/main.lua | 104 ++-- maps/mountain_fortress_v3/table.lua | 1 + modules/rpg/main.lua | 6 + modules/wave_defense/main.lua | 14 +- modules/wave_defense/table.lua | 7 + utils/datastore/logistics_data.lua | 133 +++++ utils/datastore/quickbar_data.lua | 38 +- utils/datastore/server_ups.lua | 65 ++- utils/debug/public_global_view.lua | 3 +- utils/freeplay.lua | 30 +- utils/inspect.lua | 454 ++++++++++-------- utils/stats.lua | 112 +++++ utils/table.lua | 6 + 21 files changed, 1098 insertions(+), 352 deletions(-) create mode 100644 maps/mountain_fortress_v3/difficulty_vote.lua create mode 100644 utils/datastore/logistics_data.lua create mode 100644 utils/stats.lua diff --git a/chatbot.lua b/chatbot.lua index 55e899a4..47746052 100644 --- a/chatbot.lua +++ b/chatbot.lua @@ -17,7 +17,12 @@ local brain = { 'If you have played for more than 5h in our maps then,', 'you are eligible to run the command /jail and /free' }, - [3] = {'Scenario repository for download:', 'https://github.com/M3wM3w/ComfyFactorio'} + [3] = {'Scenario repository for download:', 'https://github.com/M3wM3w/ComfyFactorio'}, + [4] = { + 'If you feel like the server is lagging, run the following command:', + '/server-ups', + 'This will display the server UPS on your top right screen.' + } } local links = { @@ -36,7 +41,8 @@ local links = { ['stealing'] = brain[2], ['stole'] = brain[2], ['troll'] = brain[2], - ['trolling'] = brain[2] + ['lag'] = brain[4], + ['lagging'] = brain[4] } local function on_player_created(event) @@ -152,9 +158,6 @@ commands.add_command( local function process_bot_answers(event) local player = game.players[event.player_index] - if player.admin == true then - return - end local message = event.message message = string.lower(message) for word in string.gmatch(message, '%g+') do @@ -171,6 +174,10 @@ local function on_console_chat(event) if not event.player_index then return end + local secs = Server.get_current_time() + if not secs then + return + end process_bot_answers(event) end diff --git a/control.lua b/control.lua index f5367a12..1dcc73a5 100644 --- a/control.lua +++ b/control.lua @@ -13,6 +13,7 @@ require 'utils.datastore.color_data' require 'utils.datastore.session_data' require 'utils.datastore.jail_data' require 'utils.datastore.quickbar_data' +require 'utils.datastore.logistics_data' require 'utils.datastore.message_on_join_data' require 'utils.datastore.player_tag_data' require 'chatbot' diff --git a/maps/mountain_fortress_v3/balance.lua b/maps/mountain_fortress_v3/balance.lua index 4a44c860..ddbcf294 100644 --- a/maps/mountain_fortress_v3/balance.lua +++ b/maps/mountain_fortress_v3/balance.lua @@ -1,5 +1,5 @@ local Event = require 'utils.event' -local Difficulty = require 'modules.difficulty_vote' +local Difficulty = require 'maps.mountain_fortress_v3.difficulty_vote' local Public = {} diff --git a/maps/mountain_fortress_v3/basic_markets.lua b/maps/mountain_fortress_v3/basic_markets.lua index fc34cdb2..0efaac19 100644 --- a/maps/mountain_fortress_v3/basic_markets.lua +++ b/maps/mountain_fortress_v3/basic_markets.lua @@ -198,16 +198,18 @@ local function get_resource_market_buys() return buys end -local function get_market_item_list(market_types, rarity) +local function get_market_item_list(rarity) if rarity < 1 then rarity = 1 end if rarity > 10 then rarity = 10 end + local types = get_types() local list = {} - for _, market_type in pairs(market_types) do - for k, item in pairs(market[market_type]) do + for i = 1, 9 do + local branch = market[types[i]] + for k, item in pairs(branch) do --if item.rarity <= rarity and item.rarity + 7 >= rarity then if item.rarity <= rarity then local price = random(floor(item.value * 0.75), floor(item.value * 1.25)) @@ -229,12 +231,7 @@ end function Public.get_random_item(rarity, sell, buy) rarity = rarity or 0 - local types = get_types() - table.shuffle_table(types) - local items - for i = 1, 9 do - items = get_market_item_list({types[i]}, rarity) - end + local items = get_market_item_list(rarity) if not items then return end @@ -244,7 +241,7 @@ function Public.get_random_item(rarity, sell, buy) local items_return = {} - for i = 1, random(5, 10), 1 do + for i = 1, 25, 1 do local item = items[i] if not item then break @@ -256,14 +253,14 @@ function Public.get_random_item(rarity, sell, buy) if sell then local sells = get_resource_market_sells() - for i = 1, random(1, 20), 1 do + for i = 1, random(1, 25), 1 do items_return[#items_return + 1] = sells[i] end end if buy then local buys = get_resource_market_buys() - for i = 1, random(1, 20), 1 do + for i = 1, random(1, 25), 1 do items_return[#items_return + 1] = buys[i] end end @@ -274,7 +271,7 @@ end function Public.mountain_market(surface, position, rarity, buy) local types = get_types() table.shuffle_table(types) - local items = get_market_item_list({types[1], types[2], types[3]}, rarity) + local items = get_market_item_list(rarity) if not items then return end diff --git a/maps/mountain_fortress_v3/breached_wall.lua b/maps/mountain_fortress_v3/breached_wall.lua index 4f933793..39982243 100644 --- a/maps/mountain_fortress_v3/breached_wall.lua +++ b/maps/mountain_fortress_v3/breached_wall.lua @@ -35,6 +35,10 @@ local spidertron_unlocked = end ) +local calculate_hp = function(zone) + return 2 + 0.2 * zone - 1 * floor(zone / 20) +end + local first_player_to_zone = Token.register( function(data) @@ -56,32 +60,6 @@ local artillery_warning = end ) -local compare_collapse_and_train = function() - local collapse_pos = Collapse.get_position() - local locomotive = WPT.get('locomotive') - if not locomotive or not locomotive.valid then - return - end - - local c_y = collapse_pos.y - local t_y = locomotive.position.y - - local gap_between_zones = WPT.get('gap_between_zones') - - if c_y - t_y <= gap_between_zones.gap then - if not gap_between_zones.set then - Collapse.set_speed(8) - Collapse.set_amount(6) - gap_between_zones.set = true - end - return - end - - Collapse.set_speed(6) - Collapse.set_amount(11) - gap_between_zones.set = false -end - local function distance(player) local rpg_t = RPG_Settings.get('rpg_t') local rpg_extra = RPG_Settings.get('rpg_extra') @@ -110,7 +88,7 @@ local function distance(player) WPT.set().placed_trains_in_zone.randomized = false WPT.set().placed_trains_in_zone.positions = {} raise_event(Balance.events.breached_wall, {}) - + --[[ global.biter_health_boost = calculate_hp(breached_wall) ]] if WPT.get('breached_wall') == WPT.get('spidertron_unlocked_at_wave') then local main_market_items = WPT.get('main_market_items') if not main_market_items['spidertron'] then @@ -172,5 +150,4 @@ local function on_player_changed_position(event) distance(player) end -Event.on_nth_tick(100, compare_collapse_and_train) Event.add(defines.events.on_player_changed_position, on_player_changed_position) diff --git a/maps/mountain_fortress_v3/difficulty_vote.lua b/maps/mountain_fortress_v3/difficulty_vote.lua new file mode 100644 index 00000000..d7c76025 --- /dev/null +++ b/maps/mountain_fortress_v3/difficulty_vote.lua @@ -0,0 +1,366 @@ +local Event = require 'utils.event' +local Server = require 'utils.server' +local Global = require 'utils.global' + +local insert = table.insert +local max = math.max + +local this = { + difficulties = { + [1] = { + name = 'Peaceful', + index = 1, + value = 0.25, + color = {r = 0.00, g = 0.45, b = 0.00}, + print_color = {r = 0.00, g = 0.8, b = 0.00}, + count = 0 + }, + [2] = { + name = 'Piece of cake', + index = 2, + value = 0.5, + color = {r = 0.00, g = 0.35, b = 0.00}, + print_color = {r = 0.00, g = 0.6, b = 0.00}, + count = 0 + }, + [3] = { + name = 'Easy', + index = 3, + value = 0.75, + color = {r = 0.00, g = 0.25, b = 0.00}, + print_color = {r = 0.00, g = 0.4, b = 0.00}, + count = 0 + }, + [4] = { + name = 'Normal', + value = 1, + index = 4, + color = {r = 0.00, g = 0.00, b = 0.25}, + print_color = {r = 0.0, g = 0.0, b = 0.5}, + count = 0 + }, + [5] = { + name = 'Hard', + index = 5, + value = 1.5, + color = {r = 0.25, g = 0.00, b = 0.00}, + print_color = {r = 0.4, g = 0.0, b = 0.00}, + count = 0 + }, + [6] = { + name = 'Nightmare', + index = 6, + value = 3, + color = {r = 0.35, g = 0.00, b = 0.00}, + print_color = {r = 0.6, g = 0.0, b = 0.00}, + count = 0 + }, + [7] = { + name = 'Impossible', + index = 7, + value = 5, + color = {r = 0.45, g = 0.00, b = 0.00}, + print_color = {r = 0.8, g = 0.0, b = 0.00}, + count = 0 + } + }, + tooltip = { + [1] = '', + [2] = '', + [3] = '', + [4] = '', + [5] = '', + [6] = '', + [7] = '' + }, + difficulty_vote_value = 1, + difficulty_vote_index = 1, + difficulty_poll_closing_timeout = 54000, + difficulty_player_votes = {}, + gui_width = 108, + name = 'Easy', + button_tooltip = nil +} + +local Public = {} + +Global.register( + this, + function(t) + this = t + end +) + +function Public.set_tooltip(...) + if type(...) == 'table' then + this.tooltip = ... + end +end + +function Public.set_difficulties(...) + if type(...) == 'table' then + this.difficulties = ... + end +end + +function Public.set_poll_closing_timeout(...) + this.difficulty_poll_closing_timeout = ... +end + +function Public.get(key) + if key then + return this[key] + else + return this + end +end + +function Public.difficulty_gui() + local tooltip = 'Current difficulty of the map is ' .. this.difficulties[this.difficulty_vote_index].name .. '.' + + for _, player in pairs(game.connected_players) do + if player.gui.top['difficulty_gui'] then + player.gui.top['difficulty_gui'].caption = this.difficulties[this.difficulty_vote_index].name + player.gui.top['difficulty_gui'].tooltip = this.button_tooltip or tooltip + player.gui.top['difficulty_gui'].style.font_color = + this.difficulties[this.difficulty_vote_index].print_color + else + local b = + player.gui.top.add { + type = 'button', + caption = this.difficulties[this.difficulty_vote_index].name, + tooltip = tooltip, + name = 'difficulty_gui' + } + b.style.font = 'heading-2' + b.style.font_color = this.difficulties[this.difficulty_vote_index].print_color + b.style.minimal_height = 38 + b.style.minimal_width = this.gui_width + end + end +end + +local function highest_count(tbl) + local init = { + count = {}, + index = {} + } + for i = 1, #tbl do + init.count[#init.count + 1] = tbl[i].count + init.index[#init.index + 1] = tbl[i].index + end + + local highest = math.max(unpack(init.count)) + + local pre = {} + for x = 1, #init.count do + if init.count[x] == highest then + pre[#pre + 1] = {i = init.index[x], c = init.count[x]} + end + end + + local post = {} + for i = 1, #pre do + post[#post + 1] = pre[i].i + end + + highest = math.round(table.mean(post)) + + return highest +end + +local function poll_difficulty(player) + if player.gui.center['difficulty_poll'] then + player.gui.center['difficulty_poll'].destroy() + return + end + if game.tick > this.difficulty_poll_closing_timeout then + if player.online_time ~= 0 then + local t = math.abs(math.floor((this.difficulty_poll_closing_timeout - game.tick) / 3600)) + local str = 'Votes have closed ' .. t + str = str .. ' minute' + if t > 1 then + str = str .. 's' + end + str = str .. ' ago.' + player.print(str) + end + return + end + + local frame = + player.gui.center.add { + type = 'frame', + caption = 'Vote difficulty:', + name = 'difficulty_poll', + direction = 'vertical' + } + for i = 1, #this.difficulties, 1 do + local b = frame.add({type = 'button', name = tostring(i), caption = this.difficulties[i].name}) + b.style.font_color = this.difficulties[i].color + b.style.font = 'heading-2' + b.style.minimal_width = 160 + b.tooltip = this.tooltip[i] + --[[ if this.difficulties[i].disabled then + b.enabled = false + end ]] + end + local b = frame.add({type = 'label', caption = '- - - - - - - - - - - - - - - - - -'}) + local b = + frame.add( + { + type = 'button', + name = 'close', + caption = 'Close (' .. + math.floor((this.difficulty_poll_closing_timeout - game.tick) / 3600) .. ' minutes left)' + } + ) + b.style.font_color = {r = 0.66, g = 0.0, b = 0.66} + b.style.font = 'heading-3' + b.style.minimal_width = 96 +end + +local function set_difficulty() + local index = highest_count(this.difficulties) + + if this.difficulty_vote_index ~= index then + local message = + table.concat({'>> Map difficulty has changed to ', this.difficulties[index].name, ' difficulty!'}) + game.print(message, this.difficulties[index].print_color) + Server.to_discord_embed(message) + end + this.difficulty_vote_index = index + this.difficulty_vote_value = this.difficulties[index].value +end + +function Public.reset_difficulty_poll(tbl) + if tbl then + this.difficulty_vote_value = tbl.difficulty_vote_value or 1 + this.difficulty_vote_index = tbl.difficulty_vote_index or 1 + this.difficulty_player_votes = {} + this.difficulty_poll_closing_timeout = tbl.difficulty_poll_closing_timeout or game.tick + 54000 + for _, p in pairs(game.connected_players) do + if p.gui.center['difficulty_poll'] then + p.gui.center['difficulty_poll'].destroy() + end + poll_difficulty(p) + end + for _, vote in pairs(this.difficulties) do + vote.count = 0 + end + Public.difficulty_gui() + else + this.difficulty_vote_value = 1 + this.difficulty_vote_index = 1 + this.difficulty_player_votes = {} + this.difficulty_poll_closing_timeout = game.tick + 54000 + for _, p in pairs(game.connected_players) do + if p.gui.center['difficulty_poll'] then + p.gui.center['difficulty_poll'].destroy() + end + poll_difficulty(p) + end + for _, vote in pairs(this.difficulties) do + vote.count = 0 + end + Public.difficulty_gui() + end +end + +local function on_player_joined_game(event) + local player = game.players[event.player_index] + if game.tick < this.difficulty_poll_closing_timeout then + if not this.difficulty_player_votes[player.name] then + poll_difficulty(player) + end + else + if player.gui.center['difficulty_poll'] then + player.gui.center['difficulty_poll'].destroy() + end + end + Public.difficulty_gui() +end + +local function on_player_left_game(event) + if game.tick > this.difficulty_poll_closing_timeout then + return + end + local player = game.players[event.player_index] + if not this.difficulty_player_votes[player.name] then + return + end + local index = this.difficulty_player_votes[player.name].index + this.difficulties[index].count = this.difficulties[index].count - 1 + if this.difficulties[index].count <= 0 then + this.difficulties[index].count = 0 + end + this.difficulty_player_votes[player.name] = nil + set_difficulty() + Public.difficulty_gui() +end + +local function on_gui_click(event) + if not event then + return + end + if not event.element then + return + end + if not event.element.valid then + return + end + local player = game.players[event.element.player_index] + if event.element.name == 'difficulty_gui' then + poll_difficulty(player) + return + end + if event.element.type ~= 'button' then + return + end + if event.element.parent.name ~= 'difficulty_poll' then + return + end + if event.element.name == 'close' then + event.element.parent.destroy() + return + end + if game.tick > this.difficulty_poll_closing_timeout then + event.element.parent.destroy() + return + end + + local i = tonumber(event.element.name) + + if this.difficulty_player_votes[player.name] and this.difficulty_player_votes[player.name].index == i then + player.print( + 'You have already voted for ' .. this.difficulties[i].name .. '.', + this.difficulties[i].print_color + ) + return + end + + if this.difficulty_player_votes[player.name] then + local index = this.difficulty_player_votes[player.name].index + this.difficulties[index].count = this.difficulties[index].count - 1 + if this.difficulties[index].count <= 0 then + this.difficulties[index].count = 0 + end + end + + this.difficulties[i].count = this.difficulties[i].count + 1 + this.difficulty_player_votes[player.name] = {voted = true, index = i} + + set_difficulty() + Public.difficulty_gui() + event.element.parent.destroy() + game.print( + player.name .. ' has voted for ' .. this.difficulties[i].name .. ' difficulty!', + this.difficulties[i].print_color + ) +end + +Event.add(defines.events.on_player_joined_game, on_player_joined_game) +Event.add(defines.events.on_player_left_game, on_player_left_game) +Event.add(defines.events.on_gui_click, on_gui_click) + +return Public diff --git a/maps/mountain_fortress_v3/entities.lua b/maps/mountain_fortress_v3/entities.lua index 34697482..62eea677 100644 --- a/maps/mountain_fortress_v3/entities.lua +++ b/maps/mountain_fortress_v3/entities.lua @@ -11,7 +11,7 @@ local Functions = require 'modules.rpg.functions' local Mining = require 'maps.mountain_fortress_v3.mining' local Terrain = require 'maps.mountain_fortress_v3.terrain' local BiterHealthBooster = require 'modules.biter_health_booster' -local Difficulty = require 'modules.difficulty_vote' +local Difficulty = require 'maps.mountain_fortress_v3.difficulty_vote' local Traps = require 'maps.mountain_fortress_v3.traps' local Locomotive = require 'maps.mountain_fortress_v3.locomotive' local ExplosiveBullets = require 'maps.mountain_fortress_v3.explosive_gun_bullets' diff --git a/maps/mountain_fortress_v3/locomotive.lua b/maps/mountain_fortress_v3/locomotive.lua index 86973a3a..3fbdd366 100644 --- a/maps/mountain_fortress_v3/locomotive.lua +++ b/maps/mountain_fortress_v3/locomotive.lua @@ -5,7 +5,7 @@ local ICW = require 'maps.mountain_fortress_v3.icw.main' local WPT = require 'maps.mountain_fortress_v3.table' local WD = require 'modules.wave_defense.table' local Session = require 'utils.datastore.session_data' -local Difficulty = require 'modules.difficulty_vote' +local Difficulty = require 'maps.mountain_fortress_v3.difficulty_vote' local Jailed = require 'utils.datastore.jail_data' local RPG_Settings = require 'modules.rpg.table' local Functions = require 'modules.rpg.functions' @@ -83,9 +83,11 @@ end local function add_random_loot_to_main_market(rarity) local main_market_items = WPT.get('main_market_items') local items = Market.get_random_item(rarity, true, false) + if not items then + return false + end local types = game.item_prototypes - local ticker = 0 for k, v in pairs(main_market_items) do if not v.static then @@ -97,7 +99,6 @@ local function add_random_loot_to_main_market(rarity) local price = v.price[1][2] + random(1, 15) * rarity local value = v.price[1][1] local stack = 1 - ticker = ticker + 1 if v.offer.item == 'coin' then price = v.price[1][2] stack = v.offer.count @@ -106,18 +107,14 @@ local function add_random_loot_to_main_market(rarity) end end - if main_market_items[v.offer.item] then - main_market_items[v.offer.item] = nil - end - main_market_items[v.offer.item] = { - stack = stack, - value = value, - price = price, - tooltip = types[v.offer.item].localised_name, - upgrade = false - } - if ticker >= 48 then - break + if not main_market_items[v.offer.item] then + main_market_items[v.offer.item] = { + stack = stack, + value = value, + price = price, + tooltip = types[v.offer.item].localised_name, + upgrade = false + } end end end @@ -976,7 +973,7 @@ local function gui_click(event) local force = game.forces.player - force.manual_mining_speed_modifier = force.manual_mining_speed_modifier + 0.05 + force.manual_mining_speed_modifier = force.manual_mining_speed_modifier + this.pickaxe_speed_per_purchase local force_mining_speed = WPT.get('force_mining_speed') force_mining_speed.speed = force.manual_mining_speed_modifier @@ -1926,8 +1923,8 @@ function Public.get_items(reroll) if reroll then fixed_prices.chest_limit_cost = random(2000, 3000) * (1 + chest_limit_outside_upgrades) fixed_prices.health_cost = random(7000, 10000) * (1 + health_upgrades) - fixed_prices.pickaxe_cost = random(2500, 4000) * (1 + pickaxe_tier) - fixed_prices.reroll_cost = random(4000, 6000) + fixed_prices.pickaxe_cost = random(1500, 2000) + fixed_prices.reroll_cost = random(2000, 3000) fixed_prices.aura_cost = random(3000, 6000) * (1 + aura_upgrades) fixed_prices.xp_point_boost_cost = random(4000, 6000) * (1 + xp_points_upgrade) fixed_prices.explosive_bullets_cost = random(18000, 21000) diff --git a/maps/mountain_fortress_v3/main.lua b/maps/mountain_fortress_v3/main.lua index c35b3748..31f53489 100644 --- a/maps/mountain_fortress_v3/main.lua +++ b/maps/mountain_fortress_v3/main.lua @@ -38,7 +38,7 @@ local Locomotive = require 'maps.mountain_fortress_v3.locomotive' local Score = require 'comfy_panel.score' local Poll = require 'comfy_panel.poll' local Collapse = require 'modules.collapse' -local Difficulty = require 'modules.difficulty_vote' +local Difficulty = require 'maps.mountain_fortress_v3.difficulty_vote' local Task = require 'utils.task' local Token = require 'utils.token' local Alert = require 'utils.alert' @@ -125,7 +125,6 @@ end local set_difficulty = function() local Diff = Difficulty.get() local wave_defense_table = WD.get_table() - local collapse_speed = WPT.get('collapse_speed') local collapse_amount = WPT.get('collapse_amount') local player_count = #game.connected_players if not Diff.difficulty_vote_value then @@ -141,10 +140,13 @@ local set_difficulty = function() -- threat gain / wave wave_defense_table.threat_gain_multiplier = 1.2 + player_count * Diff.difficulty_vote_value * 0.1 - local amount = player_count * 0.25 + 6 + local amount = player_count * 0.1 amount = floor(amount) - if amount > 10 then - amount = 10 + if amount <= 0 then + amount = 1 + end + if amount > 5 then + amount = 5 end local difficulty = Difficulty.get() @@ -152,7 +154,6 @@ local set_difficulty = function() if wave_defense_table.threat <= 0 then wave_defense_table.wave_interval = 1000 - return end if name == 'Insane' then wave_defense_table.wave_interval = 1800 @@ -169,26 +170,14 @@ local set_difficulty = function() end if name == 'Insane' then - Collapse.set_amount(12) + Collapse.set_amount(10) elseif collapse_amount then Collapse.set_amount(collapse_amount) else Collapse.set_amount(amount) end - if name == 'Insane' then - Collapse.set_speed(5) - elseif collapse_speed then - Collapse.set_speed(collapse_speed) - else - if player_count >= 8 and player_count <= 12 then - Collapse.set_speed(8) - elseif player_count >= 20 and player_count <= 24 then - Collapse.set_speed(6) - elseif player_count >= 35 then - Collapse.set_speed(5) - end - end + Collapse.set_speed(1) end local render_direction = function(surface) @@ -303,7 +292,9 @@ function Public.reset_map() Functions.reset_table() game.reset_time_played() WPT.reset_table() + Map_score.reset_score() + RPG_Func.rpg_reset_all_players() RPG_Settings.set_surface_name('mountain_fortress_v3') RPG_Settings.enable_health_and_mana_bars(true) @@ -333,6 +324,7 @@ function Public.reset_map() global.custom_highscore.description = 'Wagon distance reached:' Entities.set_scores() + AntiGrief.log_tree_harvest(true) AntiGrief.whitelist_types('tree', true) AntiGrief.enable_capsule_warning(false) @@ -530,9 +522,6 @@ local on_research_finished = function(event) if research.name == 'steel-axe' then mining_speed_bonus = mining_speed_bonus + 0.5 research.force.manual_mining_speed_modifier = mining_speed_bonus - local msg = - 'Steel-axe technology has been researched, nerfing mining-speed.\nBuy Pickaxe-upgrades in the market!' - Alert.alert_all_players(30, msg, nil, 'achievement/tech-maniac', 0.6) end -- +50% speed for steel-axe research local force_name = research.force.name @@ -736,6 +725,31 @@ local collapse_message = end ) +local compare_collapse_and_train = function() + local collapse_pos = Collapse.get_position() + local locomotive = WPT.get('locomotive') + if not locomotive or not locomotive.valid then + return + end + + local c_y = collapse_pos.y + local t_y = locomotive.position.y + + local gap_between_zones = WPT.get('gap_between_zones') + + if c_y - t_y <= gap_between_zones.gap then + if gap_between_zones.set then + set_difficulty() + gap_between_zones.set = false + end + return + end + + Collapse.set_speed(1) + Collapse.set_amount(4) + gap_between_zones.set = true +end + local collapse_after_wave_100 = function() local collapse_grace = WPT.get('collapse_grace') if not collapse_grace then @@ -779,19 +793,23 @@ local on_tick = function() is_locomotive_valid() has_the_game_ended() chunk_load() + end - if tick % 1200 == 0 then - boost_difficulty() - collapse_after_wave_100() - Entities.set_scores() - set_difficulty() - local spawn_near_collapse = WPT.get('spawn_near_collapse') - if spawn_near_collapse then - local collapse_pos = Collapse.get_position() - local position = surface.find_non_colliding_position('rocket-silo', collapse_pos, 128, 1) - if position then - WD.set_spawn_position(position) - end + if tick % 250 == 0 then + compare_collapse_and_train() + end + + if tick % 1200 == 0 then + boost_difficulty() + collapse_after_wave_100() + Entities.set_scores() + set_difficulty() + local spawn_near_collapse = WPT.get('spawn_near_collapse') + if spawn_near_collapse then + local collapse_pos = Collapse.get_position() + local position = surface.find_non_colliding_position('rocket-silo', collapse_pos, 128, 1) + if position then + WD.set_spawn_position(position) end end end @@ -804,27 +822,35 @@ local on_init = function() local difficulties = { [1] = { name = 'Easy', + index = 1, value = 0.75, color = {r = 0.00, g = 0.25, b = 0.00}, - print_color = {r = 0.00, g = 0.4, b = 0.00} + print_color = {r = 0.00, g = 0.4, b = 0.00}, + count = 0 }, [2] = { name = 'Normal', + index = 2, value = 1, color = {r = 0.00, g = 0.00, b = 0.25}, - print_color = {r = 0.0, g = 0.0, b = 0.5} + print_color = {r = 0.0, g = 0.0, b = 0.5}, + count = 0 }, [3] = { name = 'Hard', + index = 3, value = 1.5, color = {r = 0.25, g = 0.25, b = 0.00}, - print_color = {r = 0.4, g = 0.0, b = 0.00} + print_color = {r = 0.4, g = 0.0, b = 0.00}, + count = 0 }, [4] = { name = 'Insane', + index = 4, value = 3, color = {r = 0.25, g = 0.00, b = 0.00}, - print_color = {r = 0.4, g = 0.0, b = 0.00} + print_color = {r = 0.4, g = 0.0, b = 0.00}, + count = 0 } } diff --git a/maps/mountain_fortress_v3/table.lua b/maps/mountain_fortress_v3/table.lua index 4839381d..1bc25700 100644 --- a/maps/mountain_fortress_v3/table.lua +++ b/maps/mountain_fortress_v3/table.lua @@ -130,6 +130,7 @@ function Public.reset_table() } this.aura_upgrades = 0 this.pickaxe_tier = 1 + this.pickaxe_speed_per_purchase = 0.10 this.health_upgrades = 0 this.breached_wall = 1 this.offline_players_enabled = true diff --git a/modules/rpg/main.lua b/modules/rpg/main.lua index 61f17070..6439b0ef 100644 --- a/modules/rpg/main.lua +++ b/modules/rpg/main.lua @@ -1061,6 +1061,11 @@ local function on_player_used_capsule(event) return end +local function on_player_changed_surface(event) + local player = game.get_player(event.player_index) + RPG_GUI.draw_level_text(player) +end + local function tick() local ticker = game.tick local count = #game.connected_players @@ -1123,4 +1128,5 @@ Event.add(defines.events.on_player_respawned, on_player_respawned) Event.add(defines.events.on_player_rotated_entity, on_player_rotated_entity) Event.add(defines.events.on_pre_player_mined_item, on_pre_player_mined_item) Event.add(defines.events.on_player_used_capsule, on_player_used_capsule) +Event.add(defines.events.on_player_changed_surface, on_player_changed_surface) Event.on_nth_tick(10, tick) diff --git a/modules/wave_defense/main.lua b/modules/wave_defense/main.lua index f67ffba9..665063b6 100644 --- a/modules/wave_defense/main.lua +++ b/modules/wave_defense/main.lua @@ -64,7 +64,7 @@ end local function remove_trees(entity) local surface = entity.surface - local radius = 10 + local radius = 15 local pos = entity.position local area = {{pos.x - radius, pos.y - radius}, {pos.x + radius, pos.y + radius}} local trees = surface.find_entities_filtered {area = area, type = 'tree'} @@ -79,7 +79,7 @@ end local function remove_rocks(entity) local surface = entity.surface - local radius = 10 + local radius = 15 local pos = entity.position local area = {{pos.x - radius, pos.y - radius}, {pos.x + radius, pos.y + radius}} local rocks = surface.find_entities_filtered {area = area, type = 'simple-entity'} @@ -289,10 +289,8 @@ local function set_enemy_evolution() evolution_factor = 1 end - if this.threat > 50000 then - biter_health_boost = math_round(biter_health_boost + (this.threat - 50000) * 0.000033, 3) + biter_health_boost = math_round(biter_health_boost + (this.threat - 5000) * 0.000033, 3) --damage_increase = math_round(damage_increase + this.threat * 0.0000025, 3) - end global.biter_health_boost = biter_health_boost --game.forces.enemy.set_ammo_damage_modifier("melee", damage_increase) @@ -392,8 +390,10 @@ local function set_next_wave() end this.threat = this.threat + math_floor(threat_gain) - this.last_wave = this.next_wave - this.next_wave = game.tick + this.wave_interval + if not this.wave_enforced then + this.last_wave = this.next_wave + this.next_wave = game.tick + this.wave_interval + end if this.clear_corpses then local surface = game.surfaces[this.surface_index] for _, entity in pairs(surface.find_entities_filtered {type = 'corpse'}) do diff --git a/modules/wave_defense/table.lua b/modules/wave_defense/table.lua index 99bc524b..1e0f0f57 100644 --- a/modules/wave_defense/table.lua +++ b/modules/wave_defense/table.lua @@ -11,6 +11,12 @@ Global.register( end ) +function Public.debug_module() + this.next_wave = 1000 + this.wave_interval = 500 + this.wave_enforced = true +end + function Public.reset_wave_defense() this.boss_wave = false this.boss_wave_warning = false @@ -48,6 +54,7 @@ function Public.reset_wave_defense() this.unit_group_command_step_length = 15 this.unit_group_last_command = {} this.wave_interval = 3600 + this.wave_enforced = false this.wave_number = 0 this.worm_building_chance = 3 this.worm_building_density = 16 diff --git a/utils/datastore/logistics_data.lua b/utils/datastore/logistics_data.lua new file mode 100644 index 00000000..6565e4c4 --- /dev/null +++ b/utils/datastore/logistics_data.lua @@ -0,0 +1,133 @@ +local Token = require 'utils.token' +local Color = require 'utils.color_presets' +local Server = require 'utils.server' +local Event = require 'utils.event' + +local logistics_dataset = 'logistics' +local logistics_dataset_modded = 'logistics_modded' +local set_data = Server.set_data +local try_get_data = Server.try_get_data + +local Public = {} + +local function is_game_modded() + local i = 0 + for k, _ in pairs(game.active_mods) do + i = i + 1 + if i > 1 then + return true + end + end + return false +end + +local fetch = + Token.register( + function(data) + local key = data.key + local value = data.value + local player = game.players[key] + if not player or not player.valid then + return + end + if value then + for i, item_name in pairs(value) do + if item_name ~= nil and item_name ~= '' then + player.set_personal_logistic_slot(i, item_name) + end + end + end + end +) + +--- Tries to get data from the webpanel and applies the value to the player. +-- @param data_set player token +function Public.fetch(key) + local secs = Server.get_current_time() + local dataset = logistics_dataset + if not secs then + local player = game.players[key] + if not player or not player.valid then + return + end + return + else + local game_has_mods = is_game_modded() + if game_has_mods then + dataset = logistics_dataset_modded + end + + try_get_data(dataset, key, fetch) + end +end + +commands.add_command( + 'save-logistics', + 'Save your personal logistics preset so it´s always the same.', + function() + local player = game.player + if not player or not player.valid then + return + end + + local secs = Server.get_current_time() + if not secs then + return + end + + local dataset = logistics_dataset + + local game_has_mods = is_game_modded() + if game_has_mods then + dataset = logistics_dataset_modded + end + + local slots = {} + + for i = 1, 100 do + local slot = player.get_personal_logistic_slot(i) + if slot ~= nil then + slots[i] = slot.name + end + end + if next(slots) then + set_data(dataset, player.name, slots) + player.print('Your personal logistics has been saved.', Color.success) + end + end +) + +commands.add_command( + 'remove-logistics', + 'Removes your personal logistics preset from the datastore.', + function() + local player = game.player + if not player or not player.valid then + return + end + + local dataset = logistics_dataset + + local game_has_mods = is_game_modded() + if game_has_mods then + dataset = logistics_dataset_modded + end + + set_data(dataset, player.name, nil) + player.print('Your personal logistics has been removed.', Color.success) + end +) + +Event.add( + defines.events.on_player_joined_game, + function(event) + local player = game.get_player(event.player_index) + if not player or not player.valid then + return + end + + Public.fetch(player.name) + end +) + +return Public diff --git a/utils/datastore/quickbar_data.lua b/utils/datastore/quickbar_data.lua index 2e8dc60a..e3b56150 100644 --- a/utils/datastore/quickbar_data.lua +++ b/utils/datastore/quickbar_data.lua @@ -4,11 +4,23 @@ local Server = require 'utils.server' local Event = require 'utils.event' local quickbar_dataset = 'quickbar' +local quickbar_dataset_modded = 'quickbar_modded' local set_data = Server.set_data local try_get_data = Server.try_get_data local Public = {} +local function is_game_modded() + local i = 0 + for k, _ in pairs(game.active_mods) do + i = i + 1 + if i > 1 then + return true + end + end + return false +end + local fetch = Token.register( function(data) @@ -32,6 +44,7 @@ local fetch = -- @param data_set player token function Public.fetch(key) local secs = Server.get_current_time() + local dataset = quickbar_dataset if not secs then local player = game.players[key] if not player or not player.valid then @@ -39,7 +52,12 @@ function Public.fetch(key) end return else - try_get_data(quickbar_dataset, key, fetch) + local game_has_mods = is_game_modded() + if game_has_mods then + dataset = quickbar_dataset_modded + end + + try_get_data(dataset, key, fetch) end end @@ -57,6 +75,13 @@ commands.add_command( return end + local dataset = quickbar_dataset + + local game_has_mods = is_game_modded() + if game_has_mods then + dataset = quickbar_dataset_modded + end + local slots = {} for i = 1, 100 do @@ -66,7 +91,7 @@ commands.add_command( end end if next(slots) then - set_data(quickbar_dataset, player.name, slots) + set_data(dataset, player.name, slots) player.print('Your quickbar has been saved.', Color.success) end end @@ -81,7 +106,14 @@ commands.add_command( return end - set_data(quickbar_dataset, player.name, nil) + local dataset = quickbar_dataset + + local game_has_mods = is_game_modded() + if game_has_mods then + dataset = quickbar_dataset_modded + end + + set_data(dataset, player.name, nil) player.print('Your quickbar has been removed.', Color.success) end ) diff --git a/utils/datastore/server_ups.lua b/utils/datastore/server_ups.lua index b60df0fc..bfd2ece8 100644 --- a/utils/datastore/server_ups.lua +++ b/utils/datastore/server_ups.lua @@ -6,14 +6,16 @@ local Color = require 'utils.color_presets' local ups_label = GUI.uid_name() local function validate_player(player) - if not player or not player.valid then + if not player then + return false + end + if not player.valid then return false end return true end -local function set_location(event) - local player = game.get_player(event.player_index) +local function set_location(player) local gui = player.gui local label = gui.screen[ups_label] if not label or not label.valid then @@ -24,22 +26,21 @@ local function set_location(event) label.location = {x = res.width - 423 * uis, y = 30 * uis} end -local function create_label(player, label) +local function create_label(player) local ups = Server.get_ups() local sUPS = 'SUPS = ' .. ups - if not label or not label.valid then - label = - player.gui.screen.add( - { - type = 'label', - name = ups_label, - caption = sUPS - } - ) - local style = label.style - style.font = 'default-game' - return label - end + + local label = + player.gui.screen.add( + { + type = 'label', + name = ups_label, + caption = sUPS + } + ) + local style = label.style + style.font = 'default-game' + return label end Event.add( @@ -50,9 +51,9 @@ Event.add( local label = player.gui.screen[ups_label] if not label or not label.valid then - label = create_label(player, label) + label = create_label(player) end - set_location(event) + set_location(player) label.visible = false end ) @@ -64,10 +65,12 @@ Event.on_nth_tick( local ups = Server.get_ups() local caption = 'SUPS = ' .. ups local players = game.connected_players - for _, player in pairs(players) do + for i = 1, #players do + local player = players[i] local label = player.gui.screen[ups_label] if label and label.valid then label.caption = caption + set_location(player) end end end @@ -88,17 +91,33 @@ commands.add_command( local label = player.gui.screen[ups_label] if not label or not label.valid then - label = create_label(player, label) + label = create_label(player) end if label.visible then label.visible = false + player.print('Removed Server-UPS label.', Color.warning) else label.visible = true + set_location(player) + player.print('Added Server-UPS label.', Color.success) end end end ) -Event.add(defines.events.on_player_display_resolution_changed, set_location) -Event.add(defines.events.on_player_display_scale_changed, set_location) +Event.add( + defines.events.on_player_display_resolution_changed, + function(event) + local player = game.get_player(event.player_index) + set_location(player) + end +) + +Event.add( + defines.events.on_player_display_scale_changed, + function(event) + local player = game.get_player(event.player_index) + set_location(player) + end +) diff --git a/utils/debug/public_global_view.lua b/utils/debug/public_global_view.lua index f0f028ca..26c3f7a4 100644 --- a/utils/debug/public_global_view.lua +++ b/utils/debug/public_global_view.lua @@ -89,7 +89,8 @@ Gui.on_click( input_text_box.text = concat {'global.tokens[', token_id, ']'} input_text_box.style.font_color = Color.black - local content = dump(Token.get_global(token_id)) or 'nil' + local id = Token.get_global(token_id) + local content = dump(id) or 'Could not load data.' right_panel.text = content end ) diff --git a/utils/freeplay.lua b/utils/freeplay.lua index b6d37bef..ea981de4 100644 --- a/utils/freeplay.lua +++ b/utils/freeplay.lua @@ -18,6 +18,17 @@ Global.register( end ) +local function is_game_modded() + local i = 0 + for k, _ in pairs(game.active_mods) do + i = i + 1 + if i > 1 then + return true + end + end + return false +end + local util = require('util') local crash_site = require('crash-site') @@ -191,17 +202,14 @@ end Event.on_init( function() local i = 0 - for k, _ in pairs(game.active_mods) do - i = i + 1 - if i > 1 then - this.modded = true - this.disable_crashsite = false - this.created_items = created_items() - this.respawn_items = respawn_items() - this.crashed_ship_items = ship_items() - this.crashed_debris_items = debris_items() - return true - end + local game_has_mods = is_game_modded() + if game_has_mods then + this.modded = true + this.disable_crashsite = false + this.created_items = created_items() + this.respawn_items = respawn_items() + this.crashed_ship_items = ship_items() + this.crashed_debris_items = debris_items() end end ) diff --git a/utils/inspect.lua b/utils/inspect.lua index c13a8cd4..30827a03 100644 --- a/utils/inspect.lua +++ b/utils/inspect.lua @@ -1,8 +1,8 @@ -local inspect ={ - _VERSION = 'inspect.lua 3.1.0', - _URL = 'http://github.com/kikito/inspect.lua', - _DESCRIPTION = 'human-readable representations of tables', - _LICENSE = [[ +local inspect = { + _VERSION = 'inspect.lua 3.1.0', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', + _LICENSE = [[ MIT LICENSE Copyright (c) 2013 Enrique García Cota @@ -30,313 +30,363 @@ local inspect ={ local tostring = tostring -inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end}) -inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end}) +inspect.KEY = + setmetatable( + {}, + {__tostring = function() + return 'inspect.KEY' + end} +) +inspect.METATABLE = + setmetatable( + {}, + {__tostring = function() + return 'inspect.METATABLE' + end} +) -- Apostrophizes the string if it has quotes, but not aphostrophes -- Otherwise, it returns a regular quoted string local function smartQuote(str) - if str:match('"') and not str:match("'") then - return "'" .. str .. "'" - end - return '"' .. str:gsub('"', '\\"') .. '"' + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' end -- \a => '\\a', \0 => '\\0', 31 => '\31' local shortControlCharEscapes = { - ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", - ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v" + ['\a'] = '\\a', + ['\b'] = '\\b', + ['\f'] = '\\f', + ['\n'] = '\\n', + ['\r'] = '\\r', + ['\t'] = '\\t', + ['\v'] = '\\v' } local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031 -for i=0, 31 do - local ch = string.char(i) - if not shortControlCharEscapes[ch] then - shortControlCharEscapes[ch] = "\\"..i - longControlCharEscapes[ch] = string.format("\\%03d", i) - end +for i = 0, 31 do + local ch = string.char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = '\\' .. i + longControlCharEscapes[ch] = string.format('\\%03d', i) + end end local function escape(str) - return (str:gsub("\\", "\\\\") - :gsub("(%c)%f[0-9]", longControlCharEscapes) - :gsub("%c", shortControlCharEscapes)) + return (str:gsub('\\', '\\\\'):gsub('(%c)%f[0-9]', longControlCharEscapes):gsub('%c', shortControlCharEscapes)) end local function isIdentifier(str) - return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) + return type(str) == 'string' and str:match('^[_%a][_%a%d]*$') end local function isSequenceKey(k, sequenceLength) - return type(k) == 'number' - and 1 <= k - and k <= sequenceLength - and math.floor(k) == k + return type(k) == 'number' and 1 <= k and k <= sequenceLength and math.floor(k) == k end local defaultTypeOrders = { - ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, - ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 + ['number'] = 1, + ['boolean'] = 2, + ['string'] = 3, + ['table'] = 4, + ['function'] = 5, + ['userdata'] = 6, + ['thread'] = 7 } local function sortKeys(a, b) - local ta, tb = type(a), type(b) + local ta, tb = type(a), type(b) - -- strings and numbers are sorted numerically/alphabetically - if ta == tb and (ta == 'string' or ta == 'number') then return a < b end + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then + return a < b + end - local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] - -- Two default types are compared according to the defaultTypeOrders table - if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] - elseif dta then return true -- default types before custom ones - elseif dtb then return false -- custom types after default ones - end + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then + return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then + return true -- default types before custom ones + elseif dtb then + return false -- custom types after default ones + end - -- custom types are sorted out alphabetically - return ta < tb + -- custom types are sorted out alphabetically + return ta < tb end -- For implementation reasons, the behavior of rawlen & # is "undefined" when -- tables aren't pure sequences. So we implement our own # operator. local function getSequenceLength(t) - local len = 1 - local v = rawget(t,len) - while v ~= nil do - len = len + 1 - v = rawget(t,len) - end - return len - 1 + local len = 1 + local v = rawget(t, len) + while v ~= nil do + len = len + 1 + v = rawget(t, len) + end + return len - 1 end local function getNonSequentialKeys(t) - local keys = {} - local sequenceLength = getSequenceLength(t) - for k,_ in pairs(t) do - if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end - end - table.sort(keys, sortKeys) - return keys, sequenceLength + local keys = {} + local sequenceLength = getSequenceLength(t) + for k, _ in pairs(t) do + if not isSequenceKey(k, sequenceLength) then + table.insert(keys, k) + end + end + table.sort(keys, sortKeys) + return keys, sequenceLength end local function getToStringResultSafely(t, mt) - local __tostring = type(mt) == 'table' and rawget(mt, '__tostring') - local str, ok - if type(__tostring) == 'function' then - ok, str = pcall(__tostring, t) - str = ok and str or 'error: ' .. tostring(str) - end - if type(str) == 'string' and #str > 0 then return str end + local __tostring = type(mt) == 'table' and rawget(mt, '__tostring') + local str, ok + if type(__tostring) == 'function' then + ok, str = pcall(__tostring, t) + str = ok and str or 'error: ' .. tostring(str) + end + if type(str) == 'string' and #str > 0 then + return str + end end local function countTableAppearances(t, tableAppearances) - tableAppearances = tableAppearances or {} + tableAppearances = tableAppearances or {} - if type(t) == 'table' then - if not tableAppearances[t] then - tableAppearances[t] = 1 - for k,v in pairs(t) do - countTableAppearances(k, tableAppearances) - countTableAppearances(v, tableAppearances) - end - countTableAppearances(getmetatable(t), tableAppearances) - else - tableAppearances[t] = tableAppearances[t] + 1 + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k, v in pairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end end - end - return tableAppearances + return tableAppearances end local copySequence = function(s) - local copy, len = {}, #s - for i=1, len do copy[i] = s[i] end - return copy, len + local copy, len = {}, #s + for i = 1, len do + copy[i] = s[i] + end + return copy, len end local function makePath(path, ...) - local keys = {...} - local newPath, len = copySequence(path) - for i=1, #keys do - newPath[len + i] = keys[i] - end - return newPath + local keys = {...} + local newPath, len = copySequence(path) + for i = 1, #keys do + newPath[len + i] = keys[i] + end + return newPath end local function processRecursive(process, item, path, visited) - - if item == nil then return nil end - if visited[item] then return visited[item] end + if item == nil then + return nil + end + if visited[item] then + return visited[item] + end local processed = process(item, path) if type(processed) == 'table' then - local processedCopy = {} - visited[item] = processedCopy - local processedKey + local processedCopy = {} + visited[item] = processedCopy + local processedKey - for k,v in pairs(processed) do - processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) - if processedKey ~= nil then - processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + for k, v in pairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end end - end - local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) - setmetatable(processedCopy, mt) - processed = processedCopy + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + setmetatable(processedCopy, mt) + processed = processedCopy end return processed end - - ------------------------------------------------------------------- local Inspector = {} local Inspector_mt = {__index = Inspector} function Inspector:puts(...) - local args = {...} - local buffer = self.buffer - local len = #buffer - for i=1, #args do - len = len + 1 - buffer[len] = args[i] - end + local args = {...} + local buffer = self.buffer + local len = #buffer + for i = 1, #args do + len = len + 1 + buffer[len] = args[i] + end end function Inspector:down(f) - self.level = self.level + 1 - f() - self.level = self.level - 1 + self.level = self.level + 1 + f() + self.level = self.level - 1 end function Inspector:tabify() - self:puts(self.newline, string.rep(self.indent, self.level)) + self:puts(self.newline, string.rep(self.indent, self.level)) end function Inspector:alreadyVisited(v) - return self.ids[v] ~= nil + return self.ids[v] ~= nil end function Inspector:getId(v) - local id = self.ids[v] - if not id then - local tv = type(v) - id = (self.maxIds[tv] or 0) + 1 - self.maxIds[tv] = id - self.ids[v] = id - end - return tostring(id) + local id = self.ids[v] + if not id then + local tv = type(v) + id = (self.maxIds[tv] or 0) + 1 + self.maxIds[tv] = id + self.ids[v] = id + end + return tostring(id) end function Inspector:putKey(k) - if isIdentifier(k) then return self:puts(k) end - self:puts("[") - self:putValue(k) - self:puts("]") + if isIdentifier(k) then + return self:puts(k) + end + self:puts('[') + self:putValue(k) + self:puts(']') end function Inspector:putTable(t) - if t == inspect.KEY or t == inspect.METATABLE then - self:puts(tostring(t)) - elseif self:alreadyVisited(t) then - self:puts('') - elseif self.level >= self.depth then - self:puts('{...}') - else - if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end + if t == inspect.KEY or t == inspect.METATABLE then + self:puts(tostring(t)) + elseif self:alreadyVisited(t) then + self:puts('
') + elseif self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then + self:puts('<', self:getId(t), '>') + end - local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t) - local mt = getmetatable(t) - local toStringResult = getToStringResultSafely(t, mt) + local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t) + local mt = getmetatable(t) + local toStringResult = getToStringResultSafely(t, mt) - self:puts('{') - self:down(function() - if toStringResult then - self:puts(' -- ', escape(toStringResult)) - if sequenceLength >= 1 then self:tabify() end - end + self:puts('{') + self:down( + function() + if toStringResult then + self:puts(' -- ', escape(toStringResult)) + if sequenceLength >= 1 then + self:tabify() + end + end - local count = 0 - for i=1, sequenceLength do - if count > 0 then self:puts(',') end - self:puts(' ') - self:putValue(t[i]) - count = count + 1 - end + local count = 0 + for i = 1, sequenceLength do + if count > 0 then + self:puts(',') + end + self:puts(' ') + self:putValue(t[i]) + count = count + 1 + end - for _,k in ipairs(nonSequentialKeys) do - if count > 0 then self:puts(',') end - self:tabify() - self:putKey(k) - self:puts(' = ') - self:putValue(t[k]) - count = count + 1 - end + for _, k in ipairs(nonSequentialKeys) do + if count > 0 then + self:puts(',') + end + self:tabify() + self:putKey(k) + self:puts(' = ') + self:putValue(t[k]) + count = count + 1 + end - if mt then - if count > 0 then self:puts(',') end - self:tabify() - self:puts(' = ') - self:putValue(mt) - end - end) + if mt then + if count > 0 then + self:puts(',') + end + self:tabify() + self:puts(' = ') + self:putValue(mt) + end + end + ) - if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing } - self:tabify() - elseif sequenceLength > 0 then -- array tables have one extra space before closing } - self:puts(' ') + if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing } + self:tabify() + elseif sequenceLength > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + + self:puts('}') end - - self:puts('}') - end end function Inspector:putValue(v) - local tv = type(v) + local tv = type(v) - if tv == 'string' then - self:puts(smartQuote(escape(v))) - elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or - tv == 'cdata' or tv == 'ctype' then - self:puts(tostring(v)) - elseif tv == 'table' then - self:putTable(v) - else - self:puts('<',tv,' ',self:getId(v),'>') - end + if tv == 'string' then + self:puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or tv == 'cdata' or tv == 'ctype' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<', tv, ' ', self:getId(v), '>') + end end ------------------------------------------------------------------- function inspect.inspect(root, options) - options = options or {} + options = options or {} - local depth = options.depth or math.huge - local newline = options.newline or '\n' - local indent = options.indent or ' ' - local process = options.process + local depth = options.depth or math.huge + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process - if process then - root = processRecursive(process, root, {}, {}) - end + if process then + root = processRecursive(process, root, {}, {}) + end - local inspector = setmetatable({ - depth = depth, - level = 0, - buffer = {}, - ids = {}, - maxIds = {}, - newline = newline, - indent = indent, - tableAppearances = countTableAppearances(root) - }, Inspector_mt) + local inspector = + setmetatable( + { + depth = depth, + level = 0, + buffer = {}, + ids = {}, + maxIds = {}, + newline = newline, + indent = indent, + tableAppearances = countTableAppearances(root) + }, + Inspector_mt + ) - inspector:putValue(root) + inspector:putValue(root) - return table.concat(inspector.buffer) + return table.concat(inspector.buffer) end -setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) +setmetatable( + inspect, + {__call = function(_, ...) + return inspect.inspect(...) + end} +) return inspect - diff --git a/utils/stats.lua b/utils/stats.lua new file mode 100644 index 00000000..0b6b8ad6 --- /dev/null +++ b/utils/stats.lua @@ -0,0 +1,112 @@ +local Public = {} + +-- Get the mean value of a table +function Public.mean(t) + local sum = 0 + local count = 0 + + for k, v in pairs(t) do + if type(v) == 'number' then + sum = sum + v + count = count + 1 + end + end + + return (sum / count) +end + +-- Get the mode of a table. Returns a table of values. +-- Works on anything (not just numbers). +function Public.mode(t) + local counts = {} + + for k, v in pairs(t) do + if counts[v] == nil then + counts[v] = 1 + else + counts[v] = counts[v] + 1 + end + end + + local biggestCount = 0 + + for k, v in pairs(counts) do + if v > biggestCount then + biggestCount = v + end + end + + local temp = {} + + for k, v in pairs(counts) do + if v == biggestCount then + table.insert(temp, k) + end + end + + return temp +end + +-- Get the median of a table. +function Public.median(t) + local temp = {} + + -- deep copy table so that when we sort it, the original is unchanged + -- also weed out any non numbers + for k, v in pairs(t) do + if type(v) == 'number' then + table.insert(temp, v) + end + end + + table.sort(temp) + + -- If we have an even number of table elements or odd. + if math.fmod(#temp, 2) == 0 then + -- return mean value of middle two elements + return (temp[#temp / 2] + temp[(#temp / 2) + 1]) / 2 + else + -- return middle element + return temp[math.ceil(#temp / 2)] + end +end + +-- Get the standard deviation of a table +function Public.standardDeviation(t) + local m + local vm + local sum = 0 + local count = 0 + local result + + m = Public.mean(t) + + for k, v in pairs(t) do + if type(v) == 'number' then + vm = v - m + sum = sum + (vm * vm) + count = count + 1 + end + end + + result = math.sqrt(sum / (count - 1)) + + return result +end + +-- Get the max and min for a table +function Public.maxmin(t) + local max = -math.huge + local min = math.huge + + for k, v in pairs(t) do + if type(v) == 'number' then + max = math.max(max, v) + min = math.min(min, v) + end + end + + return max, min +end + +return Public diff --git a/utils/table.lua b/utils/table.lua index 6f925cc5..d651ea32 100644 --- a/utils/table.lua +++ b/utils/table.lua @@ -1,4 +1,5 @@ --luacheck: globals table +local Stats = require 'utils.stats' local random = math.random local floor = math.floor local remove = table.remove @@ -276,4 +277,9 @@ table.merge = util.merge -- @return table.equals = table.compare +--- Gets the median value out of a table. +-- @param tbl1
+-- @return +table.mean = Stats.mean + return table