diff --git a/data/factorio/mod/LICENSE.md b/data/factorio/mod/LICENSE.md index 2efbca63..1299d90b 100644 --- a/data/factorio/mod/LICENSE.md +++ b/data/factorio/mod/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2021 Berserker55 +Copyright (c) 2021 Berserker55 and Dewiniaid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/data/factorio/mod/info.json b/data/factorio/mod/info.json index 2223a57f..2b6c428c 100644 --- a/data/factorio/mod/info.json +++ b/data/factorio/mod/info.json @@ -2,7 +2,7 @@ "name": "archipelago-client", "version": "0.0.1", "title": "Archipelago", - "author": "Berserker", + "author": "Berserker and Dewiniaid", "homepage": "https://archipelago.gg", "description": "Integration client for the Archipelago Randomizer", "factorio_version": "1.1" diff --git a/data/factorio/mod_template/control.lua b/data/factorio/mod_template/control.lua index 1afbdd34..0bdd2685 100644 --- a/data/factorio/mod_template/control.lua +++ b/data/factorio/mod_template/control.lua @@ -1,11 +1,135 @@ require "lib" -script.on_event(defines.events.on_player_created, function(event) +require "util" + +FREE_SAMPLES = {{ free_samples }} +--SUPPRESS_INVENTORY_EVENTS = false + +-- Initialize force data, either from it being created or already being part of the game when the mod was added. +function on_force_created(event) + game.forces[event.force].research_queue_enabled = true + local data = {} + if FREE_SAMPLES ~= 0 then + data['earned_samples'] = { + ["burner-mining-drill"] = 19, + ["stone-furnace"] = 19 + } + end + global.forcedata[event.force] = data +end +script.on_event(defines.events.on_force_created, on_force_created) + +-- Destroy force data. This doesn't appear to be currently possible with the Factorio API, but here for completeness. +function on_force_destroyed(event) + global.forcedata[event.force] = nil +end + +-- Initialize player data, either from them joining the game or them already being part of the game when the mod was +-- added.` +function on_player_created(event) local player = game.players[event.player_index] - player.force.research_queue_enabled = true - {% if free_samples %} - player.insert({count=19, name="burner-mining-drill"}) - player.insert({count=19, name="stone-furnace"}) - {% endif %} + -- FIXME: This (probably) fires before any other mod has a chance to change the player's force + -- For now, they will (probably) always be on the 'player' force when this event fires. + local data = {} + if FREE_SAMPLES ~= 0 then + data['pending_samples'] = table.deepcopy(global.forcedata[player.force.name]['earned_samples']) + end + global.playerdata[player.index] = data + update_player(player.index) -- Attempt to send pending free samples, if relevant. +end +script.on_event(defines.events.on_player_created, on_player_created) + +function on_player_removed(event) + global.playerdata[event.player_index] = nil +end +script.on_event(defines.events.on_player_removed, on_player_removed) + +-- Updates a player, attempting to send them any pending samples (if relevant) +function update_player(index) + if FREE_SAMPLES == 0 then -- This is effectively a noop + return + end + local player = game.players[index] + if not player or not player.valid then -- Do nothing if we reference an invalid player somehow + return + end + local character = player.character or player.cutscene_character + if not character or not character.valid then + return + end + local data = global.playerdata[index] + local samples = data['pending_samples'] + local sent + --player.print(serpent.block(data['pending_samples'])) + local stack = {} + --SUPPRESS_INVENTORY_EVENTS = true + for name, count in pairs(samples) do + stack.name = name + stack.count = count + if character.can_insert(stack) then + sent = character.insert(stack) + else + sent = 0 + end + if sent > 0 then + player.print("Received " .. sent .. "x [item=" .. name .. "]") + data.suppress_full_inventory_message = false + end + if sent ~= count then -- Couldn't full send. + if not data.suppress_full_inventory_message then + player.print("Additional items will be sent when inventory space is available.", {r=1, g=1, b=0.25}) + end + data.suppress_full_inventory_message = true -- Avoid spamming them with repeated full inventory messages. + samples[name] = count - sent -- Buffer the remaining items + break -- Stop trying to send other things + else + samples[name] = nil -- Remove from the list + end + end + --SUPPRESS_INVENTORY_EVENTS = false +end + +-- Update players upon them connecting, since updates while they're offline are suppressed. +script.on_event(defines.events.on_player_joined_game, function(event) update_player(event.player_index) end) + +function update_player_event(event) + --if not SUPPRESS_INVENTORY_EVENTS then + update_player(event.player_index) + --end +end + +if FREE_SAMPLES then + script.on_event(defines.events.on_player_main_inventory_changed, update_player_event) +end + +function add_samples(force, name, count) + local function add_to_table(t) + t[name] = (t[name] or 0) + count + end + -- Add to global table of earned samples for future new players + add_to_table(global.forcedata[force.name]['earned_samples']) + -- Add to existing players + for _, player in pairs(force.players) do + add_to_table(global.playerdata[player.index]['pending_samples']) + update_player(player.index) + end +end + +script.on_init(function() + global.forcedata = {} + global.playerdata = {} + -- Fire dummy events for all currently existing forces. + local e = {} + for name, _ in pairs(game.forces) do + e.force = name + on_force_created(e) + end + e.force = nil + + -- Fire dummy events for all currently existing players. + for index, _ in pairs(game.players) do + e.player_index = index + on_player_created(e) + end end) -- for testing @@ -19,31 +143,32 @@ end) script.on_event(defines.events.on_research_finished, function(event) local technology = event.research dumpTech(technology.force) - {% if free_samples %} - local players = technology.force.players - if technology.effects then - for _, effect in pairs(technology.effects) do - if effect.type == "unlock-recipe" then - local recipe = game.recipe_prototypes[effect.recipe] - for _, result in pairs(recipe.products) do - if result.type == "item" and result.amount then - {% if free_samples == 1 %} - local new = {count=result.amount, name=result.name} - {% elif free_samples == 2 %} - local new = {count=get_any_stack_size(result.name) * 0.5, name=result.name} - {% else %} - local new = {count=get_any_stack_size(result.name), name=result.name} - {% endif %} - for _, player in pairs(players) do - player.insert(new) + if FREE_SAMPLES == 0 then + return -- Nothing else to do + end + if not technology.effects then + return -- No technology effects, so nothing to do. + end + for _, effect in pairs(technology.effects) do + if effect.type == "unlock-recipe" then + local recipe = game.recipe_prototypes[effect.recipe] + for _, result in pairs(recipe.products) do + if result.type == "item" and result.amount then + local name = result.name + local count + if FREE_SAMPLES == 1 then + count = result.amount + else + count = get_any_stack_size(result.name) + if FREE_SAMPLES == 2 then + count = math.ceil(count / 2) end end + add_samples(technology.force, name, count) end end end end - {% endif %} - end) function dumpTech(force) @@ -80,9 +205,9 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi local tech = force.technologies[tech_name] if tech ~= nil then if tech.researched ~= true then - tech.researched = true game.print({"", "Received [technology=" .. tech.name .. "] from ", source}) game.play_sound({path="utility/research_completed"}) + tech.researched = true end else game.print("Unknown Technology " .. tech_name)