1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2024-12-12 10:04:40 +02:00

Merge pull request #533 from iltar/alien-spawner-using-prototype

Alien spawner using prototype
This commit is contained in:
Matthew 2018-12-10 15:14:09 -05:00 committed by GitHub
commit 17f7e9dcfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 220 additions and 196 deletions

View File

@ -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

View File

@ -45,7 +45,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

View File

@ -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

View File

@ -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'
@ -16,12 +16,15 @@ local random = math.random
local floor = math.floor
local ceil = math.ceil
local size = table.size
local pairs = pairs
local raise_event = script.raise_event
-- this
local AlienSpawner = {}
local alien_size_chart = {}
local memory = {
alien_collision_boxes = {},
}
local locations_to_scan = {
{x = 0, y = -1.5}, -- up
{x = 1.5, y = 0}, -- right
@ -30,18 +33,15 @@ local locations_to_scan = {
}
Global.register_init({
alien_size_chart = alien_size_chart,
memory = memory,
}, function(tbl)
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
}
tbl.memory.alien_collision_boxes[name] = prototype.collision_box
end
end
end, function(tbl)
alien_size_chart = tbl.alien_size_chart
memory = tbl.memory
end)
local rocks_to_find = Template.diggy_rocks
@ -95,18 +95,21 @@ local function spawn_aliens(aliens, force, surface, x, y)
local count_tiles_filtered = surface.count_tiles_filtered
local spawn_count = 0
local alien_collision_boxes = memory.alien_collision_boxes
for name, amount in pairs(aliens) do
local size_data = alien_size_chart[name]
if not size_data then
local collision_box = alien_collision_boxes[name]
if not collision_box then
Debug.print_position(position, 'Unable to find prototype data for ' .. name)
break
end
local collision_box = size_data.collision_box
local left_top_x = collision_box.left_top.x * 1.6
local left_top_y = collision_box.left_top.y * 1.6
local right_bottom_x = collision_box.right_bottom.x * 1.6
local right_bottom_y = collision_box.right_bottom.y * 1.6
local left_top = collision_box.left_top
local right_bottom = collision_box.right_bottom
local left_top_x = left_top.x * 1.6
local left_top_y = left_top.y * 1.6
local right_bottom_x = right_bottom.x * 1.6
local right_bottom_y = right_bottom.y * 1.6
for i = #locations_to_scan, 1, -1 do
local direction = locations_to_scan[i]
@ -171,16 +174,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

View File

@ -0,0 +1,176 @@
--[[-- 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
local format = string.format
-- 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', spawner))
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

View File

@ -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