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 sys
import os
import typing
import websockets
@ -91,6 +92,7 @@ class ClientCommandProcessor(CommandProcessor):
class CommonContext():
tags:typing.Set[str] = {"AP"}
starting_reconnect_delay: int = 5
current_reconnect_delay: int = starting_reconnect_delay
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
init_logging("TextClient")
class TextContext(CommonContext):
tags = {"AP", "IgnoreGame"}
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(TextContext, self).server_auth(password_requested)
@ -499,10 +504,11 @@ if __name__ == '__main__':
await self.send_msgs([{"cmd": 'Connect',
'password': self.password, 'name': self.auth, 'version': Utils.version_tuple,
'tags': ['AP', 'IgnoreGame'],
'tags': self.tags,
'uuid': Utils.get_unique_identifier(), 'game': self.game
}])
async def main(args):
ctx = TextContext(args.connect, args.password)
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):
super(FactorioContext, self).__init__(server_address, password)
self.send_index = 0
self.send_index: int = 0
self.rcon_client = None
self.awaiting_bridge = False
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)
async def server_auth(self, password_requested: bool = False):
@ -76,13 +78,13 @@ class FactorioContext(CommonContext):
'password': self.password,
'name': self.auth,
'version': Utils.version_tuple,
'tags': ['AP'],
'tags': self.tags,
'uuid': Utils.get_unique_identifier(),
'game': "Factorio"
}])
def on_print(self, args: dict):
logger.info(args["text"])
super(FactorioContext, self).on_print(args)
if self.rcon_client:
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
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):
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]}")
ctx.locations_checked = 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)
except Exception as e:
@ -227,6 +246,10 @@ 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")
async def factorio_spinup_server(ctx: FactorioContext) -> bool:

View File

@ -122,7 +122,7 @@ class Context(CommonContext):
auth = base64.b64encode(self.rom).decode()
await self.send_msgs([{"cmd": 'Connect',
'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"
}])
@ -705,12 +705,6 @@ async def snes_flush_writes(ctx: Context):
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):
new_locations = []

View File

@ -489,7 +489,7 @@ async def on_client_connected(ctx: Context, client: Client):
'cmd': 'RoomInfo',
'password': bool(ctx.password),
'players': players,
'games': [ctx.games[x] for x in range(1, len(ctx.games)+1)],
'games': [ctx.games[x] for x in range(1, len(ctx.games) + 1)],
# tags are for additional features in the communication.
# Name them by feature or fork, as you feel is appropriate.
'tags': ctx.tags,
@ -1245,7 +1245,6 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
await ctx.send_msgs(client, reply)
elif cmd == "GetDataPackage":
exclusions = set(args.get("exclusions", []))
if exclusions:

View File

@ -73,7 +73,7 @@ def generate_mod(world, output_directory: str):
random = multiworld.slot_seeds[player]
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:
distance = random.random()
if random.randint(0, 1):

View File

@ -1,7 +1,7 @@
from __future__ import annotations
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
# schema helpers
@ -284,6 +284,11 @@ class ImportedBlueprint(DefaultOnToggle):
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)] = {
"max_science_pack": MaxSciencePack,
"tech_tree_layout": TechTreeLayout,
@ -300,4 +305,5 @@ factorio_options: typing.Dict[str, type(Option)] = {
"evolution_traps": EvolutionTrapCount,
"attack_traps": AttackTrapCount,
"evolution_trap_increase": EvolutionTrapIncrease,
"death_link": DeathLink
}

View File

@ -8,6 +8,9 @@ SLOT_NAME = "{{ slot_name }}"
SEED_NAME = "{{ seed_name }}"
FREE_SAMPLE_BLACKLIST = {{ dict_to_lua(free_sample_blacklist) }}
TRAP_EVO_FACTOR = {{ evolution_trap_increase }} / 100
DEATH_LINK = {{ death_link | int }}
CURRENTLY_DEATH_LOCK = 0
{% if not imported_blueprints -%}
function set_permissions()
@ -57,6 +60,7 @@ function on_force_created(event)
local data = {}
data['earned_samples'] = {{ dict_to_lua(starting_items) }}
data["victory"] = 0
data["death_link_tick"] = 0
global.forcedata[event.force] = data
{%- if silo == 2 %}
check_spawn_silo(force)
@ -250,6 +254,17 @@ function chain_lookup(table, ...)
return table
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)
local prototype = game.entity_prototypes[name]
@ -351,6 +366,20 @@ 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
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
commands.add_command("ap-sync", "Used by the Archipelago client to get progress information", function(call)
local force
@ -363,7 +392,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")
}
for tech_name, tech in pairs(force.technologies) do
if tech.researched and string.find(tech_name, "ap%-") == 1 then
@ -442,7 +472,7 @@ end)
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)
@ -453,5 +483,13 @@ end)
{% 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
progressive_technologies = {{ dict_to_lua(progressive_technology_table) }}