From 9dbafd3b4b5946b3d4b6b237e08fd27249165ab0 Mon Sep 17 00:00:00 2001
From: CaitSith2 <d_good@caitsith2.com>
Date: Wed, 24 Nov 2021 01:55:36 -0800
Subject: [PATCH] Factorio can now change death link state at runtime.

---
 FactorioClient.py                             | 23 ++++----
 worlds/factorio/Mod.py                        |  8 ++-
 worlds/factorio/data/mod_template/control.lua | 55 ++++++++++++++-----
 .../data/mod_template/locale/en/locale.cfg    |  8 ++-
 .../factorio/data/mod_template/settings.lua   | 12 ++++
 5 files changed, 79 insertions(+), 27 deletions(-)
 create mode 100644 worlds/factorio/data/mod_template/settings.lua

diff --git a/FactorioClient.py b/FactorioClient.py
index 056dfc3f..292d926e 100644
--- a/FactorioClient.py
+++ b/FactorioClient.py
@@ -65,12 +65,11 @@ class FactorioContext(CommonContext):
         if password_requested and not self.password:
             await super(FactorioContext, self).server_auth(password_requested)
 
-        if not self.auth:
-            if self.rcon_client:
-                get_info(self, self.rcon_client)  # retrieve current auth code
-            else:
-                raise Exception("Cannot connect to a server with unknown own identity, "
-                                "bridge to Factorio first.")
+        if self.rcon_client:
+            await get_info(self, self.rcon_client)  # retrieve current auth code
+        else:
+            raise Exception("Cannot connect to a server with unknown own identity, "
+                            "bridge to Factorio first.")
 
         await self.send_connect()
 
@@ -126,6 +125,8 @@ async def game_watcher(ctx: FactorioContext):
                     research_data = data["research_done"]
                     research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
                     victory = data["victory"]
+                    if "death_link" in data:    # TODO: Remove this if statement around version 0.2.4 or so
+                        await ctx.update_death_link(data["death_link"])
 
                     if not ctx.finished_game and victory:
                         await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
@@ -140,7 +141,8 @@ async def game_watcher(ctx: FactorioContext):
                     death_link_tick = data.get("death_link_tick", 0)
                     if death_link_tick != ctx.death_link_tick:
                         ctx.death_link_tick = death_link_tick
-                        await ctx.send_death()
+                        if "DeathLink" in ctx.tags:
+                            await ctx.send_death()
 
             await asyncio.sleep(0.1)
 
@@ -226,14 +228,13 @@ async def factorio_server_watcher(ctx: FactorioContext):
         factorio_process.wait(5)
 
 
-def get_info(ctx, rcon_client):
+async def get_info(ctx, rcon_client):
     info = json.loads(rcon_client.send_command("/ap-rcon-info"))
     ctx.auth = info["slot_name"]
     ctx.seed_name = info["seed_name"]
     # 0.2.0 addition, not present earlier
     death_link = bool(info.get("death_link", False))
-    if death_link:
-        ctx.tags.add("DeathLink")
+    await ctx.update_death_link(death_link)
 
 
 async def factorio_spinup_server(ctx: FactorioContext) -> bool:
@@ -272,7 +273,7 @@ async def factorio_spinup_server(ctx: FactorioContext) -> bool:
                     rcon_client = factorio_rcon.RCONClient("localhost", rcon_port, rcon_password)
                     if ctx.mod_version == ctx.__class__.mod_version:
                         raise Exception("No Archipelago mod was loaded. Aborting.")
-                    get_info(ctx, rcon_client)
+                    await get_info(ctx, rcon_client)
             await asyncio.sleep(0.01)
 
     except Exception as e:
diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py
index b47d7ed4..3a5dc569 100644
--- a/worlds/factorio/Mod.py
+++ b/worlds/factorio/Mod.py
@@ -56,7 +56,7 @@ recipe_time_ranges = {
 def generate_mod(world, output_directory: str):
     player = world.player
     multiworld = world.world
-    global data_final_template, locale_template, control_template, data_template
+    global data_final_template, locale_template, control_template, data_template, settings_template
     with template_load_lock:
         if not data_final_template:
             mod_template_folder = os.path.join(os.path.dirname(__file__), "data", "mod_template")
@@ -66,6 +66,7 @@ def generate_mod(world, output_directory: str):
             data_final_template = template_env.get_template("data-final-fixes.lua")
             locale_template = template_env.get_template(r"locale/en/locale.cfg")
             control_template = template_env.get_template("control.lua")
+            settings_template = template_env.get_template("settings.lua")
     # get data for templates
     player_names = {x: multiworld.player_name[x] for x in multiworld.player_ids}
     locations = []
@@ -97,7 +98,7 @@ def generate_mod(world, output_directory: str):
                      "mod_name": mod_name, "allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
                      "tech_cost_scale": tech_cost_scale, "custom_technologies": multiworld.worlds[player].custom_technologies,
                      "tech_tree_layout_prerequisites": multiworld.tech_tree_layout_prerequisites[player],
-                     "slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name,
+                     "slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name, "slot_player": player,
                      "starting_items": multiworld.starting_items[player], "recipes": recipes,
                      "random": random, "flop_random": flop_random,
                      "static_nodes": multiworld.worlds[player].static_nodes,
@@ -121,6 +122,7 @@ def generate_mod(world, output_directory: str):
     control_code = control_template.render(**template_data)
     data_template_code = data_template.render(**template_data)
     data_final_fixes_code = data_final_template.render(**template_data)
+    settings_code = settings_template.render(**template_data)
 
     mod_dir = os.path.join(output_directory, mod_name + "_" + Utils.__version__)
     en_locale_dir = os.path.join(mod_dir, "locale", "en")
@@ -132,6 +134,8 @@ def generate_mod(world, output_directory: str):
         f.write(data_final_fixes_code)
     with open(os.path.join(mod_dir, "control.lua"), "wt") as f:
         f.write(control_code)
+    with open(os.path.join(mod_dir, "settings.lua"), "wt") as f:
+        f.write(settings_code)
     locale_content = locale_template.render(**template_data)
     with open(os.path.join(en_locale_dir, "locale.cfg"), "wt") as f:
         f.write(locale_content)
diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua
index 117fe7b9..d53a0b9c 100644
--- a/worlds/factorio/data/mod_template/control.lua
+++ b/worlds/factorio/data/mod_template/control.lua
@@ -9,7 +9,13 @@ SEED_NAME = "{{ seed_name }}"
 FREE_SAMPLE_BLACKLIST = {{ dict_to_lua(free_sample_blacklist) }}
 TRAP_EVO_FACTOR = {{ evolution_trap_increase }} / 100
 MAX_SCIENCE_PACK = {{ max_science_pack }}
-DEATH_LINK = {{ death_link | int }}
+ARCHIPELAGO_DEATH_LINK_SETTING = "archipelago-death-link-{{ slot_player }}-{{ seed_name }}"
+
+if settings.global[ARCHIPELAGO_DEATH_LINK_SETTING].value then
+    DEATH_LINK = 1
+else
+    DEATH_LINK = 0
+end
 
 CURRENTLY_DEATH_LOCK = 0
 
@@ -77,6 +83,27 @@ function on_force_destroyed(event)
     global.forcedata[event.force.name] = nil
 end
 
+function on_runtime_mod_setting_changed(event)
+    local force
+    if event.player_index == nil then
+        force = game.forces.player
+    else
+        force = game.players[event.player_index].force
+    end
+
+    if event.setting == ARCHIPELAGO_DEATH_LINK_SETTING then
+        if settings.global[ARCHIPELAGO_DEATH_LINK_SETTING].value then
+            DEATH_LINK = 1
+        else
+            DEATH_LINK = 0
+        end
+        if force ~= nil then
+            dumpInfo(force)
+        end
+    end
+end
+script.on_event(defines.events.on_runtime_mod_setting_changed, on_runtime_mod_setting_changed)
+
 -- 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)
@@ -382,18 +409,19 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
 end
 
 
-if DEATH_LINK == 1 then
-    script.on_event(defines.events.on_entity_died, function(event)
-        if CURRENTLY_DEATH_LOCK == 1 then -- don't re-trigger on same event
-            return
-        end
+script.on_event(defines.events.on_entity_died, function(event)
+    if DEATH_LINK == 0 then
+        return
+    end
+    if CURRENTLY_DEATH_LOCK == 1 then -- don't re-trigger on same event
+        return
+    end
 
-        local force = event.entity.force
-        global.forcedata[force.name].death_link_tick = game.tick
-        dumpInfo(force)
-        kill_players(force)
-    end, {LuaEntityDiedEventFilter = {["filter"] = "name", ["name"] = "character"}})
-end
+    local force = event.entity.force
+    global.forcedata[force.name].death_link_tick = game.tick
+    dumpInfo(force)
+    kill_players(force)
+end, {LuaEntityDiedEventFilter = {["filter"] = "name", ["name"] = "character"}})
 
 
 -- add / commands
@@ -408,7 +436,8 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress
     local data_collection = {
         ["research_done"] = research_done,
         ["victory"] = chain_lookup(global, "forcedata", force.name, "victory"),
-        ["death_link_tick"] = chain_lookup(global, "forcedata", force.name, "death_link_tick")
+        ["death_link_tick"] = chain_lookup(global, "forcedata", force.name, "death_link_tick"),
+        ["death_link"] = DEATH_LINK
     }
 
     for tech_name, tech in pairs(force.technologies) do
diff --git a/worlds/factorio/data/mod_template/locale/en/locale.cfg b/worlds/factorio/data/mod_template/locale/en/locale.cfg
index 25e9eb23..e970dbfa 100644
--- a/worlds/factorio/data/mod_template/locale/en/locale.cfg
+++ b/worlds/factorio/data/mod_template/locale/en/locale.cfg
@@ -22,4 +22,10 @@ ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends somet
 {%- else %}
 ap-{{ tech_table[original_tech_name] }}-=Researching this technology sends something to someone. For purposes of hints, this location is called "{{ original_tech_name }}".
 {%- endif -%}
-{% endfor %}
\ No newline at end of file
+{% endfor %}
+
+[mod-setting-name]
+archipelago-death-link-{{ slot_player }}-{{ seed_name }}=Death Link
+
+[mod-setting-description]
+archipelago-death-link-{{ slot_player }}-{{ seed_name }}=Kill other players in the same Archipelago Multiworld that also have Death Link turned on, when you die.
\ No newline at end of file
diff --git a/worlds/factorio/data/mod_template/settings.lua b/worlds/factorio/data/mod_template/settings.lua
new file mode 100644
index 00000000..7703ebe2
--- /dev/null
+++ b/worlds/factorio/data/mod_template/settings.lua
@@ -0,0 +1,12 @@
+data:extend({
+    {
+        type = "bool-setting",
+        name = "archipelago-death-link-{{ slot_player }}-{{ seed_name }}",
+        setting_type = "runtime-global",
+        {% if death_link %}
+            default_value = true
+        {% else %}
+            default_value = false
+        {% endif %}
+    }
+})
\ No newline at end of file