Factorio: add DeathLink option

This commit is contained in:
Fabian Dill 2021-10-25 09:58:08 +02:00
parent c152790011
commit 4472ef20fe
7 changed files with 83 additions and 17 deletions

View File

@ -5,6 +5,7 @@ import asyncio
import urllib.parse import urllib.parse
import sys import sys
import os import os
import typing
import websockets import websockets
@ -91,6 +92,7 @@ class ClientCommandProcessor(CommandProcessor):
class CommonContext(): class CommonContext():
tags:typing.Set[str] = {"AP"}
starting_reconnect_delay: int = 5 starting_reconnect_delay: int = 5
current_reconnect_delay: int = starting_reconnect_delay current_reconnect_delay: int = starting_reconnect_delay
command_processor: int = ClientCommandProcessor command_processor: int = ClientCommandProcessor
@ -489,7 +491,10 @@ if __name__ == '__main__':
# Text Mode to use !hint and such with games that have no text entry # Text Mode to use !hint and such with games that have no text entry
init_logging("TextClient") init_logging("TextClient")
class TextContext(CommonContext): class TextContext(CommonContext):
tags = {"AP", "IgnoreGame"}
async def server_auth(self, password_requested: bool = False): async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password: if password_requested and not self.password:
await super(TextContext, self).server_auth(password_requested) await super(TextContext, self).server_auth(password_requested)
@ -499,10 +504,11 @@ if __name__ == '__main__':
await self.send_msgs([{"cmd": 'Connect', await self.send_msgs([{"cmd": 'Connect',
'password': self.password, 'name': self.auth, 'version': Utils.version_tuple, 'password': self.password, 'name': self.auth, 'version': Utils.version_tuple,
'tags': ['AP', 'IgnoreGame'], 'tags': self.tags,
'uuid': Utils.get_unique_identifier(), 'game': self.game 'uuid': Utils.get_unique_identifier(), 'game': self.game
}]) }])
async def main(args): async def main(args):
ctx = TextContext(args.connect, args.password) ctx = TextContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")

View File

@ -54,10 +54,12 @@ class FactorioContext(CommonContext):
def __init__(self, server_address, password): def __init__(self, server_address, password):
super(FactorioContext, self).__init__(server_address, password) super(FactorioContext, self).__init__(server_address, password)
self.send_index = 0 self.send_index: int = 0
self.rcon_client = None self.rcon_client = None
self.awaiting_bridge = False self.awaiting_bridge = False
self.write_data_path = None self.write_data_path = None
self.last_death_link: float = time.time() # last send/received death link on AP layer
self.death_link_tick: int = 0 # last send death link on Factorio layer
self.factorio_json_text_parser = FactorioJSONtoTextParser(self) self.factorio_json_text_parser = FactorioJSONtoTextParser(self)
async def server_auth(self, password_requested: bool = False): async def server_auth(self, password_requested: bool = False):
@ -76,13 +78,13 @@ class FactorioContext(CommonContext):
'password': self.password, 'password': self.password,
'name': self.auth, 'name': self.auth,
'version': Utils.version_tuple, 'version': Utils.version_tuple,
'tags': ['AP'], 'tags': self.tags,
'uuid': Utils.get_unique_identifier(), 'uuid': Utils.get_unique_identifier(),
'game': "Factorio" 'game': "Factorio"
}]) }])
def on_print(self, args: dict): def on_print(self, args: dict):
logger.info(args["text"]) super(FactorioContext, self).on_print(args)
if self.rcon_client: if self.rcon_client:
self.print_to_game(args['text']) self.print_to_game(args['text'])
@ -107,6 +109,12 @@ class FactorioContext(CommonContext):
self.rcon_client.send_commands({item_name: f'/ap-get-technology ap-{item_name}-\t-1' for self.rcon_client.send_commands({item_name: f'/ap-get-technology ap-{item_name}-\t-1' for
item_name in args["checked_locations"]}) item_name in args["checked_locations"]})
elif cmd == "Bounced":
if self.rcon_client:
tags = args.get("tags", [])
if "DeathLink" in tags and self.last_death_link != args["data"]["time"]:
self.rcon_client.send_command(f"/ap-deathlink {args['data']['source']}")
async def game_watcher(ctx: FactorioContext): async def game_watcher(ctx: FactorioContext):
bridge_logger = logging.getLogger("FactorioWatcher") bridge_logger = logging.getLogger("FactorioWatcher")
@ -139,6 +147,17 @@ async def game_watcher(ctx: FactorioContext):
f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}") f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data ctx.locations_checked = research_data
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}]) await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
death_link_tick = data.get("death_link_tick", 0)
if death_link_tick != ctx.death_link_tick:
ctx.death_link_tick = death_link_tick
ctx.last_death_link = time.time()
await ctx.send_msgs([{
"cmd": "Bounce", "tags": ["DeathLink"],
"data": {
"time": ctx.last_death_link,
"source": ctx.player_names[ctx.slot]
}
}])
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
except Exception as e: except Exception as e:
@ -227,6 +246,10 @@ def get_info(ctx, rcon_client):
info = json.loads(rcon_client.send_command("/ap-rcon-info")) info = json.loads(rcon_client.send_command("/ap-rcon-info"))
ctx.auth = info["slot_name"] ctx.auth = info["slot_name"]
ctx.seed_name = info["seed_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")
async def factorio_spinup_server(ctx: FactorioContext) -> bool: async def factorio_spinup_server(ctx: FactorioContext) -> bool:

View File

@ -122,7 +122,7 @@ class Context(CommonContext):
auth = base64.b64encode(self.rom).decode() auth = base64.b64encode(self.rom).decode()
await self.send_msgs([{"cmd": 'Connect', await self.send_msgs([{"cmd": 'Connect',
'password': self.password, 'name': auth, 'version': Utils.version_tuple, 'password': self.password, 'name': auth, 'version': Utils.version_tuple,
'tags': get_tags(self), 'tags': self.tags,
'uuid': Utils.get_unique_identifier(), 'game': "A Link to the Past" 'uuid': Utils.get_unique_identifier(), 'game': "A Link to the Past"
}]) }])
@ -705,12 +705,6 @@ async def snes_flush_writes(ctx: Context):
await snes_write(ctx, writes) await snes_write(ctx, writes)
# kept as function for easier wrapping by plugins
def get_tags(ctx: Context):
tags = ['AP']
return tags
async def track_locations(ctx: Context, roomid, roomdata): async def track_locations(ctx: Context, roomid, roomdata):
new_locations = [] new_locations = []

View File

@ -1245,7 +1245,6 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
await ctx.send_msgs(client, reply) await ctx.send_msgs(client, reply)
elif cmd == "GetDataPackage": elif cmd == "GetDataPackage":
exclusions = set(args.get("exclusions", [])) exclusions = set(args.get("exclusions", []))
if exclusions: if exclusions:

View File

@ -73,7 +73,7 @@ def generate_mod(world, output_directory: str):
random = multiworld.slot_seeds[player] random = multiworld.slot_seeds[player]
def flop_random(low, high, base=None): def flop_random(low, high, base=None):
"""Guarentees 50% bwlo base and 50% above base, uniform distribution in each direction.""" """Guarentees 50% below base and 50% above base, uniform distribution in each direction."""
if base: if base:
distance = random.random() distance = random.random()
if random.randint(0, 1): if random.randint(0, 1):

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import typing import typing
from Options import Choice, OptionDict, ItemDict, Option, DefaultOnToggle, Range from Options import Choice, OptionDict, ItemDict, Option, DefaultOnToggle, Range, Toggle
from schema import Schema, Optional, And, Or from schema import Schema, Optional, And, Or
# schema helpers # schema helpers
@ -284,6 +284,11 @@ class ImportedBlueprint(DefaultOnToggle):
displayname = "Blueprints" displayname = "Blueprints"
class DeathLink(Toggle):
"""When you die, everyone dies. Of course the reverse is true too."""
displayname = "Death Link"
factorio_options: typing.Dict[str, type(Option)] = { factorio_options: typing.Dict[str, type(Option)] = {
"max_science_pack": MaxSciencePack, "max_science_pack": MaxSciencePack,
"tech_tree_layout": TechTreeLayout, "tech_tree_layout": TechTreeLayout,
@ -300,4 +305,5 @@ factorio_options: typing.Dict[str, type(Option)] = {
"evolution_traps": EvolutionTrapCount, "evolution_traps": EvolutionTrapCount,
"attack_traps": AttackTrapCount, "attack_traps": AttackTrapCount,
"evolution_trap_increase": EvolutionTrapIncrease, "evolution_trap_increase": EvolutionTrapIncrease,
"death_link": DeathLink
} }

View File

@ -8,6 +8,9 @@ SLOT_NAME = "{{ slot_name }}"
SEED_NAME = "{{ seed_name }}" SEED_NAME = "{{ seed_name }}"
FREE_SAMPLE_BLACKLIST = {{ dict_to_lua(free_sample_blacklist) }} FREE_SAMPLE_BLACKLIST = {{ dict_to_lua(free_sample_blacklist) }}
TRAP_EVO_FACTOR = {{ evolution_trap_increase }} / 100 TRAP_EVO_FACTOR = {{ evolution_trap_increase }} / 100
DEATH_LINK = {{ death_link | int }}
CURRENTLY_DEATH_LOCK = 0
{% if not imported_blueprints -%} {% if not imported_blueprints -%}
function set_permissions() function set_permissions()
@ -57,6 +60,7 @@ function on_force_created(event)
local data = {} local data = {}
data['earned_samples'] = {{ dict_to_lua(starting_items) }} data['earned_samples'] = {{ dict_to_lua(starting_items) }}
data["victory"] = 0 data["victory"] = 0
data["death_link_tick"] = 0
global.forcedata[event.force] = data global.forcedata[event.force] = data
{%- if silo == 2 %} {%- if silo == 2 %}
check_spawn_silo(force) check_spawn_silo(force)
@ -250,6 +254,17 @@ function chain_lookup(table, ...)
return table return table
end end
function kill_players(force)
CURRENTLY_DEATH_LOCK = 1
local current_character = nil
for _, player in ipairs(force.players) do
current_character = player.character
if current_character ~= nil then
current_character.die()
end
end
CURRENTLY_DEATH_LOCK = 0
end
function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores) function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
local prototype = game.entity_prototypes[name] local prototype = game.entity_prototypes[name]
@ -351,6 +366,20 @@ function spawn_entity(surface, force, name, x, y, radius, randomize, avoid_ores)
end 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
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
-- add / commands -- add / commands
commands.add_command("ap-sync", "Used by the Archipelago client to get progress information", function(call) commands.add_command("ap-sync", "Used by the Archipelago client to get progress information", function(call)
local force local force
@ -363,6 +392,7 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress
local data_collection = { local data_collection = {
["research_done"] = research_done, ["research_done"] = research_done,
["victory"] = chain_lookup(global, "forcedata", force.name, "victory"), ["victory"] = chain_lookup(global, "forcedata", force.name, "victory"),
["death_link_tick"] = chain_lookup(global, "forcedata", force.name, "death_link_tick")
} }
for tech_name, tech in pairs(force.technologies) do for tech_name, tech in pairs(force.technologies) do
@ -442,7 +472,7 @@ end)
commands.add_command("ap-rcon-info", "Used by the Archipelago client to get information", function(call) commands.add_command("ap-rcon-info", "Used by the Archipelago client to get information", function(call)
rcon.print(game.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME})) rcon.print(game.table_to_json({["slot_name"] = SLOT_NAME, ["seed_name"] = SEED_NAME, ["death_link"] = DEATH_LINK}))
end) end)
@ -453,5 +483,13 @@ end)
{% endif -%} {% endif -%}
commands.add_command("ap-deathlink", "Kill all players", function(call)
local force = game.forces["player"]
local source = call.parameter or "Archipelago"
kill_players(force)
game.print("Death was granted by " .. source)
end)
-- data -- data
progressive_technologies = {{ dict_to_lua(progressive_technology_table) }} progressive_technologies = {{ dict_to_lua(progressive_technology_table) }}