diff --git a/.travis.yml b/.travis.yml index cc0b8b91..be9d282b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,46 @@ language: python sudo: false - env: - - LUA="lua=5.2" - + matrix: + - LUA="lua=5.2" +branches: + only: + - master + - develop before_install: - pip install hererocks - - hererocks lua_install -r^ --$LUA - - export PATH=$PATH:$PWD/lua_install/bin - + - hererocks ~/build/lua_install -r^ --$LUA + - export PATH=$PATH:~/build/lua_install/bin install: - luarocks install luacheck - script: - - luacheck . --exclude-files lua_install + - "if [[ $TRAVIS_EVENT_TYPE != 'cron' ]]; then luacheck .; fi" +before_deploy: + - git config --local user.name "$github_user_name" + - git config --local user.email "$github_user_email" + - DATE_FORMATTED=$(date +'%Y-%m-%d') + - export DATE_FORMATTED + - COMMIT_SHA=$(git log --format=%h -1) + - export COMMIT_SHA + - TRAVIS_TAG="nightly/"$DATE_FORMATTED-$COMMIT_SHA + - export TRAVIS_TAG + - git tag "$TRAVIS_TAG" + - RELEASE_NAME='Nightly release - '"$DATE_FORMATTED"' - '"$COMMIT_SHA" + - export RELEASE_NAME + - ./.travis/release_builder.sh +deploy: + on: + condition: $TRAVIS_EVENT_TYPE = cron + repo: Refactorio/RedMew + branch: develop + provider: releases + api_key: + secure: SZsTjOLenetur34FEDCOrOw1jCI0xoZ0KyCrsrBpieQ0+P0hlVgRjEhyg74IiKC5cX5+HS329jUiIs7dUts2mGF09gCdYTPtKtkjmbhZK61DkMSplUIUDJDv5lCsR9BQTSX0x38+olkCTZAgFnU6T449auwd4Htsw6NOKI1V0h33K5YX4QJeMwnFQUorQ5lIl9+aEz/PVKHV0Glqnfp52O/Tn2nqfZxBI48UXRkJJIJLfVi74gPpD0HjspSYoCig3u6j9Rq2HcML+/geyJeM0aL6vZUsZhADIBT2x2MSZtN5KGRby+HEGnyj1u8Bp7BNpbioXtVZ3e57hrqTIbNzMx/8p4rqip9lm7ClP6uPTlgMfHKsnFYKOWiyuMGeFtJxcb/3KzMt4wXxZQQAkoJ4BnsYcm0G7H3EkAMbIF0piOwp2Fn8MCrmUSJcb+dOEe2ixe8p00vi9ffnvBr4Qz+nyqQU6D1aq3DNafmQn2eshuqrhB6+s86uj/3F9fFaPD+GhjL9t7zGgkX/RuLDPxdM7Xo7lc6yUhrFGA2yrkROqPq4M7bCmMcegRyvl27mGsh1R6FTwAEShHa7CU5/8bnnSxsp1YIdHoMWhCBEeFrm3lz6w+gly+iAxjkc0lzREjf/nYmjWSqB1TXUMbssg/G0czCgmTYlq9G7QhhPaWd0D2I= + file: + - RedMew.zip + #- Diggy.zip + #- Crashsite.zip + skip_cleanup: true + #prerelease: true + draft: true #after the testing phase, this will be deleted and we will issue prereleases + name: "$RELEASE_NAME" diff --git a/.travis/release_builder.sh b/.travis/release_builder.sh new file mode 100755 index 00000000..7055330c --- /dev/null +++ b/.travis/release_builder.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +#Copy the repo, clean it, and write the version for the release +cd ~/build || exit +echo "Changing dir to :" +pwd +echo "Copying RedMew folder to work on" +cp -rf Refactorio/RedMew working_copy +echo "Removing git files" +rm -rf working_copy/.??* +echo "Writing the version file" +echo 'return '"$DATE_FORMATTED"'-'"$COMMIT_SHA" > working_copy/resources/version.lua +echo "Contents of the version file:" +cat working_copy/resources/version.lua + +#Create zips for each of the major maps/scenarios +function process_map (){ + echo '-----'"$1"'-----' + mv "$3" "$1" + echo 'return '"'$2'" > "$1"'/map_selection.lua' + echo "Contents of map_selection:" + cat "$1"'/map_selection.lua' + echo "Creating zip..." + zip -r9q "$1"'.zip' "$1" + #if [ "$4" != true ]; then #Base RedMew can't be deflated + #echo "Deflating the zip..." + #Having the deflater here would be dope. + #fi + echo "Stats on the zip:" + ls -al "$1"'.zip' + cp "$1"'.zip' "$HOME/build/Refactorio/RedMew/""$1"'.zip' + PREVIOUS_NAME=$1 + export PREVIOUS_NAME + return 0 +} + +#Each map after the default redmew release repeats the pattern of $1=The regular name of the map (proper casing), $2=The name of the map file (lower case), $3 "$PREVIOUS_NAME" +process_map "RedMew" "default" "working_copy" true +#process_map "Diggy" "diggy" "$PREVIOUS_NAME" +#process_map "Crashsite" "crashsite" "$PREVIOUS_NAME" diff --git a/README.md b/README.md index 581ff32c..5f4d4d8c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ To join a RedMew Factorio server, follow the following steps in Factorio: - Next select "Browse public games" (this requires a factorio account and may prompt you to log in) - In the list of "Browse games" you can filter for "RedMew" and you will see all RedMew hosted maps -> _Note_: Not every server in this list will be official. If you're in doubt, join Discord and ask. +> _Note_: Not every server in this list will be official. If you're in doubt, join our Discord and ask. ## Documentation Looking for a way to play a RedMew scenario yourself? [Check out our wiki!](https://github.com/Refactorio/RedMew/wiki). 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/gui/toast.lua b/features/gui/toast.lua new file mode 100644 index 00000000..c8cde3d0 --- /dev/null +++ b/features/gui/toast.lua @@ -0,0 +1,198 @@ +local Event = require 'utils.event' +local Game = require 'utils.game' +local Global = require 'utils.global' +local Gui = require 'utils.gui' +local Color = require 'resources.color_presets' + +local type = type +local tonumber = tonumber +local pairs = pairs +local size = table.size + +local Public = {} + +local memory = { + id = 0, + active_toasts = {}, +} + +Global.register(memory, function (tbl) memory = tbl end, 'toast') + +---Creates a unique ID for a toast message +local function autoincrement() + local id = memory.id + 1 + memory.id = id + return id +end + +---Toast to a specific player +---@param p number|string|LuaPlayer player index or object +---@param duration number in seconds +local function toast_to(p, duration) + local player + + if type(p) == 'string' then + player = Game.get_player_by_index(tonumber(p)) + elseif type(p) == 'number' then + player = Game.get_player_by_index(p) + else + player = p + end + + if not player or not player.valid then + return nil + end + + local frame = player.gui.left.add({type = 'frame', direction = 'vertical', style = 'captionless_frame'}) + frame.style.width = 300 + + local container = frame.add({type = 'flow', direction = 'horizontal'}) + container.style.horizontally_stretchable = true + + local progressbar = frame.add({type = 'progressbar'}) + local style = progressbar.style + style.width = 290 + style.height = 3 + style.color = Color.grey + progressbar.value = 1 -- it starts full + + local id = autoincrement() + local tick = game.tick + if not duration then + duration = 15 + end + + Gui.set_data(frame, { + is_toast = true, + toast_id = id, + progressbar = progressbar, + start_tick = tick, + end_tick = tick + duration * 60 + }) + memory.active_toasts[id] = player.name + + return container +end + +---Attempts to get a toast based on the element, will traverse through parents to find it. +---@param element LuaGuiElement +local function get_toast(element) + if not element or not element.valid then + return nil + end + + local data = Gui.get_data(element) + + if data and type(data) == 'table' and not data.__self and data.is_toast then + return element, data + end + + -- no data, have to check the parent + return get_toast(element.parent) +end + +Event.add(defines.events.on_gui_click, function (event) + local element, data = get_toast(event.element) + if not element or not data then + return + end + + Gui.destroy(element) + memory.active_toasts[data.toast_id] = nil +end) + +Event.on_nth_tick(2, function (event) + local active_toasts = memory.active_toasts + if size(active_toasts) == 0 then + return + end + + local tick = event.tick + local players = game.players + + for _, player_name in pairs(active_toasts) do + local player = players[player_name] + if player and player.valid then + for _, element in pairs(player.gui.left.children) do + local toast, data = get_toast(element) + if toast and data then + local end_tick = data.end_tick + + if tick > end_tick then + Gui.destroy(element) + active_toasts[data.toast_id] = nil + else + local limit = end_tick - data.start_tick + local current = end_tick - tick + data.progressbar.value = current / limit + end + end + end + end + end +end) + +---Toast a specific player, template is a callable that receives a LuaGuiElement +---to add contents to and a player as second argument. +---@param player LuaPlayer|number +---@param duration table +---@param template function +function Public.toast_player_template(player, duration, template) + local container = toast_to(player, duration) + if container then + template(container, player) + end +end + +---Toast all players of the given force, template is a callable that receives a LuaGuiElement +---to add contents to and a player as second argument. +---@param force LuaForce +---@param duration number +---@param template function +function Public.toast_force_template(force, duration, template) + for _, player in pairs(force.connected_players) do + template(toast_to(player, duration), player) + end +end + +---Toast all players, template is a callable that receives a LuaGuiElement +---to add contents to and a player as second argument. +---@param duration number +---@param template function +function Public.toast_all_players_template(duration, template) + for _, player in pairs(game.connected_players) do + template(toast_to(player, duration), player) + end +end + +---Toast a message to a specific player +---@param player LuaPlayer|number +---@param duration number +---@param message string +function Public.toast_player(player, duration, message) + Public.toast_player_template(player, duration, function (container) + local label = container.add({type = 'label', caption = message}) + label.style.single_line = false + end) +end + +---Toast a message to all players of a given force +---@param force LuaForce +---@param duration number +---@param message string +function Public.toast_force(force, duration, message) + for _, player in pairs(force.connected_players) do + Public.toast_player(player, duration, message) + end +end + +---Toast a message to all players +---@param duration number +---@param message string +function Public.toast_all_players(duration, message) + for _, player in pairs(game.connected_players) do + Public.toast_player(player, duration, message) + end +end + +return Public 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/performance.lua b/features/performance.lua index 9b91fb0f..ded2af51 100644 --- a/features/performance.lua +++ b/features/performance.lua @@ -4,56 +4,71 @@ local format = string.format local Performance = {} ---Sets the scale of performance. ----1 means the game runs at normal game speed with full particles and normal walking speed ----0.5 means the game runs at half speed, running speed is doubled and particles are halved ----@param scale number -function Performance.set_scale(scale) +---1 means the game runs at normal game speed with normal walking speed +---0.5 means the game runs at half speed, running speed is doubled +---@param scale +function Performance.set_time_scale(scale) if scale < 0.05 or scale > 1 then error(format('Scale must range from 0.05 to 1')) end game.speed = scale - local movement_speed_scale = Performance.get_running_speed_modifier() - 1 + + local stat_mod = Performance.get_player_stat_modifier() for _, force in pairs(game.forces) do - force.character_running_speed_modifier = movement_speed_scale + force.character_running_speed_modifier = stat_mod - 1 + force.manual_mining_speed_modifier = stat_mod - 1 + force.manual_crafting_speed_modifier = stat_mod - 1 end end ----Returns the current scale -function Performance.get_scale() +---Returns the current game time scale +function Performance.get_time_scale() return game.speed end ----Returns the running speed modifier -function Performance.get_running_speed_modifier() +---Returns the stat modifier for stats affecting the players +function Performance.get_player_stat_modifier() return 1 / game.speed end -Command.add('set-performance-scale', { - description = 'Sets the performance scale between 0.05 and 1. Will alter the game speed and character running speed per force.', - arguments = {'scale'}, - admin_only = true, - allowed_by_server = true, -}, function (arguments, player) - local scale = tonumber(arguments.scale) - if scale == nil or scale < 0.05 or scale > 1 then - player.print('Scale must be a valid number ranging from 0.05 to 1') - return +Command.add( + 'performance-scale-set', + { + description = 'Sets the performance scale between 0.05 and 1. Will alter the game speed, manual mining speed, manual crafting speed and character running speed per force.', + arguments = {'scale'}, + admin_only = true, + allowed_by_server = true + }, + function(arguments, player) + local scale = tonumber(arguments.scale) + if scale == nil or scale < 0.05 or scale > 1 then + player.print('Scale must be a valid number ranging from 0.05 to 1') + return + end + + Performance.set_time_scale(scale) + local p = game.print + local stat_mod = Performance.get_player_stat_modifier() + p('## - Game speed changed to compensate for UPS drops and players trying to catch up.') + p(format('## - Game speed: %.2f', Performance.get_time_scale())) + p(format('## - Running speed: %.2f', stat_mod)) + p(format('## - Manual mining speed: %.2f', stat_mod)) + p(format('## - Manual crafting speed: %.2f', stat_mod)) end +) - Performance.set_scale(scale) - local p = game.print - p('## - Changed the game speed and running speed.') - p(format('## - Game speed: %.2f', Performance.get_scale())) - p(format('## - Force running speed: %.2f', Performance.get_running_speed_modifier())) -end) - -Command.add('get-performance-scale', { - description = 'Shows the current performance scale.', -}, function (_, player) - local p = player.print - p(format('Game speed: %.2f', Performance.get_scale())) - p(format('Running speed: %.2f', Performance.get_running_speed_modifier())) -end) +Command.add( + 'performance-scale-get', + { + description = 'Shows the current performance scale.' + }, + function(_, player) + local p = player.print + local stat_mod = Performance.get_player_stat_modifier() + p(format('Game speed: %.2f', Performance.get_time_scale())) + p(format('Running speed: %.2f -- mining speed: %.2f -- crafting speed: %.2f', stat_mod, stat_mod, stat_mod)) + end +) return Performance diff --git a/features/redmew_commands.lua b/features/redmew_commands.lua index ec6fbd7f..eeecc025 100644 --- a/features/redmew_commands.lua +++ b/features/redmew_commands.lua @@ -4,6 +4,7 @@ local Game = require 'utils.game' local Server = require 'features.server' local Timestamp = require 'utils.timestamp' local Command = require 'utils.command' +local redmew_version = require 'resources.version' local format = string.format local ceil = math.ceil @@ -208,6 +209,10 @@ local function list_seeds() Game.player_print(seeds) end +local function print_version() + Game.player_print(redmew_version) +end + -- Command registrations Command.add( @@ -285,6 +290,15 @@ Command.add( list_seeds ) +Command.add( + 'redmew-version', + { + description = 'Prints the version of the RedMew scenario', + allowed_by_server = true, + }, + print_version +) + -- Commands with no functions, only calls to other modules Command.add( diff --git a/features/retailer.lua b/features/retailer.lua index 14f9d2b4..44e6bf73 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') insert(tooltip, item.description) @@ -191,6 +299,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, @@ -209,7 +321,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 @@ -248,8 +360,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'}) @@ -432,6 +544,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 @@ -443,10 +561,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) @@ -454,8 +572,10 @@ Gui.on_click(item_button_name, function (event) item = item, count = stack_count, player = player, - group_name = data.market_group + group_name = market_group, }) + + do_coin_label(coin_count - cost, data.coin_label) end) ---Add a market to the group_name retailer. @@ -472,6 +592,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 @@ -489,7 +612,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 @@ -579,4 +707,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/Diggy/Feature/DiggyCaveCollapse.lua b/map_gen/Diggy/Feature/DiggyCaveCollapse.lua index 361f5fdc..e9f85e75 100644 --- a/map_gen/Diggy/Feature/DiggyCaveCollapse.lua +++ b/map_gen/Diggy/Feature/DiggyCaveCollapse.lua @@ -63,20 +63,23 @@ local stress_map_storage = {} local new_tile_map = {} local collapse_positions_storage = {} -Global.register({ - new_tile_map = new_tile_map, - stress_map_storage = stress_map_storage, - deconstruction_alert_message_shown = show_deconstruction_alert_message, - collapse_positions_storage = collapse_positions_storage, -}, function(tbl) - new_tile_map = tbl.new_tile_map - stress_map_storage = tbl.stress_map_storage - show_deconstruction_alert_message = tbl.deconstruction_alert_message_shown - collapse_positions_storage = tbl.collapse_positions_storage -end) +Global.register( + { + new_tile_map = new_tile_map, + stress_map_storage = stress_map_storage, + deconstruction_alert_message_shown = show_deconstruction_alert_message, + collapse_positions_storage = collapse_positions_storage + }, + function(tbl) + new_tile_map = tbl.new_tile_map + stress_map_storage = tbl.stress_map_storage + show_deconstruction_alert_message = tbl.deconstruction_alert_message_shown + collapse_positions_storage = tbl.collapse_positions_storage + end +) local defaultValue = 0 -local collapse_alert = {type='item', name='stone'} +local collapse_alert = {type = 'item', name = 'stone'} DiggyCaveCollapse.events = { --[[-- @@ -86,7 +89,6 @@ DiggyCaveCollapse.events = { - player_index Number (index of player that caused the collapse) ]] on_collapse_triggered = script.generate_event_name(), - --[[-- After a collapse - position LuaPosition @@ -107,14 +109,16 @@ local function create_collapse_template(positions, surface) local do_insert = true for _, entity in pairs(find_entities_filtered({area = {position, {x + 1, y + 1}}})) do - pcall(function() - local strength = support_beam_entities[entity.name] - if strength then - do_insert = false - else - entity.die() + pcall( + function() + local strength = support_beam_entities[entity.name] + if strength then + do_insert = false + else + entity.die() + end end - end) + ) end if do_insert then @@ -140,12 +144,23 @@ local function collapse(args) local positions = {} local count = 0 local strength = config.collapse_threshold_total_strength - mask_disc_blur(position.x, position.y, strength, function(x, y, value) - stress_map_check_stress_in_threshold(surface, x, y, value, function(_, c_x, c_y) - count = count + 1 - positions[count] = {x = c_x, y = c_y} - end) - end) + mask_disc_blur( + position.x, + position.y, + strength, + function(x, y, value) + stress_map_check_stress_in_threshold( + surface, + x, + y, + value, + function(_, c_x, c_y) + count = count + 1 + positions[count] = {x = c_x, y = c_y} + end + ) + end + ) if #positions == 0 then return @@ -160,9 +175,12 @@ local function collapse(args) end local on_collapse_timeout_finished = Token.register(collapse) -local on_near_threshold = Token.register(function (params) - ceiling_crumble(params.surface, params.position) -end) +local on_near_threshold = + Token.register( + function(params) + ceiling_crumble(params.surface, params.position) + end +) local function spawn_collapse_text(surface, position) local color = { @@ -276,13 +294,15 @@ local function on_placed_entity(event) end end - -local on_new_tile_timeout_finished = Token.register(function(args) - local x_t = new_tile_map[args.x] - if x_t then - x_t[args.y] = nil --reset new tile status. This tile can cause a chain collapse now +local on_new_tile_timeout_finished = + Token.register( + function(args) + local x_t = new_tile_map[args.x] + if x_t then + x_t[args.y] = nil --reset new tile status. This tile can cause a chain collapse now + end end -end) +) local function on_void_removed(event) local strength = support_beam_entities['out-of-map'] @@ -343,12 +363,18 @@ function DiggyCaveCollapse.register(cfg) Event.add(DiggyCaveCollapse.events.on_collapse_triggered, on_collapse_triggered) Event.add(defines.events.on_robot_built_entity, on_built_entity) - Event.add(defines.events.on_robot_built_tile, function (event) - on_built_tile(event.robot.surface, event.item, event.tiles) - end) - Event.add(defines.events.on_player_built_tile, function (event) - on_built_tile(game.surfaces[event.surface_index], event.item, event.tiles) - end) + Event.add( + defines.events.on_robot_built_tile, + function(event) + on_built_tile(event.robot.surface, event.item, event.tiles) + end + ) + Event.add( + defines.events.on_player_built_tile, + function(event) + on_built_tile(game.surfaces[event.surface_index], event.item, event.tiles) + end + ) Event.add(defines.events.on_robot_mined_tile, on_robot_mined_tile) Event.add(defines.events.on_player_mined_tile, on_player_mined_tile) Event.add(defines.events.on_built_entity, on_built_entity) @@ -358,31 +384,40 @@ function DiggyCaveCollapse.register(cfg) Event.add(Template.events.on_void_removed, on_void_removed) Event.add(defines.events.on_surface_created, on_surface_created) - Event.add(defines.events.on_marked_for_deconstruction, function (event) - local entity = event.entity - local name = entity.name - if is_diggy_rock(name) then - return + Event.add( + defines.events.on_marked_for_deconstruction, + function(event) + local entity = event.entity + local name = entity.name + if is_diggy_rock(name) then + return + end + + if name == 'deconstructible-tile-proxy' or nil ~= support_beam_entities[name] then + entity.cancel_deconstruction(Game.get_player_by_index(event.player_index).force) + end end + ) - if name == 'deconstructible-tile-proxy' or nil ~= support_beam_entities[name] then - entity.cancel_deconstruction(Game.get_player_by_index(event.player_index).force) + Event.add( + defines.events.on_player_created, + function(event) + show_deconstruction_alert_message[event.player_index] = true end - end) + ) - Event.add(defines.events.on_player_created, function (event) - show_deconstruction_alert_message[event.player_index] = true - end) + Event.add( + defines.events.on_pre_player_mined_item, + function(event) + local player_index = event.player_index + if not show_deconstruction_alert_message[player_index] then + return + end - Event.add(defines.events.on_pre_player_mined_item, function (event) - local player_index = event.player_index - if not show_deconstruction_alert_message[player_index] then - return - end - - if (nil ~= support_beam_entities[event.entity.name]) then - require 'features.gui.popup'.player( - Game.get_player_by_index(player_index),[[ + if (nil ~= support_beam_entities[event.entity.name]) then + require 'features.gui.popup'.player( + Game.get_player_by_index(player_index), + [[ Mining entities such as walls, stone paths, concrete and rocks, can cause a cave-in, be careful miner! @@ -390,10 +425,11 @@ Foreman's advice: Place a wall every 4th tile to prevent a cave-in. Use stone paths and concrete to reinforce it further. ]] - ) - show_deconstruction_alert_message[player_index] = nil + ) + show_deconstruction_alert_message[player_index] = nil + end end - end) + ) enable_stress_grid = config.enable_stress_grid @@ -402,9 +438,14 @@ to reinforce it further. mask_init(config) if (config.enable_mask_debug) then local surface = RS.get_surface() - mask_disc_blur(0, 0, 10, function(x, y, fraction) - Debug.print_grid_value(fraction, surface, {x = x, y = y}) - end) + mask_disc_blur( + 0, + 0, + 10, + function(x, y, fraction) + Debug.print_grid_value(fraction, surface, {x = x, y = y}) + end + ) end end @@ -449,26 +490,32 @@ local function add_fraction(stress_map, x, y, fraction, player_index, surface) if fraction > 0 then if value > stress_threshold_causing_collapse then - raise_event(DiggyCaveCollapse.events.on_collapse_triggered, { - surface = surface, - position = {x = x, y = y}, - player_index = player_index - }) + raise_event( + DiggyCaveCollapse.events.on_collapse_triggered, + { + surface = surface, + position = {x = x, y = y}, + player_index = player_index + } + ) elseif value > near_stress_threshold_causing_collapse then set_timeout_in_ticks(2, on_near_threshold, {surface = surface, position = {x = x, y = y}}) end end if enable_stress_grid then - Debug.print_colored_grid_value(value, surface, {x = x, y = y}, 0.5, false, - value / stress_threshold_causing_collapse, {r = 0, g = 1, b = 0}, {r = 1, g = -1, b = 0}, - {r = 0, g = 1, b = 0}, {r = 1, g = 1, b = 1}) + Debug.print_colored_grid_value(value, surface, {x = x, y = y}, 0.5, false, value / stress_threshold_causing_collapse, {r = 0, g = 1, b = 0}, {r = 1, g = -1, b = 0}, {r = 0, g = 1, b = 0}, {r = 1, g = 1, b = 1}) end return value end -on_surface_created = function (event) +on_surface_created = function(event) local index = event.surface_index - stress_map_storage[index] = {} + + if stress_map_storage[index] then + table.clear_table(stress_map_storage[index]) + else + stress_map_storage[index] = {} + end local map = stress_map_storage[index] @@ -603,4 +650,12 @@ function DiggyCaveCollapse.get_extra_map_info() Place stone walls, stone paths and (refined) concrete to reinforce the mine. If you see cracks appear, run!]] end +Event.on_init( + function() + if global.config.redmew_surface.enabled then + on_surface_created({surface_index = RS.get_surface().index}) + end + end +) + return DiggyCaveCollapse diff --git a/map_gen/Diggy/Scenario.lua b/map_gen/Diggy/Scenario.lua index c9d3d4ba..9c0258f5 100644 --- a/map_gen/Diggy/Scenario.lua +++ b/map_gen/Diggy/Scenario.lua @@ -13,6 +13,7 @@ require 'utils.core' local Scenario = {} RS.set_first_player_position_check_override(true) -- forces players to spawn at 0,0 +RS.set_spawn_island_tile('stone-path') global.diggy_scenario_registered = false --[[-- diff --git a/map_gen/combined/tetris/control.lua b/map_gen/combined/tetris/scenario.lua similarity index 100% rename from map_gen/combined/tetris/control.lua rename to map_gen/combined/tetris/scenario.lua 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 diff --git a/map_gen/presets/tetris.lua b/map_gen/presets/tetris.lua index 4c5bfe75..b75fa1a3 100644 --- a/map_gen/presets/tetris.lua +++ b/map_gen/presets/tetris.lua @@ -1 +1 @@ -return require 'map_gen.combined.tetris.control' +return require 'map_gen.combined.tetris.scenario' diff --git a/map_gen/shared/chunklist.lua b/map_gen/shared/chunklist.lua new file mode 100644 index 00000000..3265487f --- /dev/null +++ b/map_gen/shared/chunklist.lua @@ -0,0 +1,61 @@ +-- This module stores chunks as they are generated, keeping their left_top coordinate in an arrayed table. +-- An event is raised on each chunk stored that other modules can hook on to. + +-- When 0.17 is released, this module should be modified take advantage of the on_chunk_deleted event in order to remove entries from the table + +-- Dependencies +local Global = require 'utils.global' +local RS = require 'map_gen.shared.redmew_surface' +local Event = require 'utils.event' + +-- Localized functions +local raise_event = script.raise_event + +-- Local vars +local surface +local Public = { + chunk_list = {}, + events = { + --[[ + on_chunk_registered + Triggered when a chunk is recorded into the table + Contains + name :: defines.events: Identifier of the event + tick :: uint: Tick the event was generated. + area :: BoundingBox: Area of the chunk + surface :: LuaSurface: The surface the chunk is on + chunk_index :: the index of the chunk in the table + ]] + on_chunk_registered = script.generate_event_name() + } +} + +-- Global register +Global.register_init( + {chunk_list = Public.chunk_list}, + function(tbl) + tbl.surface = RS.get_surface() + end, + function(tbl) + Public.chunk_list = tbl.chunk_list + surface = tbl.surface + end +) + +local function on_chunk_generated(event) + if surface ~= event.surface then + return + end + + local chunk_list = Public.chunk_list + local new_entry_index = #chunk_list + 1 + + chunk_list[new_entry_index] = event.area.left_top + + event.chunk_index = new_entry_index + raise_event(Public.events.on_chunk_registered, event) +end + +Event.add(defines.events.on_chunk_generated, on_chunk_generated) + +return Public diff --git a/resources/version.lua b/resources/version.lua new file mode 100644 index 00000000..81997639 --- /dev/null +++ b/resources/version.lua @@ -0,0 +1 @@ +return 'This map was created from source code, only releases (zips with names) have versions' diff --git a/utils/table.lua b/utils/table.lua index 42dea05b..c76e04ec 100644 --- a/utils/table.lua +++ b/utils/table.lua @@ -155,7 +155,7 @@ end --- Clears all existing entries in a table -- @param t to clear --- @param sorted to indicate whether the table is an array or not +-- @param array to indicate whether the table is an array or not function table.clear_table(t, array) if array then for i = 1, #t do