diff --git a/config.lua b/config.lua index d1c15732..995f629e 100644 --- a/config.lua +++ b/config.lua @@ -196,6 +196,10 @@ global.config = { -- rewards players for looking through the info tabs info_player_reward = true }, + -- makes manual stuff cumbersome + lazy_bastard = { + enabled = false, + }, -- automatically marks miners for deconstruction when they are depleted (currently compatible with hard mods that add miners) autodeconstruct = { enabled = true diff --git a/control.lua b/control.lua index e62e551a..89614cb1 100644 --- a/control.lua +++ b/control.lua @@ -63,6 +63,9 @@ end if config.hail_hydra.enabled then require 'features.hail_hydra' end +if config.lazy_bastard.enabled then + require 'features.lazy_bastard' +end if config.redmew_qol.enabled then require 'features.redmew_qol' end diff --git a/features/lazy_bastard.lua b/features/lazy_bastard.lua new file mode 100644 index 00000000..f913aa0f --- /dev/null +++ b/features/lazy_bastard.lua @@ -0,0 +1,157 @@ +local Event = require 'utils.event' +local Game = require 'utils.game' +local Command = require 'utils.command' +local Task = require 'utils.task' +local Token = require 'utils.token' +local Retailer = require 'features.retailer' +local round = math.round +local insert = table.insert +local remove = table.remove + +local clean_energy_interface = Token.register(function (params) + local entity = params.entity + if not entity or not entity.valid then + -- already removed o.O + return + end + + entity.destroy() +end) + +if global.config.market.enabled then + local new_items = { + { + name = 'welcome-package', + name_label = 'Lazy bastard welcome package', + type = Retailer.item_types.item_package, + description = 'Contains some goodies to get started', + sprite = 'achievement/lazy-bastard', + stack_limit = 1, + player_limit = 1, + price = 0, + items = { + {name = 'solar-panel', count = 1}, + {name = 'roboport', count = 1}, + {name = 'coin', count = 30}, + {name = 'small-electric-pole', count = 5}, + {name = 'construction-robot', count = 2}, + }, + }, + {price = 5, name = 'construction-robot'}, + {price = 15, name = 'logistic-robot'}, + {price = 50, name = 'roboport'}, + {price = 5, name = 'logistic-chest-passive-provider'}, + {price = 5, name = 'logistic-chest-active-provider'}, + {price = 5, name = 'logistic-chest-buffer'}, + {price = 5, name = 'logistic-chest-requester'}, + {price = 5, name = 'logistic-chest-storage'}, + + } + local market_items = require 'resources.market_items' + + for i = #market_items, 1, -1 do + local name = market_items[i].name + -- cleanup items we don't need, construction bot has to be replaced for convenience + if name == 'temporary-mining-speed-bonus' or name == 'construction-robot' or name == 'steel-axe' then + remove(market_items, i) + end + end + + for i = 1, #new_items do + insert(market_items, i, new_items[i]) + end +end + +-- disable pickaxes from the start +Event.on_init(function () + local recipes = game.forces.player.recipes + recipes['iron-axe'].enabled = false + recipes['steel-axe'].enabled = false +end) + +-- ensure the recipes are disabled all the time +Event.add(defines.events.on_research_finished, function (event) + local recipes = event.research.force.recipes + recipes['iron-axe'].enabled = false + recipes['steel-axe'].enabled = false +end) + +-- players cannot build anything, just place ghosts +Event.add(defines.events.on_built_entity, function(event) + local entity = event.created_entity + if not entity or not entity.valid then + return + end + + local name = entity.name + + if name == 'entity-ghost' then + return + end + + -- replace the entity by a ghost + local direction = entity.direction + local position = entity.position + local surface = entity.surface + local force = entity.force + + -- not every item has a ghost, this is the easiest way to prevent errors and stop replacement + pcall(function() + surface.create_entity({ + name = 'entity-ghost', + inner_name = name, + direction = direction, + position = position, + force = force, + }); + entity.destroy() + + -- attempt to give the item back to the player + local player = Game.get_player_by_index(event.player_index) + if not player or not player.valid then + return + end + + player.insert(event.stack) + end) +end) + +Command.add('lazy-bastard-bootstrap', { + description = 'Puts down the minimum requirements to get started', + admin_only = true, +}, function(_, player) + local surface = player.surface + local force = player.force + + local pos = player.position + pos.y = round(pos.y - 4) + pos.x = round(pos.x) + + local bot_count = 3 + local create_entity = surface.create_entity + local templates = { + {name = 'medium-electric-pole', force = force, position = {x = pos.x - 2, y = pos.y - 1}}, + {name = 'roboport', force = force, position = {x = pos.x, y = pos.y}}, + {name = 'logistic-chest-storage', force = force, position = {x = pos.x + 1, y = pos.y + 1}}, + {name = 'logistic-chest-storage', force = force, position = {x = pos.x - 2, y = pos.y + 1}}, + } + + for i = 1, #templates do + local entity = create_entity(templates[i]) + entity.minable = false + entity.destructible = false + end + + for _ = 1, bot_count do + create_entity({name = 'construction-robot', force = force, position = pos}) + end + + local power_source = create_entity({name = 'hidden-electric-energy-interface', position = pos}) + power_source.electric_buffer_size = 30000 + power_source.power_production = 30000 + power_source.destructible = false + power_source.minable = false + + -- in 7 minutes, remove the power source + Task.set_timeout(420, clean_energy_interface, {entity = power_source}) +end) diff --git a/features/market.lua b/features/market.lua index f15407b5..2244e8d8 100644 --- a/features/market.lua +++ b/features/market.lua @@ -10,8 +10,8 @@ local market_items = require 'resources.market_items' local fish_market_bonus_message = require 'resources.fish_messages' -- localized functions - local pairs = pairs +local round = math.round local random = math.random local format = string.format local currency = global.config.market.currency @@ -38,7 +38,8 @@ local function spawn_market(_, player) local force = player.force local pos = player.position - pos.y = pos.y - 4 + pos.y = round(pos.y - 4) + pos.x = round(pos.x) local market = surface.create_entity({name = 'market', position = pos}) market.destructible = false @@ -46,8 +47,10 @@ local function spawn_market(_, player) Retailer.add_market('fish_market', market) - for _, prototype in pairs(market_items) do - Retailer.set_item('fish_market', prototype) + if table.size(Retailer.get_items('fish_market')) == 0 then + for _, prototype in pairs(market_items) do + Retailer.set_item('fish_market', prototype) + end end force.add_chart_tag(surface, {icon = {type = 'item', name = currency}, position = pos, text = 'Market'}) diff --git a/features/retailer.lua b/features/retailer.lua index 9e10f2f2..20e69a72 100644 --- a/features/retailer.lua +++ b/features/retailer.lua @@ -77,6 +77,12 @@ Retailer.events = { on_market_purchase = script.generate_event_name(), } +Retailer.item_types = { + --- expects an array of item prototypes that can be inserted directly via + --- player.insert() called 'items' in the item prototype. + item_package = 'item_package', +} + local market_gui_close_distance_squared = 6 * 6 + 6 * 6 local do_update_market_gui -- token @@ -91,6 +97,7 @@ local memory = { group_label = {}, players_in_market_view = {}, market_gui_refresh_scheduled = {}, + limited_items = {}, } Global.register(memory, function (tbl) @@ -130,9 +137,9 @@ function Retailer.get_market_group_label(group_name) end ---Returns all item for the group_name retailer. ----@param group_name string -function Retailer.get_items(group_name) - return memory.items[group_name] or {} +---@param market_group string +function Retailer.get_items(market_group) + return memory.items[market_group] or {} end ---Removes an item from the markets for the group_name retailer. @@ -148,6 +155,76 @@ function Retailer.remove_item(group_name, item_name) schedule_market_gui_refresh(group_name) end +---Returns the remaining market group item limit or -1 if there is none for a given player. +---@param market_group string +---@param item_name string +---@param player_index number +function Retailer.get_player_item_limit(market_group, item_name, player_index) + local item = Retailer.get_items(market_group)[item_name] + + if not item then + Debug.print({message = 'Item not registered in the Retailer', data = { + market_group = market_group, + item_name = item_name, + }}) + return -1 + end + + return memory.limited_items[market_group][item_name][player_index] or item.player_limit +end + +---Returns the configured market group item limit or -1 if there is none. +---@param market_group string +---@param item_name string +function Retailer.get_item_limit(market_group, item_name) + local item = Retailer.get_items(market_group)[item_name] + + if not item then + Debug.print({message = 'Item not registered in the Retailer', data = { + market_group = market_group, + item_name = item_name, + }}) + return -1 + end + + return item.player_limit +end + +---sets the configured market group item limit for a given player +---@param market_group string +---@param item_name string +---@param player_index number +---@param new_limit number +function Retailer.set_player_item_limit(market_group, item_name, player_index, new_limit) + if new_limit < 0 then + Debug.print({message = 'Cannot set a negative item limit', data = { + market_group = market_group, + item_name = item_name, + new_limit = new_limit, + }}) + return + end + local item = Retailer.get_items(market_group)[item_name] + + if not item then + Debug.print({message = 'Item not registered in the Retailer', data = { + market_group = market_group, + item_name = item_name, + }}) + return -1 + end + + if new_limit > item.player_limit then + Debug.print({message = 'Cannot set an item limit higher than the item prototype defined', data = { + market_group = market_group, + item_name = item_name, + new_limit = new_limit, + }}) + new_limit = item.player_limit + end + memory.limited_items[market_group][item_name][player_index] = new_limit +end + local function redraw_market_items(data) local grid = data.grid @@ -155,23 +232,50 @@ local function redraw_market_items(data) local count = data.count local market_items = data.market_items - local player_coins = data.player_coins + local player_index = data.player_index + local player_coins = Game.get_player_by_index(player_index).get_item_count('coin') if size(market_items) == 0 then grid.add({type = 'label', caption = 'No items available at this time'}) return end + local limited_items = memory.limited_items[data.market_group] + for i, item in pairs(market_items) do - local stack_limit = item.stack_limit - local stack_count = stack_limit ~= -1 and stack_limit < count and item.stack_limit or count + local has_stack_limit = item.stack_limit ~= -1 + local stack_limit = has_stack_limit and item.stack_limit or count + local stack_count = has_stack_limit and stack_limit < count and item.stack_limit or count + local player_limit = item.player_limit + local has_player_limit = player_limit ~= -1 + + if has_player_limit then + local item_name = item.name + player_limit = limited_items[item_name][player_index] + + if player_limit == nil then + -- no limit set yet + player_limit = item.player_limit + limited_items[item_name][player_index] = item.player_limit + end + + if player_limit < stack_count then + -- ensure the stack count is never higher than the item limit for the player + stack_count = player_limit + end + end + + local player_bought_max_total = has_player_limit and stack_count == 0 local price = item.price - local tooltip = {'', item.name_label, format('\nprice: %.2f', price)} + local tooltip = {'', item.name_label} local description = item.description local total_price = ceil(price * stack_count) local disabled = item.disabled == true local message - if total_price == 1 then + + if total_price == 0 then + message = 'FREE!' + elseif total_price == 1 then message = '1 coin' else message = total_price .. ' coins' @@ -180,6 +284,10 @@ local function redraw_market_items(data) local missing_coins = total_price - player_coins local is_missing_coins = missing_coins > 0 + if price ~= 0 then + insert(tooltip, format('\nprice: %.2f', price)) + end + if description then insert(tooltip, '\n' .. item.description) end @@ -190,6 +298,10 @@ local function redraw_market_items(data) insert(tooltip, '\n\n' .. format('Missing %d coins to buy %d', missing_coins, stack_count)) end + if has_player_limit then + insert(tooltip, '\n\n' .. format('You have bought this item %d out of %d times', item.player_limit - player_limit, item.player_limit)) + end + local button = grid.add({type = 'flow'}).add({ type = 'sprite-button', name = item_button_name, @@ -208,7 +320,7 @@ local function redraw_market_items(data) label_style.font = 'default-bold' label_style.vertical_align = 'center' - if disabled then + if disabled or player_bought_max_total then label_style.font_color = Color.dark_grey button.enabled = false elseif is_missing_coins then @@ -247,8 +359,8 @@ local function draw_market_frame(player, group_name) grid = grid, count = 1, market_items = market_items, - player_coins = player_coins, market_group = group_name, + player_index = player.index, } local coin_label = frame.add({type = 'label'}) @@ -431,6 +543,12 @@ Gui.on_click(item_button_name, function (event) return end + local market_group = data.market_group + if item.player_limit ~= -1 then + local limited_item = memory.limited_items[market_group][name] + limited_item[player.index] = limited_item[player.index] - stack_count + end + if item.type == 'item' then local inserted = player.insert({name = name, count = stack_count}) if inserted < stack_count then @@ -442,10 +560,10 @@ Gui.on_click(item_button_name, function (event) end end - player.remove_item({name = 'coin', count = cost}) - local player_coins = data.player_coins - cost - data.player_coins = player_coins - do_coin_label(player_coins, data.coin_label) + if cost > 0 then + player.remove_item({name = 'coin', count = cost}) + end + redraw_market_items(data) PlayerStats.change_coin_spent(player.index, cost) @@ -453,7 +571,10 @@ Gui.on_click(item_button_name, function (event) item = item, count = stack_count, player = player, + group_name = market_group, }) + + do_coin_label(coin_count - cost, data.coin_label) end) ---Add a market to the group_name retailer. @@ -470,6 +591,9 @@ function Retailer.set_item(group_name, prototype) if not memory.items[group_name] then memory.items[group_name] = {} end + if not memory.limited_items[group_name] then + memory.limited_items[group_name] = {} + end local item_name = prototype.name local name_label = prototype.name_label @@ -487,7 +611,12 @@ function Retailer.set_item(group_name, prototype) prototype.stack_limit = -1 end + if not prototype.player_limit then + prototype.player_limit = -1 + end + memory.items[group_name][item_name] = prototype + memory.limited_items[group_name][item_name] = {} schedule_market_gui_refresh(group_name) end @@ -577,4 +706,17 @@ Event.on_nth_tick(37, function() end end) +Event.add(Retailer.events.on_market_purchase, function (event) + local package = event.item + if package.type ~= Retailer.item_types.item_package then + return + end + + local player_insert = event.player.insert + for _, item in pairs(package.items) do + item.count = item.count * event.count + player_insert(item) + end +end) + return Retailer diff --git a/map_gen/presets/terraforming_danger_ores.lua b/map_gen/presets/terraforming_danger_ores.lua index f21ef2ce..4242355a 100644 --- a/map_gen/presets/terraforming_danger_ores.lua +++ b/map_gen/presets/terraforming_danger_ores.lua @@ -8,6 +8,8 @@ local table = require 'utils.table' local RS = require 'map_gen.shared.redmew_surface' local MGSP = require 'resources.map_gen_settings' +global.config.lazy_bastard.enabled = true + RS.set_map_gen_settings( { MGSP.grass_only, @@ -66,6 +68,9 @@ Global.register_init( local s = RS.get_surface() tbl.seed = s.map_gen_settings.seed tbl.surface = s + game.difficulty_settings.technology_price_multiplier = 50 + game.forces.player.technologies.logistics.researched = true + game.forces.player.technologies.automation.researched = true end, function(tbl) local seed = tbl.seed