mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2025-01-06 00:23:49 +02:00
459 lines
17 KiB
Lua
459 lines
17 KiB
Lua
-- This file is part of thesixthroc's Pirate Ship softmod, licensed under GPLv3 and stored at https://github.com/danielmartin0/ComfyFactorio-Pirates.
|
|
|
|
local Memory = require 'maps.pirates.memory'
|
|
-- local Roles = require 'maps.pirates.roles.roles'
|
|
local Balance = require 'maps.pirates.balance'
|
|
local Common = require 'maps.pirates.common'
|
|
-- local Utils = require 'maps.pirates.utils_local'
|
|
local Math = require 'maps.pirates.math'
|
|
local Raffle = require 'maps.pirates.raffle'
|
|
-- local Loot = require 'maps.pirates.loot'
|
|
-- local CoreData = require 'maps.pirates.coredata'
|
|
local IslandEnum = require 'maps.pirates.surfaces.islands.island_enum'
|
|
local _inspect = require 'utils.inspect'.inspect
|
|
|
|
|
|
local Public = {}
|
|
|
|
local enum = {
|
|
TIME = 'Time',
|
|
FIND = 'Find',
|
|
NODAMAGE = 'No_Damage',
|
|
RESOURCEFLOW = 'Resource_Flow',
|
|
RESOURCECOUNT = 'Resource_Count',
|
|
WORMS = 'Worms',
|
|
FISH = 'Fish',
|
|
COMPILATRON = 'Compilatron', -- compilatron is robot that looks like sheep
|
|
}
|
|
Public.enum = enum
|
|
|
|
Public.quest_icons = {
|
|
[enum.TIME] = '[img=utility.time_editor_icon]',
|
|
[enum.NODAMAGE] = '[item=stone-wall]',
|
|
[enum.WORMS] = '[entity=small-worm-turret]',
|
|
[enum.FIND] = '[img=utility.ghost_time_to_live_modifier_icon]',
|
|
[enum.RESOURCEFLOW] = '',
|
|
[enum.RESOURCECOUNT] = '',
|
|
[enum.FISH] = '[item=raw-fish]',
|
|
[enum.COMPILATRON] = '[entity=compilatron]',
|
|
}
|
|
|
|
|
|
|
|
-- @TODO remake into a loot-style table:
|
|
|
|
function Public.quest_reward()
|
|
local ret
|
|
local multiplier = Balance.quest_reward_multiplier()
|
|
local rng = Math.random()
|
|
|
|
if rng <= 0.3 then
|
|
ret = {name = 'iron-plate', count = Math.ceil(2000 * multiplier), display_sprite = '[item=iron-plate]', display_amount = string.format('%.1fk', 2 * multiplier), chat_name = '[item=iron-plate]'}
|
|
elseif rng <= 0.5 then
|
|
ret = {name = 'copper-plate', count = Math.ceil(2000 * multiplier), display_sprite = '[item=copper-plate]', display_amount = string.format('%.1fk', 2 * multiplier), chat_name = '[item=copper-plate]'}
|
|
elseif rng <= 0.6 then
|
|
ret = {name = 'steel-plate', count = Math.ceil(380 * multiplier), display_sprite = '[item=steel-plate]', display_amount = string.format('%.0f', 380 * multiplier), chat_name = '[item=steel-plate]'}
|
|
elseif rng <= 0.7 then
|
|
ret = {name = 'raw-fish', count = Math.ceil(420 * (multiplier^(1/2))), display_sprite = '[item=raw-fish]', display_amount = string.format('%.0f', 420 * (multiplier^(1/2))), chat_name = '[item=raw-fish]'}
|
|
elseif rng <= 0.85 then
|
|
ret = {name = 'piercing-rounds-magazine', count = Math.ceil(250 * multiplier), display_sprite = '[item=piercing-rounds-magazine]', display_amount = string.format('%.0f', Math.ceil(250 * multiplier)), chat_name = '[item=piercing-rounds-magazine]'}
|
|
elseif rng <= 0.94 then
|
|
ret = {name = 'solid-fuel', count = Math.ceil(450 * multiplier), display_sprite = '[item=solid-fuel]', display_amount = string.format('%.0f', Math.ceil(450 * multiplier)), chat_name = '[item=solid-fuel]'}
|
|
else
|
|
ret = {name = 'modular-armor', count = 1, display_sprite = '[item=modular-armor]', display_amount = '1', chat_name = '[item=modular-armor]'}
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
|
|
|
|
|
|
function Public.initialise_random_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
destination.dynamic_data.quest_complete = false
|
|
|
|
local rng = Math.random(100)
|
|
if rng <= 10 then
|
|
Public.initialise_nodamage_quest()
|
|
elseif rng <= 33 then
|
|
Public.initialise_worms_quest()
|
|
elseif rng <= 54 then
|
|
Public.initialise_time_quest()
|
|
elseif rng <= 74 then
|
|
Public.initialise_find_quest()
|
|
elseif rng <= 100 then
|
|
Public.initialise_resourcecount_quest()
|
|
-- Public.initialise_resourceflow_quest()
|
|
end
|
|
|
|
-- Public.initialise_time_quest()
|
|
end
|
|
|
|
function Public.initialise_random_cave_island_quest()
|
|
local rng = Math.random(100)
|
|
if rng <= 30 then
|
|
Public.initialise_fish_quest()
|
|
elseif rng <= 60 then
|
|
Public.initialise_worms_quest()
|
|
else
|
|
Public.initialise_compilatron_quest()
|
|
end
|
|
end
|
|
|
|
function Public.initialise_time_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
destination.dynamic_data.quest_type = enum.TIME
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = Balance.time_quest_seconds()
|
|
destination.dynamic_data.quest_progressneeded = 9999999
|
|
|
|
return true
|
|
end
|
|
|
|
function Public.initialise_find_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
if destination.subtype == IslandEnum.enum.STANDARD or destination.subtype == IslandEnum.enum.RADIOACTIVE or destination.subtype == IslandEnum.enum.STANDARD_VARIANT then
|
|
|
|
destination.dynamic_data.quest_type = enum.FIND
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = 0
|
|
if #Common.crew_get_crew_members() > 15 then
|
|
destination.dynamic_data.quest_progressneeded = 2
|
|
else
|
|
destination.dynamic_data.quest_progressneeded = 1
|
|
end
|
|
return true
|
|
else
|
|
log('Find quest not appropriate, rerolling')
|
|
Public.initialise_random_quest() --@FIXME: mild danger of loop
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
function Public.initialise_nodamage_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
-- @FIXME: this if check looks ill-formed when destination is nil
|
|
if not destination and destination.dynamic_data and destination.dynamic_data.rocketsilomaxhp then return false end
|
|
|
|
destination.dynamic_data.quest_type = enum.NODAMAGE
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = 0
|
|
destination.dynamic_data.quest_progressneeded = destination.dynamic_data.rocketsilomaxhp
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
-- @UNUSED
|
|
-- function Public.initialise_resourceflow_quest()
|
|
-- local destination = Common.current_destination()
|
|
|
|
-- if not destination and destination.dynamic_data and destination.dynamic_data.rocketsilomaxhp then return false end
|
|
|
|
-- destination.dynamic_data.quest_type = enum.RESOURCEFLOW
|
|
-- destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
-- destination.dynamic_data.quest_progress = 0
|
|
|
|
-- local generated_flow_quest = Public.generate_flow_quest()
|
|
-- if not generated_flow_quest then return false end
|
|
|
|
-- destination.dynamic_data.quest_params = {item = generated_flow_quest.item}
|
|
|
|
-- local progressneeded_before_rounding = generated_flow_quest.base_rate * Balance.resource_quest_multiplier()
|
|
|
|
-- destination.dynamic_data.quest_progressneeded = Math.ceil(progressneeded_before_rounding/10) * 10
|
|
|
|
-- return true
|
|
-- end
|
|
|
|
|
|
function Public.initialise_resourcecount_quest()
|
|
local memory = Memory.get_crew_memory()
|
|
local destination = Common.current_destination()
|
|
|
|
if not destination and destination.dynamic_data and destination.dynamic_data.rocketsilomaxhp then return end
|
|
|
|
destination.dynamic_data.quest_type = enum.RESOURCECOUNT
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = 0
|
|
|
|
local generated_production_quest = Public.generate_resourcecount_quest()
|
|
if not generated_production_quest then return false end
|
|
|
|
destination.dynamic_data.quest_params = {item = generated_production_quest.item}
|
|
|
|
local force = memory.force
|
|
if force and force.valid then
|
|
destination.dynamic_data.quest_params.initial_count = force.item_production_statistics.get_flow_count{name = generated_production_quest.item, input = true, precision_index = defines.flow_precision_index.one_thousand_hours, count = true}
|
|
end
|
|
|
|
local progressneeded_before_rounding = generated_production_quest.base_rate * Balance.resource_quest_multiplier()
|
|
|
|
destination.dynamic_data.quest_progressneeded = Math.ceil(progressneeded_before_rounding/10)*10
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
function Public.initialise_worms_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
if not (destination.surface_name and game.surfaces[destination.surface_name]) then return false end
|
|
|
|
local surface = game.surfaces[destination.surface_name]
|
|
|
|
local worms = surface.find_entities_filtered{type = 'turret'}
|
|
|
|
local count = 0
|
|
for i = 1, #worms do
|
|
local w = worms[i]
|
|
if w.destructible then count = count + 1 end
|
|
end
|
|
|
|
local needed = Math.ceil(
|
|
15 * Math.slopefromto(count, 0, 20) + 12 * Math.slopefromto(count, 20, 80)
|
|
)
|
|
|
|
if destination.subtype == IslandEnum.enum.RED_DESERT then
|
|
needed = Math.random(20, 30)
|
|
elseif destination.subtype == IslandEnum.enum.CAVE then
|
|
needed = Math.random(15, 25)
|
|
end
|
|
|
|
-- These extra difficulty formulas don't work when there is very little amount of worms
|
|
-- if Common.difficulty_scale() < 1 then needed = Math.max(1, needed - 3) end
|
|
-- if Common.difficulty_scale() > 1 then needed = Math.max(1, needed + 2) end
|
|
|
|
-- local difficulty_name = CoreData.get_difficulty_option_informal_name_from_value(Common.difficulty_scale())
|
|
-- if difficulty_name == 'easy' then
|
|
-- needed = Math.max(1, needed - 3)
|
|
-- elseif difficulty_name ~= 'normal' then
|
|
-- needed = Math.max(1, needed + 2)
|
|
-- end
|
|
|
|
if needed >= 10 then
|
|
destination.dynamic_data.quest_type = enum.WORMS
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = 0
|
|
destination.dynamic_data.quest_progressneeded = needed
|
|
return true
|
|
else
|
|
log('Worms quest not appropriate, rerolling')
|
|
Public.initialise_random_quest() --@FIXME: mild danger of loop
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Catch amount of fish (currently Cave island exclusive, because it's hard to calculate "quest_progressneeded")
|
|
function Public.initialise_fish_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
if not destination and destination.dynamic_data then return false end
|
|
|
|
destination.dynamic_data.quest_type = enum.FISH
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = 0
|
|
destination.dynamic_data.quest_progressneeded = Math.random(350, 500) -- assuming that base caught fish amount is 3
|
|
|
|
return true
|
|
end
|
|
|
|
-- Rescue compilatrons under the heavy rocks (currently Cave island exclusive, because it's hard to calculate "quest_progressneeded")
|
|
function Public.initialise_compilatron_quest()
|
|
local destination = Common.current_destination()
|
|
|
|
if not destination and destination.dynamic_data then return false end
|
|
|
|
destination.dynamic_data.quest_type = enum.COMPILATRON
|
|
destination.dynamic_data.quest_reward = Public.quest_reward()
|
|
destination.dynamic_data.quest_progress = 0
|
|
destination.dynamic_data.quest_progressneeded = Math.random(50, 80) -- assuming that chance to find compilatron is 1/20
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
function Public.try_resolve_quest()
|
|
local memory = Memory.get_crew_memory()
|
|
local destination = Common.current_destination()
|
|
|
|
if destination.dynamic_data.quest_type and destination.dynamic_data.quest_progress and destination.dynamic_data.quest_progressneeded and destination.dynamic_data.quest_progress >= destination.dynamic_data.quest_progressneeded and (not destination.dynamic_data.quest_complete) then
|
|
|
|
local force = memory.force
|
|
if not (force and force.valid) then return end
|
|
Common.notify_force_light(force, {'pirates.granted_1', {'pirates.granted_quest_complete'}, destination.dynamic_data.quest_reward.display_amount .. destination.dynamic_data.quest_reward.chat_name})
|
|
|
|
local name = destination.dynamic_data.quest_reward.name
|
|
local count = destination.dynamic_data.quest_reward.count
|
|
|
|
-- destination.dynamic_data.quest_type = nil
|
|
-- destination.dynamic_data.quest_reward = nil
|
|
-- destination.dynamic_data.quest_progress = nil
|
|
-- destination.dynamic_data.quest_progressneeded = nil
|
|
destination.dynamic_data.quest_complete = true
|
|
|
|
local boat = memory.boat
|
|
if not boat then return end
|
|
local surface_name = boat.surface_name
|
|
if not surface_name then return end
|
|
local surface = game.surfaces[surface_name]
|
|
if not (surface and surface.valid) then return end
|
|
local chest = boat.output_chest
|
|
if not chest and chest.valid then return end
|
|
|
|
local inventory = chest.get_inventory(defines.inventory.chest)
|
|
local inserted = inventory.insert{name = name, count = count}
|
|
|
|
if inserted < count then
|
|
local chest2 = boat.backup_output_chest
|
|
if chest2 and chest2.valid then
|
|
local inventory2 = chest2.get_inventory(defines.inventory.chest)
|
|
local inserted2 = inventory2.insert{name = name, count = count - inserted}
|
|
if (inserted + inserted2) < count then
|
|
Common.notify_force(force, {'pirates.error_cabin_full'})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Public.flow_quest_data_raw = {
|
|
-- {0.2, 0, 1, false, 'submachine-gun', 3 * 12},
|
|
-- {1, 0, 1, false, 'electronic-circuit', 3 * 120},
|
|
-- {0.2, 0.1, 1, false, 'big-electric-pole', 1 * 120},
|
|
-- {0.4, 0.2, 1, false, 'engine-unit', 3 * 6},
|
|
-- -- {1, 0.5, 1, false, 'advanced-circuit', 1 * 10},
|
|
-- -- {0.3, 0.8, 1, false, 'electric-engine-unit', 1 * 6},
|
|
-- }
|
|
|
|
-- function Public.flow_quest_data()
|
|
-- local ret = {}
|
|
-- local data = Public.flow_quest_data_raw
|
|
-- for i = 1, #data do
|
|
-- local datum = data[i]
|
|
-- ret[#ret + 1] = {
|
|
-- weight = datum[1],
|
|
-- game_completion_progress_min = datum[2],
|
|
-- game_completion_progress_max = datum[3],
|
|
-- scaling = datum[4],
|
|
-- item = datum[5],
|
|
-- base_rate = datum[6],
|
|
-- }
|
|
-- end
|
|
-- return ret
|
|
-- end
|
|
|
|
-- @UNUSED
|
|
-- function Public.generate_flow_quest()
|
|
-- --@TODO: Ensure this function cannot return nil
|
|
-- --@TODO: This is related to a more general problem with raffles — how they handle game_completion being above 1. As of May '22, we cap game_completion at 1 before passing it to the raffle
|
|
|
|
-- local game_completion_progress = Common.game_completion_progress_capped()
|
|
|
|
-- local data = Public.flow_quest_data()
|
|
-- local v, w = {}, {}
|
|
|
|
-- for i = 1, #data, 1 do
|
|
-- table.insert(v, {item = data[i].item, base_rate = data[i].base_rate})
|
|
|
|
-- local destination = Common.current_destination()
|
|
-- if not (destination and destination.subtype and data[i].map_subtype and data[i].map_subtype == destination.subtype) then
|
|
-- if data[i].scaling then -- scale down weights away from the midpoint 'peak' (without changing the mean)
|
|
-- local midpoint = (data[i].game_completion_progress_max + data[i].game_completion_progress_min) / 2
|
|
-- local difference = (data[i].game_completion_progress_max - data[i].game_completion_progress_min)
|
|
-- table.insert(w, data[i].weight * Math.max(0, 1 - (Math.abs(game_completion_progress - midpoint) / (difference / 2))))
|
|
-- else -- no scaling
|
|
-- if data[i].game_completion_progress_min <= game_completion_progress and data[i].game_completion_progress_max >= game_completion_progress then
|
|
-- table.insert(w, data[i].weight)
|
|
-- else
|
|
-- table.insert(w, 0)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
|
|
-- return Raffle.raffle(v, w)
|
|
-- end
|
|
|
|
|
|
|
|
|
|
|
|
Public.resourcecount_quest_data_raw = {
|
|
{1.1, 0, 1, false, 'iron-gear-wheel', 2400},
|
|
{0.5, 0, 1, false, 'electronic-circuit', 1400},
|
|
{1.1, 0, 1, false, 'transport-belt', 1600},
|
|
{0.8, 0, 1, false, 'repair-pack', 350},
|
|
-- {0.1, 0, 1, false, 'red-wire', 500},
|
|
{0.4, 0, 1, false, 'empty-barrel', 200},
|
|
{0.7, 0, 0.5, false, 'underground-belt', 200},
|
|
{0.7, 0, 0.5, false, 'splitter', 150},
|
|
{0.35, 0.2, 1, false, 'fast-splitter', 60},
|
|
{0.35, 0.2, 1, false, 'fast-underground-belt', 75},
|
|
{0.7, 0.3, 1, false, 'big-electric-pole', 100},
|
|
{0.3, 0.61, 1, false, 'advanced-circuit', 350},
|
|
{1, 0, 1, false, 'shotgun-shell', 600},
|
|
{1, 0.9, 1, false, 'processing-unit', 40},
|
|
{0.6, 0.8, 1, false, 'electric-engine-unit', 1 * 6},
|
|
}
|
|
|
|
function Public.resourcecount_quest_data()
|
|
local ret = {}
|
|
local data = Public.resourcecount_quest_data_raw
|
|
for i = 1, #data do
|
|
local datum = data[i]
|
|
ret[#ret + 1] = {
|
|
weight = datum[1],
|
|
game_completion_progress_min = datum[2],
|
|
game_completion_progress_max = datum[3],
|
|
scaling = datum[4],
|
|
item = datum[5],
|
|
base_rate = datum[6],
|
|
}
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function Public.generate_resourcecount_quest()
|
|
local game_completion_progress = Common.game_completion_progress_capped()
|
|
|
|
local data = Public.resourcecount_quest_data()
|
|
local v, w = {}, {}
|
|
|
|
for i = 1, #data, 1 do
|
|
table.insert(v, {item = data[i].item, base_rate = data[i].base_rate})
|
|
|
|
local destination = Common.current_destination()
|
|
if not (destination and destination.subtype and data[i].map_subtype and data[i].map_subtype == destination.subtype) then
|
|
if data[i].scaling then -- scale down weights away from the midpoint 'peak' (without changing the mean)
|
|
local midpoint = (data[i].game_completion_progress_max + data[i].game_completion_progress_min) / 2
|
|
local difference = (data[i].game_completion_progress_max - data[i].game_completion_progress_min)
|
|
table.insert(w, data[i].weight * Math.max(0, 1 - (Math.abs(game_completion_progress - midpoint) / (difference / 2))))
|
|
else -- no scaling
|
|
if data[i].game_completion_progress_min <= game_completion_progress and data[i].game_completion_progress_max >= game_completion_progress then
|
|
table.insert(w, data[i].weight)
|
|
else
|
|
table.insert(w, 0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return Raffle.raffle(v, w)
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
return Public |