288 lines
9.7 KiB
Lua

--[=[
File containing the primary logic for the mod.
Since v2.0.0 no more state is stored in the
global table, and instead all information can
be derived from the researched technologies.
]=]
local categories = require('categories')
local config = require('config_control')
local technology_formats = require('defines.technology_name_formats')
local setting_formats = require('defines.setting_name_formats')
local is_logging_enabled = false
local function plog(str, ...)
if is_logging_enabled then
log(('[qol] ' .. str):format(...))
end
end
local function setting_is_research_enabled(category_name)
return settings.startup[setting_formats.research_enabled:format(category_name)].value
end
local function setting_multiplier(category_name)
return settings.global[setting_formats.multiplier:format(category_name)].value
end
local function setting_flat_bonus(category_name)
return settings.global[setting_formats.flat_bonus:format(category_name)].value
end
local function setting_effect_enabled(category_name, effect_toggle)
return settings.global[setting_formats.effect_flag:format(category_name, effect_toggle)].value
end
--[=[
Calculates the total bonus at a particular set of levels.
@param category Category
@param levels number[]
The amount of finished researches in each tier
]=]
local function calculate_technology_bonus_at(category, levels)
if not setting_is_research_enabled(category.name) then
return 0
end
local tiers = config[category.name]
assert(#levels == #tiers, 'levels size must be match tier count')
local multiplier = setting_multiplier(category.name)
local bonus = 0
for index, tier in ipairs(tiers) do
bonus = bonus + tier.bonus_per_technology * levels[index]
end
bonus = bonus * multiplier
if category.type == 'double' then
return bonus
else
return math.ceil(bonus)
end
end
--[=[
Determines how many technologies have been researched in a specific category
and tier.
@param force LuaForce
@param category Category
@param tier_index number
@return number Index of the technology within the tier that has
**finished** being researched.
--]=]
local function get_researched_count_within_tier(force, category, tier_index)
if not setting_is_research_enabled(category.name) then return end
local technology_count = config[category.name][tier_index].technology_count
-- If infinite research
if technology_count == 0 then
return force.technologies[technology_formats.player:format(category.name, tier_index, 1)].level - 1
end
for i = technology_count, 1, -1 do
if force.technologies[('qol-%s-%d-%d'):format(category.name, tier_index, i)].researched then
return i
end
end
return 0
end
--[=[
Runs get_researched_count_within_tier for each tier.
@param force LuaForce
@param category Category
@return number[]
]=]
local function get_tier_research_levels(force, category)
local levels = {}
for tier_index = 1, #config[category.name] do
levels[tier_index] = get_researched_count_within_tier(force, category, tier_index)
end
return levels
end
--[=[
Updates the field values for a specific force and category.
@param force LuaForce
@param category Category
]=]
local function update_for_force_and_category(force, category)
local bonus = setting_flat_bonus(category.name)
if setting_is_research_enabled(category.name) then
local levels = get_tier_research_levels(force, category)
bonus = bonus + calculate_technology_bonus_at(category, levels)
end
if bonus < 0 then
plog('detected negative bonus for %q of value %s, clamping', category.name, bonus)
bonus = 0
end
local internal_technology_count = category.internal_technology_spec.count
local internal_value_scale = category.internal_technology_spec.value_scale
for _, effect in ipairs(category.effects) do
local remainder = math.ceil(bonus / internal_value_scale - 0.01)
if category.effect_settings and
category.effect_settings[effect] and
not setting_effect_enabled(category.name, category.effect_settings[effect])
then
remainder = 0
end
local pending_disables = {}
for n = internal_technology_count - 1, 0, -1 do
local value = math.pow(2, n)
local tech = force.technologies[technology_formats.internal:format(effect, n + 1)]
if remainder >= value then
remainder = remainder - value
if not tech.researched then
plog(' enabled internal tech: %q (%d * %f) for force %q', tech.name, value, internal_value_scale, force.name)
tech.researched = true
end
elseif tech.researched then
plog('disabled internal tech: %q (%d * %f) for force %q', tech.name, value, internal_value_scale, force.name)
pending_disables[#pending_disables + 1] = tech
end
end
for _, tech in ipairs(pending_disables) do
tech.researched = false
tech.enabled = false
end
end
end
--[=[
Updates the field values for a specific force.
@param force LuaForce
]=]
local function update_for_force(force)
for _, category in ipairs(categories.list) do
update_for_force_and_category(force, category)
end
end
--[=[
Updates the field values for a all forces.
]=]
local function update_for_all_forces()
for _, force in pairs(game.forces) do
update_for_force(force)
end
end
--[=[
Parses the name of a technology
@param research_name string
@return nothing or
category Category
tier_index number
technology_index number
]=]
local function parse_research_name(research_name)
local category_name, tier_index, technology_index = research_name:match('^qol%-([a-z-]-)%-(%d+)%-(%d)$')
if not category_name then return end
local tiers = config[category_name]
local category = categories.map[category_name]
tier_index, technology_index = tonumber(tier_index), tonumber(technology_index)
if not tiers or
not category or
not tier_index or
not technology_index or
tier_index < 1 or
tier_index > #tiers or
technology_index < 1
then
game.print(('[qol] warning: unknown technology, bug or conflicting mod? %q'):format(research_name))
return
end
return category, tier_index, technology_index
end
-- Handles adding bonuses for completed research
script.on_event(defines.events.on_research_finished, function (event)
local research = event.research
local force = research.force
local category = parse_research_name(research.name)
if not category then return end
plog('research completed %q', research.name)
update_for_force_and_category(force, category)
end)
script.on_event(defines.events.on_runtime_mod_setting_changed, function (event)
if event.setting:find('qol-', 1, true) ~= 1 then
return
end
log('setting changed')
update_for_all_forces()
end)
script.on_init(function ()
update_for_all_forces()
end)
script.on_event(defines.events.on_player_created, function (event)
local player = game.players[event.player_index]
if player.admin then
--player.print('[qol] Quality of Life Research v3 is installed') --drd
--player.print('[qol] Check out https://qol-research.aidiakapi.com/ if you want to create your own configuration!') --drd
end
end)
script.on_configuration_changed(function (changes)
local was_logging_enabled = is_logging_enabled
is_logging_enabled = true
plog('configuration change detected, updating for all forces and resetting technology effects')
update_for_all_forces()
for _, force in pairs(game.forces) do
force.reset_technology_effects()
end
is_logging_enabled = was_logging_enabled
local qol_research = changes.mod_changes.qol_research
if qol_research ~= nil then
local old_version = qol_research.old_version
if old_version ~= nil then
local version_major = old_version:match('^(%d+).(%d+).%d+$')
version_major = tonumber(version_major)
if version_major < 3 then
game.print(('[qol] Updated to version 3'))
game.print('[qol] Check out https://qol-research.aidiakapi.com/ if you want to create your own configuration!')
end
end
end
end)
commands.add_command('qol-sync', [[Syncs all technology effects, you can run this manually after using commands or mods to undo researching a technology.]], function (event)
local player = game.players[event.player_index]
if player.admin then
local was_logging_enabled = is_logging_enabled
is_logging_enabled = true
player.print('[qol] Synced, any details can be found in the log file.')
update_for_all_forces()
plog('syncing completed')
is_logging_enabled = was_logging_enabled
else
player.print('[qol] You must be an admin to run this command.')
end
end)
commands.add_command('qol-reset-technology-effects', [[Calls LuaForce::reset_technology_effects() for all forces.]], function (event)
local player = game.players[event.player_index]
if player.admin then
for _, force in pairs(game.forces) do
force.reset_technology_effects()
end
player.print('[qol] Technology effects reset.')
else
player.print('[qol] You must be an admin to run this command.')
end
end)