mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2025-01-10 00:43:27 +02:00
de5620b7eb
Changes: - Fixed an issue that would sometimes cause poles between surfaces not connect due to connection limit. Current solution is not ideal as power loss can still happen (since some poles will be forcefully disconnected), but poles between surfaces are now guaranteed to be connected.
1673 lines
52 KiB
Lua
1673 lines
52 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 Math = require 'maps.pirates.math'
|
|
local Raffle = require 'maps.pirates.raffle'
|
|
local Server = require 'utils.server'
|
|
local Utils = require 'maps.pirates.utils_local'
|
|
local CoreData = require 'maps.pirates.coredata'
|
|
local Memory = require 'maps.pirates.memory'
|
|
local _inspect = require 'utils.inspect'.inspect
|
|
|
|
local LootRaffle = require 'functions.loot_raffle'
|
|
-- local simplex_noise = require 'utils.simplex_noise'.d2
|
|
-- local perlin_noise = require 'utils.perlin_noise'
|
|
-- local Force_health_booster = require 'modules.force_health_booster'
|
|
|
|
-- == Common variables and functions used throughout pirate ship files
|
|
|
|
|
|
local Public = {}
|
|
|
|
-- Public.active_crews_cap = 1
|
|
Public.activeCrewsCap = 2
|
|
Public.private_run_cap = 1
|
|
Public.minimumCapacitySliderValue = 1
|
|
Public.minimum_run_capacity_to_enforce_space_for = 22
|
|
-- auto-disbanding when there are no players left in the crew:
|
|
Public.autodisband_ticks = nil
|
|
-- Public.autodisband_ticks = 30*60*60
|
|
-- Public.autodisband_ticks = 30 --the reason this is low is because the comfy server runs very slowly when no-one is on it
|
|
|
|
Public.boat_steps_at_a_time = 1
|
|
|
|
Public.seconds_after_landing_to_enable_AI = 45
|
|
|
|
Public.boat_default_starting_distance_from_shore = 22
|
|
-- Public.mapedge_distance_from_boat_starting_position = 136
|
|
Public.mapedge_distance_from_boat_starting_position = 272 -- to accommodate horseshoe
|
|
Public.deepwater_distance_from_leftmost_shore = 32
|
|
Public.lobby_spawnpoint = {x = -72, y = -8}
|
|
Public.structure_ensure_chunk_radius = 2
|
|
|
|
Public.allow_barreling_off_ship = true
|
|
|
|
Public.coin_tax_percentage = 10
|
|
|
|
Public.fraction_of_map_loaded_at_sea = 1
|
|
Public.map_loading_ticks_atsea = 68 * 60
|
|
Public.map_loading_ticks_atsea_maze = 80 * 60
|
|
Public.map_loading_ticks_atsea_dock = 20 * 60
|
|
Public.map_loading_ticks_onisland = 2 * 60 * 60
|
|
Public.loading_interval = 5
|
|
|
|
Public.first_cost_to_leave_macrox = 7
|
|
|
|
Public.minimum_ore_placed_per_tile = 10
|
|
|
|
Public.maze_minimap_jam_league = 960
|
|
|
|
Public.ban_from_rejoining_crew_ticks = 45 * 60 --to prevent observing map and rejoining
|
|
|
|
Public.afk_time = 60 * 60 * 5
|
|
Public.afk_warning_time = 60 * 60 * 4.5
|
|
Public.logged_off_items_preserved_minutes = 5
|
|
Public.logout_unprotected_items = {'uranium-235', 'uranium-238', 'fluid-wagon', 'coal', 'electric-engine-unit', 'flying-robot-frame', 'advanced-circuit', 'beacon', 'speed-module-3', 'speed-module-2', 'roboport', 'construction-robot'} --internal inventories of these will not be preserved
|
|
|
|
-- Public.mainshop_rate_limit_ticks = 11
|
|
|
|
|
|
function Public.ore_real_to_abstract(amount)
|
|
return amount/1800
|
|
end
|
|
function Public.ore_abstract_to_real(amount)
|
|
return Math.ceil(amount*1800)
|
|
end
|
|
|
|
-- 3000 oil resource is '1% yield'
|
|
function Public.oil_real_to_abstract(amount)
|
|
return amount/(3000)
|
|
end
|
|
function Public.oil_abstract_to_real(amount)
|
|
return Math.ceil(amount*3000)
|
|
end
|
|
|
|
function Public.difficulty_scale()
|
|
local memory = Memory.get_crew_memory()
|
|
if memory.overworldx > 0 then
|
|
return memory.difficulty
|
|
else
|
|
return 0.75
|
|
end
|
|
end
|
|
function Public.capacity() return Memory.get_crew_memory().capacity end
|
|
-- function Public.mode() return Memory.get_crew_memory().mode end
|
|
function Public.overworldx() return Memory.get_crew_memory().overworldx end
|
|
function Public.game_completion_progress() return Public.overworldx()/CoreData.victory_x end
|
|
function Public.game_completion_progress_capped() return Math.clamp(0, 1, Public.overworldx()/CoreData.victory_x) end
|
|
function Public.capacity_scale()
|
|
local capacity = Public.capacity()
|
|
if not capacity then --e.g. for EE wattage on boats not owned by a crew
|
|
return 1
|
|
elseif capacity <= 1 then
|
|
return 0.5
|
|
elseif capacity <= 4 then
|
|
return 0.75
|
|
elseif capacity <= 8 then
|
|
return 1
|
|
elseif capacity <= 16 then
|
|
return 1.3
|
|
else
|
|
return 1.5
|
|
end
|
|
end
|
|
|
|
function Public.activecrewcount()
|
|
local global_memory = Memory.get_global_memory()
|
|
local memory = Memory.get_crew_memory()
|
|
if not Public.is_id_valid(memory.id) then return 0 end
|
|
|
|
local count = 0
|
|
for _, id in pairs(memory.crewplayerindices) do
|
|
local player = game.players[id]
|
|
if player and player.valid and not Utils.contains(global_memory.afk_player_indices, player.index) then
|
|
count = count + 1
|
|
end
|
|
end
|
|
|
|
return count
|
|
end
|
|
|
|
|
|
function Public.notify_game(message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_game
|
|
game.print({"", '>> ', message}, color_override)
|
|
end
|
|
|
|
function Public.notify_lobby(message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_lobby
|
|
game.forces['player'].print({"", '>> ', message}, color_override)
|
|
end
|
|
|
|
function Public.notify_force(force, message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_force
|
|
force.print({"", '>> ', message}, color_override)
|
|
end
|
|
|
|
function Public.notify_force_light(force, message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_force_light
|
|
force.print({"", '>> ', message}, color_override)
|
|
end
|
|
|
|
function Public.notify_force_error(force, message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_error
|
|
force.print({"", '>> ', message}, color_override)
|
|
force.play_sound{path = "utility/cannot_build"}
|
|
end
|
|
|
|
function Public.notify_player_error(player, message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_error
|
|
player.print({"", '## ', {'pirates.notify_whisper'}, ' ', message}, color_override)
|
|
player.play_sound{path = "utility/cannot_build"}
|
|
end
|
|
|
|
function Public.notify_player_expected(player, message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_player_expected
|
|
player.print({"", '## ', {'pirates.notify_whisper'}, ' ', message}, color_override)
|
|
end
|
|
|
|
function Public.notify_player_announce(player, message, color_override)
|
|
color_override = color_override or CoreData.colors.notify_player_announce
|
|
player.print({"", '## ', {'pirates.notify_whisper'}, ' ', message}, color_override)
|
|
end
|
|
|
|
function Public.parrot_speak(force, message)
|
|
force.print({"", {'pirates.notify_parrot'}, ' ', message}, CoreData.colors.parrot)
|
|
|
|
local memory = Memory.get_crew_memory()
|
|
Server.to_discord_embed_raw({"", '[' .. memory.name .. '] ', {'pirates.notify_parrot'}, ' ', message}, true)
|
|
end
|
|
|
|
|
|
function Public.flying_text(surface, position, text)
|
|
surface.create_entity(
|
|
{
|
|
name = 'flying-text',
|
|
position = {position.x - 0.7, position.y - 3.05},
|
|
text = text,
|
|
}
|
|
)
|
|
end
|
|
|
|
|
|
function Public.flying_text_small(surface, position, text) --differs just in the location of the text, more suitable for small things like '+'
|
|
surface.create_entity(
|
|
{
|
|
name = 'flying-text',
|
|
position = {position.x - 0.08, position.y - 1.5},
|
|
-- position = {position.x - 0.06, position.y - 1.5},
|
|
text = text,
|
|
}
|
|
)
|
|
end
|
|
|
|
|
|
|
|
function Public.processed_loot_data(raw_data)
|
|
local ret = {}
|
|
for i = 1, #raw_data do
|
|
local loot_data_item = raw_data[i]
|
|
ret[#ret + 1] = {
|
|
weight = loot_data_item[1],
|
|
game_completion_progress_min = loot_data_item[2],
|
|
game_completion_progress_max = loot_data_item[3],
|
|
scaling = loot_data_item[4],
|
|
name = loot_data_item[5],
|
|
min_count = loot_data_item[6],
|
|
max_count = loot_data_item[7],
|
|
map_subtype = loot_data_item[8]
|
|
}
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--=='raw' data is in the form e.g.
|
|
-- {
|
|
-- {100, 0, 1, false, 'steel-plate', 140, 180},
|
|
-- {50, 0, 1, false, 'defender-capsule', 15, 25},
|
|
-- {20, 0, 1, false, 'flying-robot-frame', 20, 35},
|
|
-- }
|
|
|
|
|
|
|
|
--@TODO: Replace this old function with the newer code in raffle.lua
|
|
function Public.raffle_from_processed_loot_data(processed_loot_data, how_many, game_completion_progress)
|
|
local ret = {}
|
|
|
|
local loot_types, loot_weights = {}, {}
|
|
for i = 1, #processed_loot_data, 1 do
|
|
local data = processed_loot_data[i]
|
|
table.insert(loot_types, {['name'] = data.name, ['min_count'] = data.min_count, ['max_count'] = data.max_count})
|
|
|
|
local destination = Public.current_destination()
|
|
if not (destination and destination.subtype and data.map_subtype and data.map_subtype == destination.subtype) then
|
|
if data.scaling then -- scale down weights away from the midpoint 'peak' (without changing the mean)
|
|
local midpoint = (data.game_completion_progress_max + data.game_completion_progress_min) / 2
|
|
local difference = (data.game_completion_progress_max - data.game_completion_progress_min)
|
|
local w = 2 * data.weight * Math.max(0, 1 - (Math.abs(game_completion_progress - midpoint) / (difference / 2)))
|
|
table.insert(loot_weights, w)
|
|
else -- no scaling
|
|
if data.game_completion_progress_min <= game_completion_progress and data.game_completion_progress_max >= game_completion_progress then
|
|
table.insert(loot_weights, data.weight)
|
|
else
|
|
table.insert(loot_weights, 0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _ = 1, how_many do
|
|
local loot = Raffle.raffle(loot_types, loot_weights)
|
|
if loot then
|
|
local low = Math.max(1, Math.ceil(loot.min_count))
|
|
local high = Math.max(1, Math.ceil(loot.max_count))
|
|
local _count = Math.random(low, high)
|
|
local lucky = Math.random(1, 220)
|
|
if lucky == 1 then --lucky
|
|
_count = _count * 3
|
|
elseif lucky <= 12 then
|
|
_count = _count * 2
|
|
end
|
|
ret[#ret + 1] = {name = loot.name, count = _count}
|
|
end
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
function Public.give(player, stacks, spill_position, short_form, spill_surface, flying_text_position)
|
|
-- stack elements of form {name = '', count = '', color = {r = , g = , b = }}
|
|
-- to just spill on the ground, pass player and nill and give a position and surface directly
|
|
spill_position = spill_position or player.position
|
|
spill_surface = spill_surface or player.surface
|
|
flying_text_position = flying_text_position or spill_position
|
|
|
|
local text1 = ''
|
|
local text2 = ''
|
|
|
|
local stacks2 = stacks
|
|
table.sort(stacks2, function(a,b) return a.name < b.name end)
|
|
|
|
if not (spill_surface and spill_surface.valid) then return end
|
|
local inv
|
|
|
|
if player then
|
|
inv = player.get_inventory(defines.inventory.character_main)
|
|
if not (inv and inv.valid) then return end
|
|
end
|
|
|
|
for j = 1, #stacks2 do
|
|
local stack = stacks2[j]
|
|
local itemname, itemcount, flying_text_color = stack.name, stack.count or 1, stack.color or (CoreData.colors[stack.name] or {r = 1, g = 1, b = 1})
|
|
local itemcount_remember = itemcount
|
|
|
|
if not itemname then return end
|
|
|
|
if itemcount > 0 then
|
|
if player then
|
|
local a = inv.insert{name = itemname, count = itemcount}
|
|
itemcount = itemcount - a
|
|
if itemcount >= 50 then
|
|
for i = 1, Math.floor(itemcount / 50), 1 do
|
|
local e = spill_surface.create_entity{name = 'item-on-ground', position = spill_position, stack = {name = itemname, count = 50}}
|
|
if e and e.valid then
|
|
e.to_be_looted = true
|
|
end
|
|
itemcount = itemcount - 50
|
|
end
|
|
end
|
|
if itemcount > 0 then
|
|
-- if itemcount < 5 then
|
|
-- spill_surface.spill_item_stack(spill_position, {name = itemname, count = itemcount}, true)
|
|
-- else
|
|
-- local e = spill_surface.create_entity{name = 'item-on-ground', position = spill_position, stack = {name = itemname, count = itemcount}}
|
|
-- if e and e.valid then
|
|
-- e.to_be_looted = true
|
|
-- end
|
|
-- end
|
|
spill_surface.spill_item_stack(spill_position, {name = itemname, count = itemcount}, true)
|
|
end
|
|
else
|
|
-- local e = spill_surface.create_entity{name = 'item-on-ground', position = spill_position, stack = {name = itemname, count = itemcount}}
|
|
-- if e and e.valid then
|
|
-- e.to_be_looted = true
|
|
-- end
|
|
spill_surface.spill_item_stack(spill_position, {name = itemname, count = itemcount}, true)
|
|
end
|
|
end
|
|
|
|
if itemcount_remember >= 0 then
|
|
if short_form then
|
|
text1 = text1 .. '[color=' .. flying_text_color.r .. ',' .. flying_text_color.g .. ',' .. flying_text_color.b .. ']' .. '+' .. itemcount_remember .. '[/color]'
|
|
else
|
|
text1 = text1 .. '[color=1,1,1]'
|
|
text1 = text1 .. '+'
|
|
text1 = text1 .. itemcount_remember .. '[/color] [item=' .. itemname .. ']'
|
|
end
|
|
else
|
|
if short_form then
|
|
text1 = text1 .. '[color=' .. flying_text_color.r .. ',' .. flying_text_color.g .. ',' .. flying_text_color.b .. ']' .. '-' .. -itemcount_remember .. '[/color]'
|
|
else
|
|
text1 = text1 .. '[color=1,1,1]'
|
|
text1 = text1 .. '-'
|
|
text1 = text1 .. -itemcount_remember .. '[/color] [item=' .. itemname .. ']'
|
|
end
|
|
end
|
|
|
|
|
|
if player and (not short_form) then
|
|
-- count total of that item they have:
|
|
local new_total_count = 0
|
|
|
|
local cursor_stack = player.cursor_stack
|
|
if cursor_stack and cursor_stack.valid_for_read and cursor_stack.name == itemname and cursor_stack.count and cursor_stack.count > 0 then
|
|
new_total_count = new_total_count + cursor_stack.count
|
|
end
|
|
if inv and inv.get_item_count(itemname) and inv.get_item_count(itemname) > 0 then
|
|
new_total_count = new_total_count + inv.get_item_count(itemname)
|
|
end
|
|
|
|
if #stacks2 > 1 then
|
|
text2 = text2 .. '[color=' .. flying_text_color.r .. ',' .. flying_text_color.g .. ',' .. flying_text_color.b .. ']' .. new_total_count .. '[/color]'
|
|
else
|
|
text2 = '[color=' .. flying_text_color.r .. ',' .. flying_text_color.g .. ',' .. flying_text_color.b .. '](' .. new_total_count .. ')[/color]'
|
|
end
|
|
if j < #stacks2 then
|
|
text2 = text2 .. ', '
|
|
end
|
|
end
|
|
|
|
if j < #stacks2 then
|
|
text1 = text1 .. ', '
|
|
end
|
|
end
|
|
|
|
if text2 ~= '' then
|
|
if #stacks2 > 1 then
|
|
text2 = '(' .. text2 .. ')'
|
|
end
|
|
Public.flying_text(spill_surface, flying_text_position, text1 .. ' [font=count-font]' .. text2 .. '[/font]')
|
|
else
|
|
Public.flying_text(spill_surface, flying_text_position, text1)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function Public.is_captain(player)
|
|
local memory = Memory.get_crew_memory()
|
|
|
|
if memory.playerindex_captain and memory.playerindex_captain == player.index then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
function Public.is_officer(player_index)
|
|
local memory = Memory.get_crew_memory()
|
|
|
|
if memory.officers_table and memory.officers_table[player_index] then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- lifted shamelessly from biter battles, since I haven't done balancing work on this:
|
|
function Public.surplus_evo_biter_damage_modifier(surplus_evo)
|
|
return Math.floor(surplus_evo/2*1000)/1000 --is this floor needed?
|
|
end
|
|
-- function Public.surplus_evo_biter_health_fractional_modifier(surplus_evo)
|
|
-- return surplus_evo*3
|
|
-- -- return Math.floor(surplus_evo*3*1000)/1000
|
|
-- end
|
|
|
|
|
|
function Public.set_biter_surplus_evo_modifiers()
|
|
local memory = Memory.get_crew_memory()
|
|
local enemy_force = memory.enemy_force
|
|
|
|
if not (memory.evolution_factor and enemy_force and enemy_force.valid) then
|
|
return nil
|
|
end
|
|
local surplus = memory.evolution_factor - 1
|
|
|
|
local damage_fractional_mod
|
|
-- local health_fractional_mod
|
|
|
|
if surplus > 0 then
|
|
damage_fractional_mod = Public.surplus_evo_biter_damage_modifier(surplus)
|
|
-- health_fractional_mod = Public.surplus_evo_biter_health_fractional_modifier(surplus)
|
|
else
|
|
damage_fractional_mod = 0
|
|
-- health_fractional_mod = 0
|
|
end
|
|
enemy_force.set_ammo_damage_modifier('melee', damage_fractional_mod)
|
|
enemy_force.set_ammo_damage_modifier('biological', damage_fractional_mod)
|
|
enemy_force.set_ammo_damage_modifier('artillery-shell', damage_fractional_mod)
|
|
enemy_force.set_ammo_damage_modifier('flamethrower', damage_fractional_mod)
|
|
|
|
-- this event is behaving really weirdly, e.g. messing up samurai damage:
|
|
-- Force_health_booster.set_health_modifier(enemy_force.index, 1 + health_fractional_mod)
|
|
end
|
|
|
|
function Public.set_evo(evolution)
|
|
local memory = Memory.get_crew_memory()
|
|
memory.evolution_factor = evolution
|
|
if memory.enemy_force_name then
|
|
local ef = memory.enemy_force
|
|
if ef and ef.valid then
|
|
ef.evolution_factor = memory.evolution_factor
|
|
Public.set_biter_surplus_evo_modifiers()
|
|
end
|
|
end
|
|
end
|
|
|
|
function Public.increment_evo(evolution)
|
|
local memory = Memory.get_crew_memory()
|
|
memory.evolution_factor = memory.evolution_factor + evolution
|
|
if memory.enemy_force_name then
|
|
local ef = memory.enemy_force
|
|
if ef and ef.valid then
|
|
ef.evolution_factor = memory.evolution_factor
|
|
Public.set_biter_surplus_evo_modifiers()
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function Public.current_destination()
|
|
local memory = Memory.get_crew_memory()
|
|
|
|
if memory.currentdestination_index then
|
|
return memory.destinations[memory.currentdestination_index]
|
|
else
|
|
return CoreData.fallthrough_destination
|
|
end
|
|
end
|
|
|
|
|
|
function Public.time_adjusted_departure_cost(cost)
|
|
local memory = Memory.get_crew_memory()
|
|
|
|
local ret = cost
|
|
|
|
-- 1.5s memoization since the gui update will call this function:
|
|
if (not memory.time_adjusted_departure_cost_memoized) or (memory.time_adjusted_departure_cost_memoized.tick < game.tick - 90) then
|
|
local destination = Public.current_destination()
|
|
local dynamic_data = destination.dynamic_data
|
|
local timer = dynamic_data.timer
|
|
local time_remaining = dynamic_data.time_remaining
|
|
|
|
if timer and time_remaining and timer >= 0 and time_remaining >= 0 then
|
|
local total_time = timer + time_remaining
|
|
local elapsed_fraction = timer / total_time
|
|
local cost_fraction = 1 - elapsed_fraction
|
|
|
|
local new_cost = {}
|
|
for name, count in pairs(cost) do
|
|
if type(count) == "number" then
|
|
new_cost[name] = Math.floor(count * cost_fraction)
|
|
-- new_cost[name] = Math.ceil(count * cost_fraction)
|
|
else
|
|
new_cost[name] = count
|
|
end
|
|
end
|
|
|
|
ret = new_cost
|
|
end
|
|
|
|
local resources_strings1 = ''
|
|
local j = 1
|
|
for name, count in pairs(cost) do
|
|
if name ~= 'launch_rocket' then
|
|
if j > 1 then
|
|
resources_strings1 = resources_strings1 .. ', '
|
|
end
|
|
resources_strings1 = resources_strings1 .. count .. ' [item=' .. name .. ']'
|
|
|
|
j = j + 1
|
|
end
|
|
end
|
|
local resources_strings2 = ''
|
|
j = 1
|
|
for name, count in pairs(ret) do
|
|
if name ~= 'launch_rocket' then
|
|
if j > 1 then
|
|
resources_strings2 = resources_strings2 .. ', '
|
|
end
|
|
resources_strings2 = resources_strings2 .. count .. ' [item=' .. name .. ']'
|
|
|
|
j = j + 1
|
|
end
|
|
end
|
|
|
|
memory.time_adjusted_departure_cost_memoized = {
|
|
tick = game.tick,
|
|
cost = ret,
|
|
resources_strings = {resources_strings1, resources_strings2}
|
|
}
|
|
else
|
|
ret = memory.time_adjusted_departure_cost_memoized.cost
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
|
|
function Public.time_adjusted_departure_cost_resources_strings(memory)
|
|
-- written to be efficient... only called in the gui after Public.time_adjusted_departure_cost()
|
|
|
|
return memory.time_adjusted_departure_cost_memoized.resources_strings
|
|
end
|
|
|
|
|
|
function Public.query_can_pay_cost_to_leave()
|
|
local memory = Memory.get_crew_memory()
|
|
local boat = memory.boat
|
|
local destination = Public.current_destination()
|
|
if not (boat and destination) then return end
|
|
|
|
local cost = destination.static_params.base_cost_to_undock
|
|
if not cost then return true end
|
|
|
|
local adjusted_cost = Public.time_adjusted_departure_cost(cost)
|
|
|
|
local can_leave = true
|
|
for name, count in pairs(adjusted_cost) do
|
|
if name == 'launch_rocket' then
|
|
if not destination.dynamic_data.rocketlaunched then
|
|
can_leave = false
|
|
end
|
|
else
|
|
local stored = (memory.boat.stored_resources and memory.boat.stored_resources[name]) or 0
|
|
if stored < count then
|
|
can_leave = false
|
|
end
|
|
end
|
|
end
|
|
|
|
return can_leave
|
|
end
|
|
|
|
|
|
|
|
-- This function assumes you're placing obstacle boxes in the hold
|
|
function Public.surface_place_random_obstacle_boxes(surface, center, width, height, spacing_entity, box_size_table, contents)
|
|
contents = contents or {}
|
|
|
|
local memory = Memory.get_crew_memory()
|
|
if not surface then return end
|
|
|
|
local function boxposition()
|
|
local p1 = {x = center.x - width/2 + Math.random(Math.ceil(width)), y = center.y - height/2 + Math.random(Math.ceil(height))}
|
|
local p2 = surface.find_non_colliding_position(spacing_entity, p1, 32, 2, true) or p1
|
|
return {x = p2.x, y = p2.y}
|
|
end
|
|
|
|
local placed = 0
|
|
for size, count in pairs(box_size_table) do
|
|
if count >= 1 then
|
|
for i = 1, count do
|
|
placed = placed + 1
|
|
local p = boxposition()
|
|
for j = 1, size^2 do
|
|
local p2 = surface.find_non_colliding_position('wooden-chest', p, 5, 0.1, true) or p
|
|
local e = surface.create_entity{name = 'wooden-chest', position = p2, force = memory.force_name, create_build_effect_smoke = false}
|
|
memory.hold_surface_destroyable_wooden_chests[e.unit_number] = e
|
|
e.destructible = false
|
|
e.minable = false
|
|
e.rotatable = false
|
|
if contents[placed] and j==1 then
|
|
local inventory = e.get_inventory(defines.inventory.chest)
|
|
for name, count2 in pairs(contents[placed]) do
|
|
inventory.insert{name = name, count = count2}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function Public.update_boat_stored_resources()
|
|
local memory = Memory.get_crew_memory()
|
|
local boat = memory.boat
|
|
if not boat.stored_resources then return end
|
|
local input_chests = boat.input_chests
|
|
|
|
if not input_chests then return end
|
|
|
|
for i, chest in ipairs(input_chests) do
|
|
if i>1 and CoreData.cost_items[i-1] then
|
|
local inv = chest.get_inventory(defines.inventory.chest)
|
|
local contents = inv.get_contents()
|
|
|
|
local item_type = CoreData.cost_items[i-1].name
|
|
local count = contents[item_type] or 0
|
|
|
|
boat.stored_resources[item_type] = count
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function Public.spend_stored_resources(to_spend)
|
|
to_spend = to_spend or {}
|
|
local memory = Memory.get_crew_memory()
|
|
local boat = memory.boat
|
|
if not memory.boat.stored_resources then return end
|
|
local input_chests = boat.input_chests
|
|
|
|
if not input_chests then return end
|
|
|
|
for i, chest in ipairs(input_chests) do
|
|
if i>1 then
|
|
local inv = chest.get_inventory(defines.inventory.chest)
|
|
local item_type = CoreData.cost_items[i-1].name
|
|
local to_spend_i = to_spend[item_type] or 0
|
|
|
|
if to_spend_i > 0 then
|
|
inv.remove{name = item_type, count = to_spend_i}
|
|
end
|
|
end
|
|
end
|
|
|
|
Public.update_boat_stored_resources()
|
|
end
|
|
|
|
|
|
function Public.new_healthbar(text, target_entity, max_health, optional_id, health, size, extra_offset, location_override)
|
|
health = health or max_health
|
|
size = size or 0.5
|
|
text = text or false
|
|
extra_offset = extra_offset or 0
|
|
location_override = location_override or Memory.get_crew_memory()
|
|
|
|
local render1 = rendering.draw_sprite(
|
|
{
|
|
sprite = 'virtual-signal/signal-white',
|
|
tint = {0, 200, 0},
|
|
x_scale = size * 15,
|
|
y_scale = size,
|
|
render_layer = 'light-effect',
|
|
target = target_entity,
|
|
target_offset = {0, -2.5 + extra_offset},
|
|
surface = target_entity.surface,
|
|
}
|
|
)
|
|
local render2
|
|
if text then
|
|
render2 = rendering.draw_text(
|
|
{
|
|
color = {255, 255, 255},
|
|
scale = 1.2 + size*2,
|
|
render_layer = 'light-effect',
|
|
target = target_entity,
|
|
target_offset = {0, -3.6 - size*0.6 + extra_offset},
|
|
surface = target_entity.surface,
|
|
alignment = 'center',
|
|
}
|
|
)
|
|
end
|
|
|
|
local new_healthbar = {
|
|
health = health,
|
|
max_health = max_health,
|
|
size = size,
|
|
extra_offset = extra_offset,
|
|
render1 = render1,
|
|
render2 = render2,
|
|
id = optional_id,
|
|
}
|
|
|
|
if not location_override.healthbars then location_override.healthbars = {} end
|
|
location_override.healthbars[target_entity.unit_number] = new_healthbar
|
|
|
|
Public.update_healthbar_rendering(new_healthbar, health)
|
|
|
|
return new_healthbar
|
|
end
|
|
|
|
function Public.transfer_healthbar(old_unit_number, new_entity, location_override)
|
|
location_override = location_override or Memory.get_crew_memory()
|
|
if not location_override.healthbars then return end
|
|
local old_healthbar = location_override.healthbars[old_unit_number]
|
|
-- local new_unit_number = new_entity.unit_number
|
|
|
|
-- if new_surface_bool then
|
|
-- Public.new_healthbar(old_healthbar.render2, new_entity, old_healthbar.max_health, old_healthbar.id, old_healthbar.health, rendering.get_y_scale(old_healthbar.render1))
|
|
-- else
|
|
-- rendering.set_target(old_healthbar.render1, new_entity)
|
|
-- if old_healthbar.render2 then
|
|
-- rendering.set_target(old_healthbar.render2, new_entity)
|
|
-- end
|
|
-- memory.healthbars[new_unit_number] = old_healthbar
|
|
-- end
|
|
|
|
Public.new_healthbar(old_healthbar.render2, new_entity, old_healthbar.max_health, old_healthbar.id, old_healthbar.health, old_healthbar.size, old_healthbar.extra_offset, location_override)
|
|
|
|
if rendering.is_valid(old_healthbar.render1) then
|
|
rendering.destroy(old_healthbar.render1)
|
|
end
|
|
if rendering.is_valid(old_healthbar.render2) then
|
|
rendering.destroy(old_healthbar.render2)
|
|
end
|
|
|
|
location_override.healthbars[old_unit_number] = nil
|
|
end
|
|
|
|
function Public.entity_damage_healthbar(entity, damage, location_override)
|
|
location_override = location_override or Memory.get_crew_memory()
|
|
local unit_number = entity.unit_number
|
|
if not (location_override.healthbars) then return end
|
|
|
|
local healthbar = location_override.healthbars[unit_number]
|
|
if not healthbar then return 0 end
|
|
|
|
local new_health = healthbar.health - damage
|
|
healthbar.health = new_health
|
|
Public.update_healthbar_rendering(healthbar, new_health)
|
|
|
|
if entity and entity.valid then
|
|
entity.health = entity.prototype.max_health
|
|
end
|
|
|
|
return healthbar.health
|
|
end
|
|
|
|
function Public.update_healthbar_rendering(new_healthbar, health)
|
|
local max_health = new_healthbar.max_health
|
|
local render1 = new_healthbar.render1
|
|
local render2 = new_healthbar.render2
|
|
|
|
if health > 0 then
|
|
local m = health / max_health
|
|
local x_scale = rendering.get_y_scale(render1) * 15
|
|
rendering.set_x_scale(render1, x_scale * m)
|
|
rendering.set_color(render1, {Math.floor(255 - 255 * m), Math.floor(200 * m), 0})
|
|
|
|
if render2 then
|
|
rendering.set_text(render2, string.format('HP: %d/%d',Math.ceil(health),Math.ceil(max_health)))
|
|
end
|
|
else
|
|
rendering.destroy(render1)
|
|
if render2 then
|
|
rendering.destroy(render2)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Public.spawner_count(surface)
|
|
local memory = Memory.get_crew_memory()
|
|
|
|
local spawners = surface.find_entities_filtered({type = 'unit-spawner', force = memory.enemy_force_name})
|
|
return #spawners or 0
|
|
end
|
|
|
|
|
|
|
|
function Public.create_poison_clouds(surface, position)
|
|
|
|
local random_angles = {Math.rad(Math.random(359)), Math.rad(Math.random(359))}
|
|
|
|
surface.create_entity({name = 'poison-cloud', position = {x = position.x, y = position.y}})
|
|
surface.create_entity({name = 'poison-cloud', position = {x = position.x + 12 * Math.cos(random_angles[1]), y = position.y + 12 * Math.sin(random_angles[1])}})
|
|
surface.create_entity({name = 'poison-cloud', position = {x = position.x + 12 * Math.cos(random_angles[2]), y = position.y + 12 * Math.sin(random_angles[2])}})
|
|
end
|
|
|
|
|
|
function Public.crew_get_crew_members()
|
|
local memory = Memory.get_crew_memory()
|
|
if not Public.is_id_valid(memory.id) then return {} end
|
|
|
|
local playerlist = {}
|
|
for _, id in pairs(memory.crewplayerindices) do
|
|
local player = game.players[id]
|
|
if player and player.valid then playerlist[#playerlist + 1] = player end
|
|
end
|
|
return playerlist
|
|
end
|
|
|
|
|
|
function Public.crew_get_crew_members_and_spectators()
|
|
local memory = Memory.get_crew_memory()
|
|
if not Public.is_id_valid(memory.id) then return {} end
|
|
|
|
local playerlist = {}
|
|
for _, id in pairs(memory.crewplayerindices) do
|
|
local player = game.players[id]
|
|
if player and player.valid then playerlist[#playerlist + 1] = player end
|
|
end
|
|
for _, id in pairs(memory.spectatorplayerindices) do
|
|
local player = game.players[id]
|
|
if player and player.valid then playerlist[#playerlist + 1] = player end
|
|
end
|
|
return playerlist
|
|
end
|
|
|
|
|
|
|
|
|
|
function Public.crew_get_nonafk_crew_members()
|
|
local global_memory = Memory.get_global_memory()
|
|
local memory = Memory.get_crew_memory()
|
|
if not Public.is_id_valid(memory.id) then return {} end
|
|
|
|
local playerlist = {}
|
|
for _, id in pairs(memory.crewplayerindices) do
|
|
local player = game.players[id]
|
|
if player and player.valid and not Utils.contains(global_memory.afk_player_indices, player.index) then
|
|
playerlist[#playerlist + 1] = player
|
|
end
|
|
end
|
|
|
|
return playerlist
|
|
end
|
|
|
|
|
|
function Public.destroy_decoratives_in_area(surface, area, offset)
|
|
local area2 = {{area[1][1] + offset.x, area[1][2] + offset.y}, {area[2][1] + offset.x, area[2][2] + offset.y}}
|
|
|
|
surface.destroy_decoratives{area = area2}
|
|
end
|
|
|
|
function Public.can_place_silo_setup(surface, p, points_to_avoid, silo_count, generous, build_check_type_name)
|
|
|
|
-- game.print('checking silo pos: ' .. p.x .. ', ' .. p.y)
|
|
|
|
points_to_avoid = points_to_avoid or {}
|
|
|
|
Public.ensure_chunks_at(surface, p, 0.2)
|
|
|
|
build_check_type_name = build_check_type_name or 'manual'
|
|
local build_check_type = defines.build_check_type[build_check_type_name]
|
|
local s = true
|
|
local allowed = true
|
|
for i=1,silo_count do
|
|
local pos = {x = p.x + 9 * (i-1), y = p.y}
|
|
s = (surface.can_place_entity{name = 'rocket-silo', position = pos, build_check_type = build_check_type} or (generous and i>2)) and s
|
|
|
|
for _, pa in pairs(points_to_avoid) do
|
|
if Math.distance({x = pa.x, y = pa.y}, pos) < pa.r then
|
|
allowed = false
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return s and allowed
|
|
end
|
|
|
|
function Public.ensure_chunks_at(surface, pos, radius) --WARNING: THIS DOES NOT PLAY NICELY WITH DELAYED TASKS. log(_inspect{global_memory.working_id}) was observed to vary before and after this function.
|
|
-- local global_memory = Memory.get_global_memory()
|
|
if surface and surface.valid then
|
|
surface.request_to_generate_chunks(pos, radius)
|
|
surface.force_generate_chunk_requests() --WARNING: THIS DOES NOT PLAY NICELY WITH DELAYED TASKS. log(_inspect{global_memory.working_id}) was observed to vary before and after this function.
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
function Public.default_map_gen_settings(width, height, seed)
|
|
width = width or 512
|
|
height = height or 512
|
|
seed = seed or Math.random(1, 1000000)
|
|
|
|
local map_gen_settings = {
|
|
['seed'] = seed,
|
|
['width'] = width,
|
|
['height'] = height,
|
|
['water'] = 0,
|
|
--FIXME: Back when this was at x=2000, a crash was caused once by a player spawning at x=2000. So there will be a crash in future under unknown circumstances if there is no space at x=0,y=0.
|
|
['starting_points'] = {{x = 0, y = 0}},
|
|
['cliff_settings'] = {cliff_elevation_interval = 0, cliff_elevation_0 = 0},
|
|
['default_enable_all_autoplace_controls'] = true,
|
|
['autoplace_settings'] = {
|
|
['entity'] = {treat_missing_as_default = false},
|
|
['tile'] = {treat_missing_as_default = true},
|
|
['decorative'] = {treat_missing_as_default = true},
|
|
},
|
|
['property_expression_names'] = {},
|
|
}
|
|
|
|
return map_gen_settings
|
|
end
|
|
|
|
|
|
function Public.build_from_blueprint(bp_string, surface, pos, force, flipped)
|
|
flipped = flipped or false
|
|
|
|
local bp_entity = game.surfaces['nauvis'].create_entity{name = 'item-on-ground', position = {x = 158.5, y = 158.5}, stack = 'blueprint'}
|
|
bp_entity.stack.import_stack(bp_string)
|
|
|
|
local direction = flipped and defines.direction.south or defines.direction.north
|
|
|
|
local entities = bp_entity.stack.build_blueprint{surface = surface, force = force, position = {x = pos.x, y = pos.y}, force_build = true, skip_fog_of_war = false, direction = direction}
|
|
|
|
bp_entity.destroy()
|
|
|
|
local rev_entities = {}
|
|
for _, e in pairs(entities) do
|
|
if e and e.valid then
|
|
local _collisions, revived_entity = e.silent_revive()
|
|
rev_entities[#rev_entities + 1] = revived_entity
|
|
end
|
|
end
|
|
|
|
-- once again, to revive wagons:
|
|
for _, e in pairs(entities) do
|
|
if e and e.valid and e.type and e.type == 'entity-ghost' then
|
|
local _collisions, revived_entity = e.silent_revive()
|
|
rev_entities[#rev_entities + 1] = revived_entity
|
|
|
|
if revived_entity and revived_entity.valid and revived_entity.name == 'locomotive' then
|
|
revived_entity.color = {255, 106, 52}
|
|
revived_entity.get_inventory(defines.inventory.fuel).insert({name = 'wood', count = 16})
|
|
revived_entity.operable = false
|
|
end
|
|
end
|
|
end
|
|
|
|
return rev_entities
|
|
end
|
|
|
|
function Public.build_small_loco(surface, pos, force, color)
|
|
local p1 = {x = pos.x, y = pos.y}
|
|
local p2 = {x = pos.x, y = pos.y -2}
|
|
local p3 = {x = pos.x, y = pos.y + 2}
|
|
local es = {}
|
|
es[1] = surface.create_entity({name = 'straight-rail', position = p1, force = force, create_build_effect_smoke = false})
|
|
es[2] = surface.create_entity({name = 'straight-rail', position = p2, force = force, create_build_effect_smoke = false})
|
|
es[3] = surface.create_entity({name = 'straight-rail', position = p3, force = force, create_build_effect_smoke = false})
|
|
es[4] = surface.create_entity({name = 'locomotive', position = p1, force = force, create_build_effect_smoke = false})
|
|
for _, e in pairs(es) do
|
|
if e and e.valid then
|
|
e.destructible = false
|
|
e.minable = false
|
|
e.rotatable = false
|
|
e.operable = false
|
|
end
|
|
end
|
|
if es[4] and es[4].valid then
|
|
es[4].color = color
|
|
es[4].get_inventory(defines.inventory.fuel).insert({name = 'wood', count = 16})
|
|
end
|
|
end
|
|
|
|
function Public.add_tiles_from_blueprint(tilesTable, bp_string, tile_name, offset)
|
|
|
|
local bp_entity = game.surfaces['nauvis'].create_entity{name = 'item-on-ground', position = {x = 158.5, y = 158.5}, stack = 'blueprint'}
|
|
bp_entity.stack.import_stack(bp_string)
|
|
|
|
local bp_tiles = bp_entity.stack.get_blueprint_tiles()
|
|
|
|
if bp_tiles then
|
|
for _, tile in pairs(bp_tiles) do
|
|
tilesTable[#tilesTable + 1] = {name = tile_name, position = {x = tile.position.x + offset.x, y = tile.position.y + offset.y}}
|
|
end
|
|
end
|
|
|
|
bp_entity.destroy()
|
|
|
|
return tilesTable
|
|
end
|
|
|
|
function Public.tile_positions_from_blueprint(bp_string, offset)
|
|
-- May '22 change: There seems to be a base game bug(?) which causes the tiles to be offset. We now correct for that (with ` - (max_x - min_x)/2` and ` - (max_y - min_y)/2`).
|
|
|
|
local bp_entity = game.surfaces['nauvis'].create_entity{name = 'item-on-ground', position = {x = 158.5, y = 158.5}, stack = 'blueprint'}
|
|
bp_entity.stack.import_stack(bp_string)
|
|
|
|
local bp_tiles = bp_entity.stack.get_blueprint_tiles()
|
|
|
|
|
|
local min_x
|
|
local min_y
|
|
local max_x
|
|
local max_y
|
|
|
|
local positions = {}
|
|
if bp_tiles then
|
|
for _, tile in pairs(bp_tiles) do
|
|
positions[#positions + 1] = {x = tile.position.x, y = tile.position.y}
|
|
if not min_x or tile.position.x < min_x then
|
|
min_x = tile.position.x
|
|
end
|
|
if not min_y or tile.position.y < min_y then
|
|
min_y = tile.position.y
|
|
end
|
|
if not max_x or tile.position.x > max_x then
|
|
max_x = tile.position.x
|
|
end
|
|
if not max_y or tile.position.y > max_y then
|
|
max_y = tile.position.y
|
|
end
|
|
end
|
|
end
|
|
|
|
if min_x and min_y and max_x and max_y then
|
|
for _, pos in pairs(positions) do
|
|
pos.x = pos.x - (max_x - min_x)/2 + offset.x
|
|
pos.y = pos.y - (max_y - min_y)/2 + offset.y
|
|
end
|
|
end
|
|
|
|
bp_entity.destroy()
|
|
|
|
return positions
|
|
end
|
|
|
|
function Public.tile_positions_from_blueprint_arrayform(bp_string, offset)
|
|
-- does not include the above May '22 fix yet, so may give different results
|
|
|
|
local bp_entity = game.surfaces['nauvis'].create_entity{name = 'item-on-ground', position = {x = 158.5, y = 158.5}, stack = 'blueprint'}
|
|
bp_entity.stack.import_stack(bp_string)
|
|
|
|
local bp_tiles = bp_entity.stack.get_blueprint_tiles()
|
|
|
|
local positions = {}
|
|
if bp_tiles then
|
|
for _, tile in pairs(bp_tiles) do
|
|
local x = tile.position.x+ offset.x
|
|
local y = tile.position.y + offset.y
|
|
if not positions[x] then positions[x] = {} end
|
|
positions[x][y] = true
|
|
end
|
|
end
|
|
|
|
bp_entity.destroy()
|
|
|
|
return positions
|
|
end
|
|
|
|
function Public.entity_positions_from_blueprint(bp_string, offset)
|
|
|
|
local bp_entity = game.surfaces['nauvis'].create_entity{name = 'item-on-ground', position = {x = 158.5, y = 158.5}, stack = 'blueprint'}
|
|
bp_entity.stack.import_stack(bp_string)
|
|
|
|
local es = bp_entity.stack.get_blueprint_entities()
|
|
|
|
local positions = {}
|
|
if es then
|
|
for _, e in pairs(es) do
|
|
positions[#positions + 1] = {x = e.position.x + offset.x, y = e.position.y + offset.y}
|
|
end
|
|
end
|
|
|
|
bp_entity.destroy()
|
|
|
|
return positions
|
|
end
|
|
|
|
function Public.get_random_unit_type(evolution)
|
|
-- designed to approximate https://wiki.factorio.com/Enemies
|
|
local r = Math.random()
|
|
|
|
if Math.random(5) == 1 then
|
|
if r < 1 - 1/0.15*(evolution - 0.25) then
|
|
return 'small-biter'
|
|
elseif r < 1 - 1/0.3*(evolution - 0.4) then
|
|
return 'small-spitter'
|
|
elseif r < 1 - 0.85/0.5*(evolution - 0.5) then
|
|
return 'medium-spitter'
|
|
elseif r < 1 - 0.4/0.1*(evolution - 0.9) then
|
|
return 'big-spitter'
|
|
else
|
|
return 'behemoth-spitter'
|
|
end
|
|
else
|
|
if r < 1 - 1/0.4*(evolution - 0.2) then
|
|
return 'small-biter'
|
|
elseif r < 1 - 0.8/0.5*(evolution - 0.5) then
|
|
return 'medium-biter'
|
|
elseif r < 1 - 0.4/0.1*(evolution - 0.9) then
|
|
return 'big-biter'
|
|
else
|
|
return 'behemoth-biter'
|
|
end
|
|
end
|
|
end
|
|
|
|
function Public.get_random_biter_type(evolution)
|
|
-- designed to approximate https://wiki.factorio.com/Enemies
|
|
local r = Math.random()
|
|
|
|
if r < 1 - 1/0.4*(evolution - 0.2) then
|
|
return 'small-biter'
|
|
elseif r < 1 - 0.8/0.5*(evolution - 0.5) then
|
|
return 'medium-biter'
|
|
elseif r < 1 - 0.4/0.1*(evolution - 0.9) then
|
|
return 'big-biter'
|
|
else
|
|
return 'behemoth-biter'
|
|
end
|
|
end
|
|
|
|
function Public.get_random_spitter_type(evolution)
|
|
-- designed to approximate https://wiki.factorio.com/Enemies
|
|
local r = Math.random()
|
|
|
|
if r < 1 - 1/0.3*(evolution - 0.4) then
|
|
return 'small-spitter'
|
|
elseif r < 1 - 0.85/0.5*(evolution - 0.5) then
|
|
return 'medium-spitter'
|
|
elseif r < 1 - 0.4/0.1*(evolution - 0.9) then
|
|
return 'big-spitter'
|
|
else
|
|
return 'behemoth-spitter'
|
|
end
|
|
end
|
|
|
|
function Public.get_random_worm_type(evolution)
|
|
-- custom
|
|
local r = Math.random()
|
|
|
|
if r < 1 - 1/0.7*(evolution + 0.1) then
|
|
return 'small-worm-turret'
|
|
elseif r < 1 - 0.75/0.75*(evolution - 0.25) then
|
|
return 'medium-worm-turret'
|
|
elseif r < 1 - 0.4/0.4*(evolution - 0.6) then
|
|
return 'big-worm-turret'
|
|
else
|
|
return 'behemoth-worm-turret'
|
|
end
|
|
end
|
|
|
|
function Public.maximumUnitPollutionCost(evolution)
|
|
if evolution < 0.2 then return 4
|
|
elseif evolution < 0.5 then return 20
|
|
elseif evolution < 0.9 then return 80
|
|
else return 400
|
|
end
|
|
end
|
|
|
|
function Public.averageUnitPollutionCost(evolution)
|
|
|
|
local sum_biters = 0
|
|
local f1 = Math.slopefromto(1 - 1/0.4*(evolution - 0.2), 0, 1)
|
|
local f2 = Math.slopefromto(1 - 0.8/0.5*(evolution - 0.5), 0, 1)
|
|
local f3 = Math.slopefromto(1 - 0.4/0.1*(evolution - 0.9), 0, 1)
|
|
sum_biters = sum_biters + 4 * f1
|
|
sum_biters = sum_biters + 20 * (f2 - f1)
|
|
sum_biters = sum_biters + 80 * (f3 - f2)
|
|
sum_biters = sum_biters + 400 * (1 - f3)
|
|
|
|
local sum_spitters = 0
|
|
local g1 = Math.slopefromto(1 - 1/0.15*(evolution - 0.25), 0, 1)
|
|
local g2 = Math.slopefromto(1 - 1/0.3*(evolution - 0.4), 0, 1)
|
|
local g3 = Math.slopefromto(1 - 0.85/0.5*(evolution - 0.5), 0, 1)
|
|
local g4 = Math.slopefromto(1 - 0.4/0.1*(evolution - 0.9), 0, 1)
|
|
sum_spitters = sum_spitters + 4 * g1
|
|
sum_spitters = sum_spitters + 4 * (g2 - g1)
|
|
sum_spitters = sum_spitters + 12 * (g3 - g2)
|
|
sum_spitters = sum_spitters + 30 * (g4 - g3)
|
|
sum_spitters = sum_spitters + 200 * (1 - g4)
|
|
|
|
return (5 * sum_biters + sum_spitters)/6
|
|
end
|
|
|
|
function Public.orthog_positions_in_orthog_area(area)
|
|
local positions = {}
|
|
for y = area[1][2] + 0.5, area[2][2] - 0.5, 1 do
|
|
for x = area[1][1] + 0.5, area[2][1] - 0.5, 1 do
|
|
positions[#positions + 1] = {x = x, y = y}
|
|
end
|
|
end
|
|
return positions
|
|
end
|
|
|
|
function Public.tileslist_add_area_offset(tiles_list_to_add_to, area, offset, tile_type)
|
|
for _, p in pairs(Public.orthog_positions_in_orthog_area(area)) do
|
|
tiles_list_to_add_to[#tiles_list_to_add_to + 1] = {name = tile_type, position = {x = offset.x + p.x, y = offset.y + p.y}}
|
|
end
|
|
end
|
|
|
|
function Public.central_positions_within_area(area, offset)
|
|
local offsetx = offset.x or 0
|
|
local offsety = offset.y or 0
|
|
local xr1, xr2, yr1, yr2 = offsetx + Math.ceil(area[1][1] - 0.5), offsetx + Math.floor(area[2][1] + 0.5), offsety + Math.ceil(area[1][2] - 0.5), offsety + Math.floor(area[2][2] + 0.5)
|
|
|
|
local positions = {}
|
|
for y = yr1 + 0.5, yr2 - 0.5, 1 do
|
|
for x = xr1 + 0.5, xr2 - 0.5, 1 do
|
|
positions[#positions + 1] = {x = x, y = y}
|
|
end
|
|
end
|
|
return positions
|
|
end
|
|
|
|
function Public.tiles_from_area(tiles_list_to_add_to, area, offset, tile_type)
|
|
for _, p in pairs(Public.central_positions_within_area(area, offset)) do
|
|
tiles_list_to_add_to[#tiles_list_to_add_to + 1] = {name = tile_type, position = {x = p.x, y = p.y}}
|
|
end
|
|
end
|
|
|
|
function Public.tiles_horizontally_flipped(tiles, x_to_flip_about)
|
|
local tiles2 = {}
|
|
for _, t in pairs(tiles) do
|
|
local t2 = Utils.deepcopy(t)
|
|
t2.position = {x = 2 * x_to_flip_about - t2.position.x, y = t2.position.y}
|
|
tiles2[#tiles2 + 1] = t2
|
|
end
|
|
return tiles2
|
|
end
|
|
|
|
|
|
function Public.validate_player(player)
|
|
if player and player.valid and player.connected and game.players[player.name] then
|
|
return true
|
|
else
|
|
if _DEBUG then
|
|
log('player validation fail: ' .. (player.name or 'noname'))
|
|
end
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
function Public.validate_player_and_character(player)
|
|
local ret = Public.validate_player(player)
|
|
ret = ret and player.character and player.character.valid
|
|
return ret
|
|
end
|
|
|
|
|
|
function Public.send_important_items_from_player_to_crew(player, all_items)
|
|
local player_inv = {}
|
|
player_inv[1] = game.players[player.index].get_inventory(defines.inventory.character_main)
|
|
player_inv[2] = game.players[player.index].get_inventory(defines.inventory.character_armor)
|
|
player_inv[3] = game.players[player.index].get_inventory(defines.inventory.character_guns)
|
|
player_inv[4] = game.players[player.index].get_inventory(defines.inventory.character_ammo)
|
|
player_inv[5] = game.players[player.index].get_inventory(defines.inventory.character_trash)
|
|
|
|
local any = false
|
|
|
|
for ii = 1, 5, 1 do
|
|
if player_inv[ii].valid then
|
|
-- local to_keep = {}
|
|
local to_remove = {}
|
|
for iii = 1, #player_inv[ii], 1 do
|
|
-- local item_stack = player_inv[ii][iii] --don't do this as LuaItemStack is a reference!
|
|
if player_inv[ii] and player_inv[ii][iii].valid and player_inv[ii][iii].valid_for_read then
|
|
if all_items or (player_inv[ii][iii].name and Utils.contains(Public.logout_unprotected_items, player_inv[ii][iii].name)) then
|
|
to_remove[#to_remove + 1] = player_inv[ii][iii]
|
|
any = true
|
|
-- else
|
|
-- to_keep[#to_keep + 1] = Utils.deepcopy(player_inv[ii][iii])
|
|
end
|
|
end
|
|
end
|
|
|
|
if #to_remove > 0 then
|
|
for iii = 1, #to_remove, 1 do
|
|
if to_remove[iii].valid_for_read then
|
|
Public.give_items_to_crew{{name = to_remove[iii].name, count = to_remove[iii].count}}
|
|
to_remove[iii].clear()
|
|
end
|
|
end
|
|
-- clear and move over from to_keep if necessary?
|
|
end
|
|
end
|
|
end
|
|
|
|
return any
|
|
end
|
|
|
|
|
|
function Public.give_items_to_crew(items)
|
|
local memory = Memory.get_crew_memory()
|
|
|
|
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, chest2
|
|
|
|
if items.name and items.name == 'coin' then
|
|
chest = boat.backup_output_chest
|
|
if not (chest and chest.valid) then return end
|
|
chest2 = boat.output_chest
|
|
if not (chest2 and chest2.valid) then return end
|
|
else
|
|
chest = boat.output_chest
|
|
if not (chest and chest.valid) then return end
|
|
chest2 = boat.backup_output_chest
|
|
if not (chest2 and chest2.valid) then return end
|
|
end
|
|
|
|
local inventory = chest.get_inventory(defines.inventory.chest)
|
|
|
|
if items.name then --1 item
|
|
if not (items.count and items.count>0) then return end
|
|
local inserted = inventory.insert(items)
|
|
if items.count - inserted > 0 then
|
|
local inventory2 = chest2.get_inventory(defines.inventory.chest)
|
|
local i2 = Utils.deepcopy(items)
|
|
if i2.name then
|
|
i2.count = items.count - inserted
|
|
local inserted2 = inventory2.insert(i2)
|
|
if items.count - inserted - inserted2 > 0 then
|
|
local force = memory.force
|
|
if not (force and force.valid) then return end
|
|
Public.notify_force(force, 'Warning: captain\'s cabin chests are full!')
|
|
end
|
|
else
|
|
if _DEBUG then
|
|
log('give_items_to_crew: i2.name is nil. _inspect:')
|
|
log(_inspect(items))
|
|
log(_inspect(i2))
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for _, i in pairs(items) do
|
|
if not (i.count and i.count>0) then return end
|
|
local inserted = inventory.insert(i)
|
|
if i.count - inserted > 0 then
|
|
local inventory2 = chest2.get_inventory(defines.inventory.chest)
|
|
local i2 = Utils.deepcopy(i)
|
|
i2.count = i.count - inserted
|
|
local inserted2 = inventory2.insert(i2)
|
|
if i.count - inserted - inserted2 > 0 then
|
|
local force = memory.force
|
|
if not (force and force.valid) then return end
|
|
Public.notify_force(force, 'Warning: captain\'s cabin chests are full!')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function Public.version_to_array(v)
|
|
local vArray = {}
|
|
if type(v) == 'number' then --this is a legacy form
|
|
local vs = tostring(v)
|
|
for i = 1, string.len(vs) do
|
|
local char = vs:sub(i, i)
|
|
if i ~= 2 then
|
|
vArray[#vArray+1] = char
|
|
end
|
|
end
|
|
else
|
|
for i = 1, string.len(v) do
|
|
local char = v:sub(i, i)
|
|
if char ~= '.' then
|
|
vArray[#vArray+1] = char
|
|
end
|
|
end
|
|
end
|
|
|
|
return vArray
|
|
end
|
|
|
|
|
|
function Public.version_greater_than(v1, v2)
|
|
local v1Array = Public.version_to_array(v1)
|
|
local v2Array = Public.version_to_array(v2)
|
|
|
|
for i = 1, math.max(#v1Array, #v2Array) do
|
|
local v1i = tonumber(v1Array[i])
|
|
local v2i = tonumber(v2Array[i])
|
|
if v1i ~= nil and v2i ~= nil then
|
|
if v1i < v2i then
|
|
return false
|
|
elseif v1i > v2i then
|
|
return true
|
|
end
|
|
elseif v1i == nil then
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function Public.init_game_settings(technology_price_multiplier)
|
|
|
|
--== Tuned for Pirate Ship ==--
|
|
|
|
global.friendly_fire_history = {}
|
|
global.landfill_history = {}
|
|
global.mining_history = {}
|
|
|
|
game.difficulty_settings.technology_price_multiplier = technology_price_multiplier
|
|
|
|
game.map_settings.enemy_evolution.pollution_factor = 0
|
|
game.map_settings.enemy_evolution.time_factor = 0
|
|
game.map_settings.enemy_evolution.destroy_factor = 0
|
|
|
|
game.map_settings.unit_group.min_group_gathering_time = 60 * 5
|
|
game.map_settings.unit_group.max_group_gathering_time = 60 * 210
|
|
game.map_settings.unit_group.max_wait_time_for_late_members = 60 * 15
|
|
game.map_settings.unit_group.member_disown_distance = 5000
|
|
game.map_settings.unit_group.max_group_radius = 70
|
|
game.map_settings.unit_group.min_group_radius = 0.5 --seems to govern biter 'attack area' stopping distance
|
|
|
|
-- (0,2) for a symmetric search:
|
|
game.map_settings.path_finder.goal_pressure_ratio = -0.1 --small pressure for stupid paths
|
|
game.map_settings.path_finder.fwd2bwd_ratio = 2 -- on experiments I found that this value was symmetric, despite the vanilla game comments saying it is 1...
|
|
game.map_settings.max_failed_behavior_count = 2
|
|
game.map_settings.path_finder.max_work_done_per_tick = 20000
|
|
game.map_settings.path_finder.short_cache_min_algo_steps_to_cache = 100
|
|
game.map_settings.path_finder.cache_accept_path_start_distance_ratio = 0.1
|
|
|
|
|
|
game.map_settings.enemy_expansion.enabled = true
|
|
-- faster expansion:
|
|
-- game.map_settings.enemy_expansion.min_expansion_cooldown = 4 * 3600
|
|
-- game.map_settings.enemy_expansion.max_expansion_cooldown = 30 * 3600
|
|
-- slowed down due to the effect on single-player games:
|
|
game.map_settings.enemy_expansion.min_expansion_cooldown = 6 * 3600
|
|
game.map_settings.enemy_expansion.max_expansion_cooldown = 45 * 3600
|
|
game.map_settings.enemy_expansion.settler_group_max_size = 24
|
|
game.map_settings.enemy_expansion.settler_group_min_size = 6
|
|
-- maybe should be 3.5 if possible:
|
|
game.map_settings.enemy_expansion.max_expansion_distance = 4
|
|
|
|
-- could turn off default AI attacks:
|
|
game.map_settings.pollution.enemy_attack_pollution_consumption_modifier = 1
|
|
--
|
|
game.map_settings.pollution.enabled = true
|
|
game.map_settings.pollution.expected_max_per_chunk = 120
|
|
game.map_settings.pollution.min_to_show_per_chunk = 10
|
|
game.map_settings.pollution.min_pollution_to_damage_trees = 20
|
|
game.map_settings.pollution.pollution_per_tree_damage = 0.2
|
|
game.map_settings.pollution.max_pollution_to_restore_trees = 0.04
|
|
game.map_settings.pollution.pollution_restored_per_tree_damage = 0.01
|
|
game.map_settings.pollution.pollution_with_max_forest_damage = 80
|
|
game.map_settings.pollution.ageing = 0.1
|
|
|
|
game.map_settings.pollution.diffusion_ratio = 0.035
|
|
--
|
|
-- game.forces.neutral.character_inventory_slots_bonus = 500
|
|
game.forces.enemy.evolution_factor = 0
|
|
end
|
|
|
|
-- prefer memory.force_name if possible
|
|
function Public.get_crew_force_name(id)
|
|
return string.format('crew-%03d', id)
|
|
end
|
|
|
|
-- prefer memory.enemy_force_name if possible
|
|
function Public.get_enemy_force_name(id)
|
|
return string.format('enemy-%03d', id)
|
|
end
|
|
|
|
-- prefer memory.ancient_friendly_force_name if possible
|
|
function Public.get_ancient_friendly_force_name(id)
|
|
return string.format('ancient-friendly-%03d', id)
|
|
end
|
|
|
|
-- prefer memory.ancient_enemy_force_name if possible
|
|
function Public.get_ancient_hostile_force_name(id)
|
|
return string.format('ancient-hostile-%03d', id)
|
|
end
|
|
|
|
function Public.get_id_from_force_name(force_name)
|
|
return tonumber(string.sub(force_name, -3, -1)) or nil
|
|
end
|
|
|
|
function Public.is_id_valid(id)
|
|
if id and id ~= 0 then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- NOTE: Items here are either unobtainable or hard to find/get
|
|
-- Connected with crew.lua recipe and technology disables
|
|
function Public.get_item_blacklist(tier)
|
|
local blacklist = LootRaffle.get_tech_blacklist(tier)
|
|
blacklist['landfill'] = true
|
|
blacklist['concrete'] = true
|
|
blacklist['hazard-concrete'] = true
|
|
blacklist['locomotive'] = true
|
|
blacklist['cargo-wagon'] = true
|
|
blacklist['fluid-wagon'] = true
|
|
blacklist['train-stop'] = true
|
|
blacklist['rail-signal'] = true
|
|
blacklist['rail-chain-signal'] = true
|
|
blacklist['refined-concrete'] = true
|
|
blacklist['refined-hazard-concrete'] = true
|
|
blacklist['flamethrower-turret'] = true
|
|
blacklist['tank'] = true
|
|
blacklist['cannon-shell'] = true
|
|
blacklist['explosive-cannon-shell'] = true
|
|
blacklist['speed-module-3'] = true
|
|
blacklist['productivity-module-3'] = true
|
|
blacklist['effectivity-module-3'] = true
|
|
blacklist['space-science-pack'] = true
|
|
blacklist['rocket-control-unit'] = true
|
|
blacklist['artillery-wagon'] = true
|
|
blacklist['artillery-turret'] = true
|
|
blacklist['artillery-targeting-remote'] = true
|
|
blacklist['uranium-cannon-shell'] = true
|
|
blacklist['explosive-uranium-cannon-shell'] = true
|
|
blacklist['satellite'] = true
|
|
blacklist['rocket-silo'] = true
|
|
blacklist['destroyer-capsule'] = true
|
|
blacklist['spidertron'] = true
|
|
blacklist['discharge-defense-remote'] = true
|
|
blacklist['discharge-defense-equipment'] = true
|
|
blacklist['express-loader'] = true
|
|
blacklist['land-mine'] = true
|
|
blacklist['wood'] = true -- too easy to acquire
|
|
|
|
return blacklist
|
|
end
|
|
|
|
-- tier: affects amount of items and rarity returned
|
|
-- scale: final result of formula with tier scaled
|
|
-- tech_tier: float in range [0; 1]; 1 = everything unlocked
|
|
function Public.pick_random_price(tier, scale, tech_tier)
|
|
if tier < 0 or scale < 0 then return end
|
|
|
|
local item_stacks = LootRaffle.roll(math.floor(scale * (tier ^ 2 + 10 * tier)), 100, Public.get_item_blacklist(tech_tier))
|
|
local price = {}
|
|
for _, item_stack in pairs(item_stacks) do
|
|
price[#price+1] = {name = item_stack.name, amount = item_stack.count}
|
|
end
|
|
|
|
return price
|
|
end
|
|
|
|
-- This method should exist in table but it doesn't for some reason on comfy repo so I copied it to here
|
|
function Public.get_random_dictionary_entry(t, key)
|
|
local target_index = Math.random(1, table_size(t))
|
|
local count = 1
|
|
for k, v in pairs(t) do
|
|
if target_index == count then
|
|
if key then
|
|
return k
|
|
else
|
|
return v
|
|
end
|
|
end
|
|
count = count + 1
|
|
end
|
|
end
|
|
|
|
-- mainly used to connect multi-surface poles
|
|
function Public.force_connect_poles(pole1, pole2)
|
|
if not pole1 then return end
|
|
if not pole1.valid then return end
|
|
if not pole2 then return end
|
|
if not pole2.valid then return end
|
|
|
|
-- force connections for testing (by placing many poles around the substations)
|
|
-- for _, e in pairs(pole1.surface.find_entities_filtered{type="electric-pole", position = pole1.position, radius = 10}) do
|
|
-- pole1.connect_neighbour(e)
|
|
-- end
|
|
|
|
-- for _, e in pairs(pole2.surface.find_entities_filtered{type="electric-pole", position = pole2.position, radius = 10}) do
|
|
-- pole2.connect_neighbour(e)
|
|
-- end
|
|
|
|
local success = pole1.connect_neighbour(pole2)
|
|
if success then return end
|
|
|
|
local pole1_neighbours = pole1.neighbours['copper']
|
|
local pole2_neighbours = pole2.neighbours['copper']
|
|
|
|
-- try avoiding disconnecting more poles than needed
|
|
local disconnect_from_pole1 = false
|
|
local disconnect_from_pole2 = false
|
|
|
|
if #pole1_neighbours >= #pole2_neighbours then
|
|
disconnect_from_pole1 = true
|
|
end
|
|
|
|
if #pole2_neighbours >= #pole1_neighbours then
|
|
disconnect_from_pole2 = true
|
|
end
|
|
|
|
if disconnect_from_pole1 then
|
|
-- Prioritise disconnecting last connections as those are most likely redundant (at least for holds, although even then it's not always the case)
|
|
for i = #pole1_neighbours, 1, -1 do
|
|
local e = pole1_neighbours[i]
|
|
-- only disconnect poles from same surface
|
|
if e and e.valid and e.surface.name == pole1.surface.name then
|
|
pole1.disconnect_neighbour(e)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if disconnect_from_pole2 then
|
|
-- Prioritise disconnecting last connections as those are most likely redundant (at least for holds, although even then it's not always the case)
|
|
for i = #pole2_neighbours, 1, -1 do
|
|
local e = pole2_neighbours[i]
|
|
-- only disconnect poles from same surface
|
|
if e and e.valid and e.surface.name == pole2.surface.name then
|
|
pole2.disconnect_neighbour(e)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local success2 = pole1.connect_neighbour(pole2)
|
|
if not success2 then
|
|
-- This can happen if in future pole reach connection limit(5) with poles from other surfaces
|
|
log("Error: power fix didn't work")
|
|
end
|
|
end
|
|
|
|
return Public |