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:
commit
17f7e9dcfe
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
||||
|
176
utils/alien_evolution_progress.lua
Normal file
176
utils/alien_evolution_progress.lua
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user