MultiServer: add !collect and collect_mode
CommonClient: make missing and checked location lookups faster FactorioClient: implement reverse grant technologies for collect/forfeit/coop
This commit is contained in:
parent
66e198cbb6
commit
34eba2655e
|
@ -117,8 +117,8 @@ class CommonContext():
|
|||
self.locations_checked: typing.Set[int] = set()
|
||||
self.locations_scouted: typing.Set[int] = set()
|
||||
self.items_received = []
|
||||
self.missing_locations: typing.List[int] = []
|
||||
self.checked_locations: typing.List[int] = []
|
||||
self.missing_locations: typing.Set[int] = set()
|
||||
self.checked_locations: typing.Set[int] = set()
|
||||
self.locations_info = {}
|
||||
|
||||
self.input_queue = asyncio.Queue()
|
||||
|
@ -389,8 +389,8 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
|
|||
# This list is used to only send to the server what is reported as ACTUALLY Missing.
|
||||
# This also serves to allow an easy visual of what locations were already checked previously
|
||||
# when /missing is used for the client side view of what is missing.
|
||||
ctx.missing_locations = args["missing_locations"]
|
||||
ctx.checked_locations = args["checked_locations"]
|
||||
ctx.missing_locations = set(args["missing_locations"])
|
||||
ctx.checked_locations = set(args["checked_locations"])
|
||||
|
||||
elif cmd == 'ReceivedItems':
|
||||
start_index = args["index"]
|
||||
|
|
|
@ -99,9 +99,9 @@ class FactorioContext(CommonContext):
|
|||
f"{text}")
|
||||
|
||||
def on_package(self, cmd: str, args: dict):
|
||||
if cmd == "Connected":
|
||||
if cmd in {"Connected", "RoomUpdate"}:
|
||||
# catch up sync anything that is already cleared.
|
||||
if args["checked_locations"]:
|
||||
if "checked_locations" in args and args["checked_locations"]:
|
||||
self.rcon_client.send_commands({item_name: f'/ap-get-technology ap-{item_name}-\t-1' for
|
||||
item_name in args["checked_locations"]})
|
||||
|
||||
|
|
|
@ -66,12 +66,14 @@ class Context:
|
|||
"password": str,
|
||||
"forfeit_mode": str,
|
||||
"remaining_mode": str,
|
||||
"collect_mode": str,
|
||||
"item_cheat": bool,
|
||||
"compatibility": int}
|
||||
|
||||
def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
|
||||
hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", remaining_mode: str = "disabled",
|
||||
auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2, log_network: bool = False):
|
||||
hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", collect_mode="disabled",
|
||||
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
|
||||
log_network: bool = False):
|
||||
super(Context, self).__init__()
|
||||
self.log_network = log_network
|
||||
self.endpoints = []
|
||||
|
@ -86,6 +88,7 @@ class Context:
|
|||
self.allow_forfeits = {}
|
||||
self.remote_items = set()
|
||||
self.remote_start_inventory = set()
|
||||
# player location_id item_id target_player_id
|
||||
self.locations: typing.Dict[int, typing.Dict[int, typing.Tuple[int, int]]] = {}
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
@ -102,6 +105,7 @@ class Context:
|
|||
self.hints: typing.Dict[team_slot, typing.Set[NetUtils.Hint]] = collections.defaultdict(set)
|
||||
self.forfeit_mode: str = forfeit_mode
|
||||
self.remaining_mode: str = remaining_mode
|
||||
self.collect_mode: str = collect_mode
|
||||
self.item_cheat = item_cheat
|
||||
self.running = True
|
||||
self.client_activity_timers: typing.Dict[
|
||||
|
@ -484,6 +488,7 @@ def get_permissions(ctx) -> typing.Dict[str, Permission]:
|
|||
return {
|
||||
"forfeit": Permission.from_text(ctx.forfeit_mode),
|
||||
"remaining": Permission.from_text(ctx.remaining_mode),
|
||||
"collect": Permission.from_text(ctx.collect_mode)
|
||||
}
|
||||
|
||||
|
||||
|
@ -558,11 +563,32 @@ def send_new_items(ctx: Context):
|
|||
client.send_index = len(items)
|
||||
|
||||
|
||||
def update_checked_locations(ctx: Context, team: int, slot: int):
|
||||
for client in ctx.endpoints:
|
||||
if client.team == team and client.slot == slot:
|
||||
ctx.send_msgs(client, [{"cmd": "RoomUpdate", "checked_locations": get_checked_checks(ctx, client)}])
|
||||
|
||||
|
||||
def forfeit_player(ctx: Context, team: int, slot: int):
|
||||
# register any locations that are in the multidata
|
||||
"""register any locations that are in the multidata"""
|
||||
all_locations = set(ctx.locations[slot])
|
||||
ctx.notify_all("%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
|
||||
register_location_checks(ctx, team, slot, all_locations)
|
||||
update_checked_locations(ctx, team, slot)
|
||||
|
||||
|
||||
def collect_player(ctx: Context, team: int, slot: int):
|
||||
"""register any locations that are in the multidata, pointing towards this player"""
|
||||
all_locations = collections.defaultdict(set)
|
||||
for source_slot, location_data in ctx.locations.items():
|
||||
for location_id, (item_id, target_player_id) in location_data.items():
|
||||
if target_player_id == slot:
|
||||
all_locations[source_slot].add(location_id)
|
||||
|
||||
ctx.notify_all("%s (Team #%d) has collected" % (ctx.player_names[(team, slot)], team + 1))
|
||||
for source_player, location_ids in all_locations.items():
|
||||
register_location_checks(ctx, team, source_player, location_ids)
|
||||
update_checked_locations(ctx, team, source_player)
|
||||
|
||||
|
||||
def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[int]:
|
||||
|
@ -889,6 +915,25 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
" You can ask the server admin for a /forfeit")
|
||||
return False
|
||||
|
||||
def _cmd_forfeit(self) -> bool:
|
||||
"""Send your remaining items to yourself"""
|
||||
if "enabled" in self.ctx.collect_mode:
|
||||
collect_player(self.ctx, self.client.team, self.client.slot)
|
||||
return True
|
||||
elif "disabled" in self.ctx.collect_mode:
|
||||
self.output(
|
||||
"Sorry, client collecting has been disabled on this server. You can ask the server admin for a /collect")
|
||||
return False
|
||||
else: # is auto or goal
|
||||
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
|
||||
collect_player(self.ctx, self.client.team, self.client.slot)
|
||||
return True
|
||||
else:
|
||||
self.output(
|
||||
"Sorry, client collecting requires you to have beaten the game on this server."
|
||||
" You can ask the server admin for a /collect")
|
||||
return False
|
||||
|
||||
def _cmd_remaining(self) -> bool:
|
||||
"""List remaining items in your game, but not their location or recipient"""
|
||||
if self.ctx.remaining_mode == "enabled":
|
||||
|
@ -982,7 +1027,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
|||
return True
|
||||
else:
|
||||
world = proxy_worlds[self.ctx.games[self.client.slot]]
|
||||
item_name, usable, response = get_intended_text(input_text, world.all_names if not explicit_location else world.location_names)
|
||||
item_name, usable, response = get_intended_text(input_text,
|
||||
world.all_names if not explicit_location else world.location_names)
|
||||
if usable:
|
||||
if item_name in world.hint_blacklist:
|
||||
self.output(f"Sorry, \"{item_name}\" is marked as non-hintable.")
|
||||
|
@ -1238,6 +1284,8 @@ def update_client_status(ctx: Context, client: Client, new_status: ClientStatus)
|
|||
forfeit_player(ctx, client.team, client.slot)
|
||||
elif proxy_worlds[ctx.games[client.slot]].forced_auto_forfeit:
|
||||
forfeit_player(ctx, client.team, client.slot)
|
||||
if "auto" in ctx.collect_mode:
|
||||
collect_player(ctx, client.team, client.slot)
|
||||
|
||||
ctx.client_game_state[client.team, client.slot] = new_status
|
||||
|
||||
|
@ -1317,9 +1365,21 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
|||
self.output(response)
|
||||
return False
|
||||
|
||||
@mark_raw
|
||||
def _cmd_collect(self, player_name: str) -> bool:
|
||||
"""Send out the remaining items to player."""
|
||||
seeked_player = player_name.lower()
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name.lower() == seeked_player:
|
||||
collect_player(self.ctx, team, slot)
|
||||
return True
|
||||
|
||||
self.output(f"Could not find player {player_name} to collect")
|
||||
return False
|
||||
|
||||
@mark_raw
|
||||
def _cmd_forfeit(self, player_name: str) -> bool:
|
||||
"""Send out the remaining items from a player's game to their intended recipients"""
|
||||
"""Send out the remaining items from a player to their intended recipients"""
|
||||
seeked_player = player_name.lower()
|
||||
for (team, slot), name in self.ctx.player_names.items():
|
||||
if name.lower() == seeked_player:
|
||||
|
@ -1423,7 +1483,7 @@ class ServerCommandProcessor(CommonCommandProcessor):
|
|||
return input_text
|
||||
setattr(self.ctx, option_name, attrtype(option))
|
||||
self.output(f"Set option {option_name} to {getattr(self.ctx, option_name)}")
|
||||
if option_name in {"forfeit_mode", "remaining_mode"}:
|
||||
if option_name in {"forfeit_mode", "remaining_mode", "collect_mode"}:
|
||||
self.ctx.broadcast_all([{"cmd": "RoomUpdate", 'permissions': get_permissions(self.ctx)}])
|
||||
return True
|
||||
else:
|
||||
|
@ -1469,6 +1529,15 @@ def parse_args() -> argparse.Namespace:
|
|||
goal: !forfeit can be used after goal completion
|
||||
auto-enabled: !forfeit is available and automatically triggered on goal completion
|
||||
''')
|
||||
parser.add_argument('--collect_mode', default=defaults["collect_mode"], nargs='?',
|
||||
choices=['auto', 'enabled', 'disabled', "goal", "auto-enabled"], help='''\
|
||||
Select !collect Accessibility. (default: %(default)s)
|
||||
auto: Automatic "collect" on goal completion
|
||||
enabled: !collect is always available
|
||||
disabled: !collect is never available
|
||||
goal: !collect can be used after goal completion
|
||||
auto-enabled: !collect is available and automatically triggered on goal completion
|
||||
''')
|
||||
parser.add_argument('--remaining_mode', default=defaults["remaining_mode"], nargs='?',
|
||||
choices=['enabled', 'disabled', "goal"], help='''\
|
||||
Select !remaining Accessibility. (default: %(default)s)
|
||||
|
@ -1523,7 +1592,8 @@ async def main(args: argparse.Namespace):
|
|||
format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||
|
||||
ctx = Context(args.host, args.port, args.server_password, args.password, args.location_check_points,
|
||||
args.hint_cost, not args.disable_item_cheat, args.forfeit_mode, args.remaining_mode,
|
||||
args.hint_cost, not args.disable_item_cheat, args.forfeit_mode, args.collect_mode,
|
||||
args.remaining_mode,
|
||||
args.auto_shutdown, args.compatibility, args.log_network)
|
||||
data_filename = args.multidata
|
||||
|
||||
|
|
1
Utils.py
1
Utils.py
|
@ -180,6 +180,7 @@ def get_default_options() -> dict:
|
|||
"location_check_points": 1,
|
||||
"hint_cost": 10,
|
||||
"forfeit_mode": "goal",
|
||||
"collect_mode": "disabled",
|
||||
"remaining_mode": "goal",
|
||||
"auto_shutdown": 0,
|
||||
"compatibility": 2,
|
||||
|
|
|
@ -48,7 +48,7 @@ class DBCommandProcessor(ServerCommandProcessor):
|
|||
|
||||
class WebHostContext(Context):
|
||||
def __init__(self):
|
||||
super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", 0, 2)
|
||||
super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", "enabled", 0, 2)
|
||||
self.main_loop = asyncio.get_running_loop()
|
||||
self.video = {}
|
||||
self.tags = ["AP", "WebHost"]
|
||||
|
|
|
@ -53,7 +53,7 @@ Sent to clients when they connect to an Archipelago server.
|
|||
| version | NetworkVersion | Object denoting the version of Archipelago which the server is running. See [NetworkVersion](#NetworkVersion) for more details. |
|
||||
| tags | list\[str\] | Denotes special features or capabilities that the sender is capable of. Example: `WebHost` |
|
||||
| password | bool | Denoted whether a password is required to join this room.|
|
||||
| permissions | dict\[str, Permission\[int\]\] | Mapping of permission name to [Permission](#Permission), keys are: "forfeit" and "remaining". |
|
||||
| permissions | dict\[str, Permission\[int\]\] | Mapping of permission name to [Permission](#Permission), keys are: "forfeit", "collect" and "remaining". |
|
||||
| hint_cost | int | The amount of points it costs to receive a hint from the server. |
|
||||
| location_check_points | int | The amount of hint points you receive per item/location check completed. ||
|
||||
| players | list\[NetworkPlayer\] | Sent only if the client is properly authenticated (see [Archipelago Connection Handshake](#Archipelago-Connection-Handshake)). Information on the players currently connected to the server. See [NetworkPlayer](#NetworkPlayer) for more details. |
|
||||
|
@ -61,7 +61,7 @@ Sent to clients when they connect to an Archipelago server.
|
|||
| datapackage_versions | dict[str, int] | Data versions of the individual games' data packages the server will send. |
|
||||
| seed_name | str | uniquely identifying name of this generation |
|
||||
|
||||
#### forfeit_mode
|
||||
#### forfeit
|
||||
Dictates what is allowed when it comes to a player forfeiting their run. A forfeit is an action which distributes the rest of the items in a player's run to those other players awaiting them.
|
||||
|
||||
* `auto`: Distributes a player's items to other players when they complete their goal.
|
||||
|
@ -70,7 +70,17 @@ Dictates what is allowed when it comes to a player forfeiting their run. A forfe
|
|||
* `disabled`: All forfeit modes disabled.
|
||||
* `goal`: Allows for manual use of forfeit command once a player completes their goal. (Disabled until goal completion)
|
||||
|
||||
#### remaining_mode
|
||||
#### collect
|
||||
Dictates what is allowed when it comes to a player collecting their run. A collect is an action which sends the rest of the items in a player's run.
|
||||
|
||||
* `auto`: Automatically when they complete their goal.
|
||||
* `enabled`: Denotes that players may !collect at any time in the game.
|
||||
* `auto-enabled`: Both of the above options together.
|
||||
* `disabled`: All collect modes disabled.
|
||||
* `goal`: Allows for manual use of collect command once a player completes their goal. (Disabled until goal completion)
|
||||
|
||||
|
||||
#### remaining
|
||||
Dictates what is allowed when it comes to a player querying the items remaining in their run.
|
||||
|
||||
* `goal`: Allows a player to query for items remaining in their run but only after they completed their own goal.
|
||||
|
@ -365,7 +375,7 @@ class Permission(enum.IntEnum):
|
|||
disabled = 0b000 # 0, completely disables access
|
||||
enabled = 0b001 # 1, allows manual use
|
||||
goal = 0b010 # 2, allows manual use after goal completion
|
||||
auto = 0b110 # 6, forces use after goal completion, only works for forfeit
|
||||
auto = 0b110 # 6, forces use after goal completion, only works for forfeit and collect
|
||||
auto_enabled = 0b111 # 7, forces use after goal completion, allows manual use any time
|
||||
```
|
||||
|
||||
|
|
11
host.yaml
11
host.yaml
|
@ -23,12 +23,21 @@ server_options:
|
|||
# so for example hint_cost: 20 would mean that for every 20% of available checks, you get the ability to hint, for a total of 5
|
||||
hint_cost: 10 # Set to 0 if you want free hints
|
||||
# Forfeit modes
|
||||
# A Forfeit sends out the remaining items *from* a world that forfeits
|
||||
# "disabled" -> clients can't forfeit,
|
||||
# "enabled" -> clients can always forfeit
|
||||
# "auto" -> automatic forfeit on goal completion, "goal" -> clients can forfeit after achieving their goal
|
||||
# "auto" -> automatic forfeit on goal completion
|
||||
# "auto-enabled" -> automatic forfeit on goal completion and manual forfeit is also enabled
|
||||
# "goal" -> forfeit is allowed after goal completion
|
||||
forfeit_mode: "goal"
|
||||
# Collect modes
|
||||
# A Collect sends the remaining items *to* a world that collects
|
||||
# "disabled" -> clients can't collect,
|
||||
# "enabled" -> clients can always collect
|
||||
# "auto" -> automatic collect on goal completion, "goal" -> clients can forfeit after achieving their goal
|
||||
# "auto-enabled" -> automatic collect on goal completion and collect forfeit is also enabled
|
||||
# "goal" -> collect is allowed after goal completion
|
||||
collect_mode: "disabled"
|
||||
# Remaining modes
|
||||
# !remaining handling, that tells a client which items remain in their pool
|
||||
# "enabled" -> Client can always ask for remaining items
|
||||
|
|
|
@ -168,7 +168,7 @@ class World(metaclass=AutoWorldRegister):
|
|||
pass
|
||||
|
||||
def get_required_client_version(self) -> Tuple[int, int, int]:
|
||||
return 0, 0, 3
|
||||
return 0, 1, 6
|
||||
|
||||
# end of Main.py calls
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ function on_force_created(event)
|
|||
local data = {}
|
||||
data['earned_samples'] = {{ dict_to_lua(starting_items) }}
|
||||
data["victory"] = 0
|
||||
data["checked_technologies"] = {}
|
||||
global.forcedata[event.force] = data
|
||||
{%- if silo == 2 %}
|
||||
check_spawn_silo(force)
|
||||
|
@ -200,7 +201,10 @@ end)
|
|||
script.on_event(defines.events.on_research_finished, function(event)
|
||||
local technology = event.research
|
||||
if technology.researched and string.find(technology.name, "ap%-") == 1 then
|
||||
dumpInfo(technology.force) --is sendable
|
||||
-- check if it came from the server anyway, then we don't need to double send.
|
||||
if global.forcedata[technology.force.name]["checked_technologies"][technology.name] ~= nil then
|
||||
dumpInfo(technology.force) --is sendable
|
||||
end
|
||||
else
|
||||
if FREE_SAMPLES == 0 then
|
||||
return -- Nothing else to do
|
||||
|
@ -389,6 +393,7 @@ commands.add_command("ap-get-technology", "Grant a technology, used by the Archi
|
|||
if index == -1 then -- for coop sync and restoring from an older savegame
|
||||
tech = force.technologies[item_name]
|
||||
if tech.researched ~= true then
|
||||
global.forcedata[force.name]["checked_technologies"][tech.name] = 1 -- mark as don't send again
|
||||
game.print({"", "Received [technology=" .. tech.name .. "] as it is already checked."})
|
||||
game.play_sound({path="utility/research_completed"})
|
||||
tech.researched = true
|
||||
|
|
Loading…
Reference in New Issue