From 0fbfe49b2f45f88ef4608340a4c476b22b613f56 Mon Sep 17 00:00:00 2001 From: Lynn Date: Sun, 9 Dec 2018 19:10:16 +0100 Subject: [PATCH] AlienEvolutionProgress to utils and uses prototype data --- features/chat_triggers.lua | 2 +- features/player_create.lua | 2 +- map_gen/Diggy/AlienEvolutionProgress.lua | 159 -------------------- map_gen/Diggy/Feature/AlienSpawner.lua | 20 ++- utils/alien_evolution_progress.lua | 175 +++++++++++++++++++++++ utils/table.lua | 27 ++-- 6 files changed, 202 insertions(+), 183 deletions(-) delete mode 100644 map_gen/Diggy/AlienEvolutionProgress.lua create mode 100644 utils/alien_evolution_progress.lua diff --git a/features/chat_triggers.lua b/features/chat_triggers.lua index 3e81f0bb..62790e93 100644 --- a/features/chat_triggers.lua +++ b/features/chat_triggers.lua @@ -34,7 +34,7 @@ local function hodor(event) -- first check for a match, since 99% of messages aren't a match for 'hodor' local message = event.message:lower() if message:match('hodor') then - game.print('Hodor: ' .. table.get_random_weighted(Hodor, 1, 2)) + game.print('Hodor: ' .. table.get_random_weighted(Hodor)) end end diff --git a/features/player_create.lua b/features/player_create.lua index 6f1336b0..9aad5fee 100644 --- a/features/player_create.lua +++ b/features/player_create.lua @@ -44,7 +44,7 @@ local function player_created(event) local random_messages = config.random_join_message_set if #random_messages > 0 then - p(get_random_weighted(random_messages, 1, 2)) + p(get_random_weighted(random_messages)) end if config.show_info_at_start then diff --git a/map_gen/Diggy/AlienEvolutionProgress.lua b/map_gen/Diggy/AlienEvolutionProgress.lua deleted file mode 100644 index 5f3950c4..00000000 --- a/map_gen/Diggy/AlienEvolutionProgress.lua +++ /dev/null @@ -1,159 +0,0 @@ ---[[-- info - Original (javascript) version: https://hastebin.com/udakacavap.js - Can be tested against: https://wiki.factorio.com/Enemies#Spawn_chances_by_evolution_factor -]] - --- dependencies -local Global = require 'utils.global' -local random = math.random -local round = math.round - --- this -local AlienEvolutionProgress = {} - -local alien_cache = { - biters = { - evolution = -1, - cache = {}, - }, - spitters = { - evolution = -1, - cache = {}, - }, -} - -Global.register({ - alien_cache = alien_cache, -}, function(tbl) - alien_cache = tbl.alien_cache -end) - --- values are in the form {evolution, weight} -local biters = { - {'small-biter', {{0.0, 0.3}, {0.6, 0.0}}}, - {'medium-biter', {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}, - {'big-biter', {{0.5, 0.0}, {1.0, 0.4}}}, - {'behemoth-biter', {{0.9, 0.0}, {1.0, 0.3}}}, -} - -local spitters = { - {'small-biter', {{0.0, 0.3}, {0.35, 0.0}}}, - {'small-spitter', {{0.25, 0.0}, {0.5, 0.3}, {0.7, 0.0}}}, - {'medium-spitter', {{0.4, 0.0}, {0.7, 0.3}, {0.9, 0.1}}}, - {'big-spitter', {{0.5, 0.0}, {1.0, 0.4}}}, - {'behemoth-spitter', {{0.9, 0.0}, {1.0, 0.3}}}, -} - -local function lerp(low, high, pos) - local s = high[1] - low[1]; - local l = (pos - low[1]) / s; - return (low[2] * (1 - l)) + (high[2] * l) -end - -local function get_values(map, evo) - local result = {} - local sum = 0 - - for _, data in pairs(map) do - local list = data[2]; - local low = list[1]; - local high = list[#list]; - - for _, val in pairs(list) do - if(val[1] <= evo and val[1] > low[1]) then - low = val; - end - if(val[1] >= evo and val[1] < high[1]) then - high = val - end - end - - local val - if (evo <= low[1]) then - val = low[2] - elseif (evo >= high[1]) then - val = high[2]; - else - val = lerp(low, high, evo) - end - sum = sum + val; - - result[data[1]] = val; - end - - for index, _ in pairs(result) do - result[index] = result[index] / sum - end - - return result; -end - -local function get_name_by_random(collection) - local pre_calculated = random() - local current = 0 - - for name, probability in pairs(collection) do - current = current + probability - if (current >= pre_calculated) then - return name - end - end - - Debug.print('AlienEvolutionProgress.get_name_by_random: Current \'' .. current .. '\' should be higher or equal to random \'' .. pre_calculated .. '\'') -end - -function AlienEvolutionProgress.getBiterValues(evolution) - local evolution_value = round(evolution * 100) - - if (alien_cache.biters.evolution < evolution_value) then - alien_cache.biters.evolution = evolution_value - alien_cache.biters.cache = get_values(biters, evolution) - end - - return alien_cache.biters.cache -end - -function AlienEvolutionProgress.getSpitterValues(evolution) - local evolution_value = round(evolution * 100) - - if (alien_cache.spitters.evolution < evolution_value) then - alien_cache.spitters.evolution = evolution_value - alien_cache.spitters.cache = get_values(spitters, evolution) - end - - return alien_cache.spitters.cache -end - -function AlienEvolutionProgress.getBitersByEvolution(total_biters, evolution) - local biters_calculated = {} - local map = AlienEvolutionProgress.getBiterValues(evolution) - - for i = 1, total_biters do - local name = get_name_by_random(map) - if (nil == biters_calculated[name]) then - biters_calculated[name] = 1 - else - biters_calculated[name] = biters_calculated[name] + 1 - end - end - - return biters_calculated -end - -function AlienEvolutionProgress.getSpittersByEvolution(total_spitters, evolution) - local spitters_calculated = {} - local map = AlienEvolutionProgress.getSpitterValues(evolution) - - for i = 1, total_spitters do - local name = get_name_by_random(map) - if (nil == spitters_calculated[name]) then - spitters_calculated[name] = 1 - else - spitters_calculated[name] = spitters_calculated[name] + 1 - end - end - - return spitters_calculated -end - -return AlienEvolutionProgress diff --git a/map_gen/Diggy/Feature/AlienSpawner.lua b/map_gen/Diggy/Feature/AlienSpawner.lua index a2e11a1c..7aa27c7d 100644 --- a/map_gen/Diggy/Feature/AlienSpawner.lua +++ b/map_gen/Diggy/Feature/AlienSpawner.lua @@ -8,7 +8,7 @@ local Event = require 'utils.event' local Global = require 'utils.global' local Token = require 'utils.token' local Task = require 'utils.Task' -local AlienEvolutionProgress = require 'map_gen.Diggy.AlienEvolutionProgress' +local AlienEvolutionProgress = require 'utils.alien_evolution_progress' local Debug = require 'map_gen.Diggy.Debug' local Template = require 'map_gen.Diggy.Template' local CreateParticles = require 'features.create_particles' @@ -35,7 +35,6 @@ Global.register_init({ for name, prototype in pairs(game.entity_prototypes) do if prototype.type == 'unit' and prototype.subgroup.name == 'enemies' then tbl.alien_size_chart[name] = { - name = name, collision_box = prototype.collision_box } end @@ -236,16 +235,13 @@ function AlienSpawner.register(config) return end - local aliens = AlienEvolutionProgress.getBitersByEvolution(random(1, 2), evolution_factor) - for name, amount in pairs(AlienEvolutionProgress.getSpittersByEvolution(random(1, 2), evolution_factor)) do - local existing = aliens[name] - if existing then - amount = amount + existing - end - aliens[name] = amount - end - - spawn_aliens(aliens, force, event.surface, x, y) + spawn_aliens( + AlienEvolutionProgress.get_aliens(AlienEvolutionProgress.create_spawner_request(3), force.evolution_factor), + force, + event.surface, + x, + y + ) end) end diff --git a/utils/alien_evolution_progress.lua b/utils/alien_evolution_progress.lua new file mode 100644 index 00000000..23bac8e0 --- /dev/null +++ b/utils/alien_evolution_progress.lua @@ -0,0 +1,175 @@ +--[[-- info + Original (javascript) version: https://hastebin.com/udakacavap.js + Can be tested against: https://wiki.factorio.com/Enemies#Spawn_chances_by_evolution_factor +]] + +-- dependencies +local Global = require 'utils.global' +local Debug = require 'utils.debug' +local get_random_weighted = table.get_random_weighted +local round = math.round +local ceil = math.ceil +local floor = math.floor +local random = math.random +local pairs = pairs + +-- this +local AlienEvolutionProgress = {} + +local memory = { + spawner_specifications = {}, + spawner_specifications_count = 0, + evolution_cache = { + ['biter-spawner'] = { + evolution = -1, + weight_table = {}, + }, + ['spitters-spawner'] = { + evolution = -1, + weight_table = {}, + }, + }, +} + +Global.register_init({ + memory = memory, +}, function(tbl) + for name, prototype in pairs(game.entity_prototypes) do + if prototype.type == 'unit-spawner' and prototype.subgroup.name == 'enemies' then + tbl.memory.spawner_specifications[name] = prototype.result_units + memory.spawner_specifications_count = memory.spawner_specifications_count + 1 + end + end +end, function(tbl) + memory = tbl.memory +end) + +local function lerp(low, high, pos) + local s = high.evolution_factor - low.evolution_factor; + local l = (pos - low.evolution_factor) / s; + return (low.weight * (1 - l)) + (high.weight * l) +end + +local function get_values(map, evolution_factor) + local result = {} + local sum = 0 + + for _, spawner_data in pairs(map) do + local list = spawner_data.spawn_points; + local low = list[1]; + local high = list[#list]; + + for _, val in pairs(list) do + local val_evolution = val.evolution_factor + if val_evolution <= evolution_factor and val_evolution > low.evolution_factor then + low = val; + end + if val_evolution >= evolution_factor and val_evolution < high.evolution_factor then + high = val + end + end + + local val + if evolution_factor <= low.evolution_factor then + val = low.weight + elseif evolution_factor >= high.evolution_factor then + val = high.weight; + else + val = lerp(low, high, evolution_factor) + end + sum = sum + val; + + result[spawner_data.unit] = val; + end + + local weighted_table = {} + local count = 0 + for index, _ in pairs(result) do + count = count + 1 + weighted_table[count] = {index, result[index] / sum} + end + + return weighted_table; +end + +local function get_spawner_values(spawner, evolution) + local spawner_specification = memory.spawner_specifications[spawner] + if not spawner_specification then + Debug.print(format('Spawner "%s" does not exist in the prototype data')) + return + end + + local cache = memory.evolution_cache[spawner] + + if not cache then + cache = { + evolution = -1, + weight_table = {}, + } + memory.evolution_cache[spawner] = cache + end + + local evolution_value = round(evolution * 100) + if (cache.evolution < evolution_value) then + cache.evolution = evolution_value + cache.weight_table = get_values(spawner_specification, evolution) + end + + return cache.weight_table +end + +local function calculate_total(count, spawner, evolution) + if count == 0 then + return {} + end + + local spawner_values = get_spawner_values(spawner, evolution) + if not spawner_values then + return {} + end + + local aliens = {} + for _ = 1, count do + local name = get_random_weighted(spawner_values) + aliens[name] = (aliens[name] or 0) + 1 + end + + return aliens +end + +---Creates the spawner_request structure required for AlienEvolutionProgress.get_aliens for all +---available spawners. If dividing the total spawners by the total aliens causes a fraction, the +---fraction will decide a chance to spawn. 1 alien for 2 spawners will have 50% on both. +---@param total_aliens table +function AlienEvolutionProgress.create_spawner_request(total_aliens) + local per_spawner = total_aliens / memory.spawner_specifications_count + local fraction = per_spawner % 1 + + local spawner_request = {} + for spawner, _ in pairs(memory.spawner_specifications) do + local count = per_spawner + if fraction > 0 then + if random() > fraction then + count = ceil(count) + else + count = floor(count) + end + end + spawner_request[spawner] = count + end + + return spawner_request +end + +function AlienEvolutionProgress.get_aliens(spawner_requests, evolution) + local aliens = {} + for spawner, count in pairs(spawner_requests) do + for name, amount in pairs(calculate_total(count, spawner, evolution)) do + aliens[name] = (aliens[name] or 0) + amount + end + end + + return aliens +end + +return AlienEvolutionProgress diff --git a/utils/table.lua b/utils/table.lua index 6a2cea50..b69cc71c 100644 --- a/utils/table.lua +++ b/utils/table.lua @@ -1,6 +1,10 @@ local random = math.random -local insert = table.insert +local floor = math.floor local remove = table.remove +local insert = table.insert +local tonumber = tonumber +local pairs = pairs +local table_size = table_size --- Searches a table to remove a specific element without an index -- @param t table to search @@ -42,12 +46,14 @@ table.index_of = function(t, e) return -1 end +local index_of = table.index_of + --- Checks if a table contains an element -- @param t table to search -- @param e element to search for -- @returns true or false table.contains = function(t, e) - return table.index_of(t, e) > -1 + return index_of(t, e) > -1 end --- Adds an element into a specific index position while shuffling the rest down @@ -85,18 +91,20 @@ end --- Chooses a random entry from a weighted table -- @param weight_table a table of tables with items and their weights --- @param item_index the index of the items --- @param weight_index the index of the weights +-- @param item_index the index of the items, defaults to 1 +-- @param weight_index the index of the weights, defaults to 2 -- @returns a random item with weighting -- @see features.chat_triggers.hodor table.get_random_weighted = function(weighted_table, item_index, weight_index) local total_weight = 0 + item_index = item_index or 1 + weight_index = weight_index or 2 for _, w in pairs(weighted_table) do total_weight = total_weight + w[weight_index] end - local index = random(total_weight) + local index = random() * total_weight local weight_sum = 0 for _, w in pairs(weighted_table) do weight_sum = weight_sum + w[weight_index] @@ -134,8 +142,8 @@ end --[[ Returns the index where t[index] == target. - If there is no such index, returns a negative vaule such that bit32.bnot(value) is - the index that the vaule should be inserted to keep the list ordered. + If there is no such index, returns a negative value such that bit32.bnot(value) is + the index that the value should be inserted to keep the list ordered. t must be a list in ascending order for the return value to be valid. Usage example: @@ -148,8 +156,7 @@ end game.print("value found at index: " .. index) end ]] -table.binary_search = - function(t, target) +table.binary_search = function(t, target) --For some reason bit32.bnot doesn't return negative numbers so I'm using ~x = -1 - x instead. local lower = 1 @@ -160,7 +167,7 @@ table.binary_search = end repeat - local mid = math.floor((lower + upper) / 2) + local mid = floor((lower + upper) * 0.5) local value = t[mid] if value == target then return mid