1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-06 00:23:49 +02:00
ComfyFactorio/maps/pirates/quest.lua
Piratux 564428661a Fixes and quest balanacing
Changes:
- Fixed an issue, where crew would lose when ship auto undock timer reached 0, but crew hadn't escaped from island by then, causing it to lose (in cases where resources are a must to escape from island).
- Fixed an issue, where boat wouldn't auto undock in radioactive island when auto undock timer reached 0.
- Adjusted some dock market prices (for converting one item to another).
- Decreased the amount of items needed for a gui island crafting quest for harder difficulties and decreased the scaling of item requirement. This should make these quests completable even in late game.
- Slightly decreased the reward for gui island crafting quest to accommodate the change above.
- Reduced amount of items needed for island market quests.
2023-02-13 22:41:00 +02:00

450 lines
16 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 <= 50 then
Public.initialise_fish_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(
1 + 9 * Math.slopefromto(count, 0, 20) + 10 * Math.slopefromto(count, 20, 70)
)
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 >= 5 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