async def set_player_role(sid: str, data: PlayerRoleChange): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning( f"{pr.player.name} attempted to change the role of a player") return new_role = Role(data["role"]) player_pr: PlayerRoom = PlayerRoom.get(player=data["player"], room=pr.room) creator: User = player_pr.room.creator if pr.player != creator and creator == player_pr.player: logger.warning( f"{pr.player.name} attempted to change the role of the campaign creator" ) return player_pr.role = new_role player_pr.save() for sid in game_state.get_sids(player=player_pr.player, room=pr.room): await sio.disconnect(sid, namespace=GAME_NS) for psid in game_state.get_sids(room=pr.room): if game_state.get(psid).role == Role.DM: await sio.emit("Player.Role.Set", data, room=psid, namespace=GAME_NS)
async def change_location(sid: int, data: Dict[str, str]): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to change location") return # Send an anouncement to show loading state for room_player in pr.room.players: if not room_player.player.name in data["users"]: continue for psid in game_state.get_sids(player=room_player.player, room=pr.room): await sio.emit("Location.Change.Start", room=psid, namespace="/planarally") new_location = Location[data["location"]] for room_player in pr.room.players: if not room_player.player.name in data["users"]: continue for psid in game_state.get_sids(player=room_player.player, room=pr.room): sio.leave_room( psid, room_player.active_location.get_path(), namespace="/planarally" ) sio.enter_room(psid, new_location.get_path(), namespace="/planarally") await load_location(psid, new_location) room_player.active_location = new_location room_player.save()
async def change_location(sid: str, data: LocationChangeData): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to change location") return # Send an anouncement to show loading state for room_player in pr.room.players: if not room_player.player.name in data["users"]: continue for psid in game_state.get_sids(player=room_player.player, room=pr.room): await sio.emit("Location.Change.Start", room=psid, namespace=GAME_NS) new_location = Location.get_by_id(data["location"]) for room_player in pr.room.players: if not room_player.player.name in data["users"]: continue for psid in game_state.get_sids(player=room_player.player, room=pr.room): try: sio.leave_room(psid, room_player.active_location.get_path(), namespace=GAME_NS) sio.enter_room(psid, new_location.get_path(), namespace=GAME_NS) except KeyError: await game_state.remove_sid(psid) continue await load_location(psid, new_location) # We could send this to all users in the new location, BUT # loading times might vary and we don't want to snap people back when they already move around # And it's possible that there are already users on the new location that don't want to be moved to this new position if "position" in data: await sio.emit( "Position.Set", data=data["position"], room=psid, namespace=GAME_NS, ) room_player.active_location = new_location room_player.save()
def get_owner_sids(pr: PlayerRoom, shape: Shape, skip_sid=None) -> Generator[str, None, None]: for psid in game_state.get_sids(active_location=pr.active_location, skip_sid=skip_sid): if has_ownership(shape, game_state.get(psid)): yield psid
async def claim_invite(request): user = await check_authorized(request) data = await request.json() room = Room.get_or_none(invitation_code=data["code"]) if room is None: return web.HTTPNotFound() else: if user != room.creator and not PlayerRoom.get_or_none(player=user, room=room): query = PlayerRoom.select().where(PlayerRoom.room == room) try: loc = query.where( PlayerRoom.role == Role.PLAYER)[0].active_location except IndexError: loc = query.where( PlayerRoom.role == Role.DM)[0].active_location PlayerRoom.create(player=user, room=room, role=Role.PLAYER, active_location=loc) for csid in game_state.get_sids(player=room.creator, room=room): await sio.emit( "Room.Info.Players.Add", { "id": user.id, "name": user.name }, room=csid, namespace="/planarally", ) return web.json_response({ "sessionUrl": f"/game/{urllib.parse.quote(room.creator.name, safe='')}/{urllib.parse.quote(room.name, safe='')}" })
async def set_name_visible(sid: str, data: ShapeSetBooleanValue): pr: PlayerRoom = game_state.get(sid) shape = get_shape_or_none(pr, data["shape"], "NameVisible.Set") if shape is None: return shape.name_visible = data["value"] shape.save() owners = [*get_owner_sids(pr, shape, skip_sid=sid)] await sio.emit( "Shape.Options.NameVisible.Set", data, skip_sid=sid, room=pr.active_location.get_path(), namespace=GAME_NS, ) for psid in game_state.get_sids(active_location=pr.active_location, skip_sid=sid): if psid in owners: continue await sio.emit( "Shape.Options.Name.Set", { "shape": shape.uuid, "value": shape.name if data["value"] else "?" }, room=psid, namespace=GAME_NS, )
async def create_aura(sid: str, data: AuraDelta): pr: PlayerRoom = game_state.get(sid) shape = get_shape_or_none(pr, data["shape"], "Aura.Create") if shape is None: return model = reduce_data_to_model(Aura, data) aura = Aura.create(**model) aura.save() owners = [*get_owner_sids(pr, shape, skip_sid=sid)] for psid in owners: await sio.emit( "Shape.Options.Aura.Create", data, room=psid, namespace=GAME_NS, ) if aura.visible: for psid in game_state.get_sids( active_location=pr.active_location, skip_sid=sid ): if psid in owners: continue await sio.emit( "Shape.Options.Aura.Create", data, room=psid, namespace=GAME_NS, )
async def refresh_invite_code(sid: str): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning( f"{pr.player.name} attempted to refresh the invitation code.") return pr.room.invitation_code = uuid.uuid4() pr.room.save() for room_player in pr.room.players: if room_player.role != Role.DM: continue for psid in game_state.get_sids( player=room_player.player, active_location=pr.active_location, ): await sio.emit( "Room.Info.InvitationCode.Set", str(pr.room.invitation_code), room=psid, namespace=GAME_NS, )
async def add_shape(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) if "temporary" not in data: data["temporary"] = False floor = pr.active_location.floors.select().where( Floor.name == data["shape"]["floor"])[0] layer = floor.layers.where(Layer.name == data["shape"]["layer"])[0] if pr.role != Role.DM and not layer.player_editable: logger.warning( f"{pr.player.name} attempted to add a shape to a dm layer") return if data["temporary"]: game_state.add_temp(sid, data["shape"]["uuid"]) else: with db.atomic(): data["shape"]["layer"] = layer data["shape"]["index"] = layer.shapes.count() # Shape itself shape = Shape.create(**reduce_data_to_model(Shape, data["shape"])) # Subshape type_table = get_table(shape.type_) type_table.create( shape=shape, **type_table.pre_create( **reduce_data_to_model(type_table, data["shape"])), ) # Owners for owner in data["shape"]["owners"]: ShapeOwner.create( shape=shape, user=User.by_name(owner["user"]), edit_access=owner["edit_access"], movement_access=owner["movement_access"], vision_access=owner["vision_access"], ) # Trackers for tracker in data["shape"]["trackers"]: Tracker.create(**reduce_data_to_model(Tracker, tracker), shape=shape) # Auras for aura in data["shape"]["auras"]: Aura.create(**reduce_data_to_model(Aura, aura), shape=shape) for room_player in pr.room.players: is_dm = room_player.role == Role.DM for psid in game_state.get_sids(player=room_player.player, active_location=pr.active_location): if psid == sid: continue if not is_dm and not layer.player_visible: continue if not data["temporary"]: data["shape"] = shape.as_dict(room_player.player, is_dm) await sio.emit("Shape.Add", data["shape"], room=psid, namespace=GAME_NS)
async def remove_shape(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) # We're first gonna retrieve the existing server side shape for some validation checks if data["temporary"]: if not has_ownership_temp(data["shape"], pr): logger.warning( f"User {pr.player.name} tried to update a shape it does not own." ) return # This stuff is not stored so we cannot do any server side validation /shrug shape = data["shape"] floor = pr.active_location.floors.select().where( Floor.name == data["shape"]["floor"])[0] layer = floor.layers.where(Layer.name == data["shape"]["layer"])[0] else: # Use the server version of the shape. try: shape = Shape.get(uuid=data["shape"]["uuid"]) except Shape.DoesNotExist: logger.warning( f"Attempt to update unknown shape by {pr.player.name}") return layer = shape.layer if not has_ownership(shape, pr): logger.warning( f"User {pr.player.name} tried to update a shape it does not own." ) return if data["temporary"]: game_state.remove_temp(sid, data["shape"]["uuid"]) else: old_index = shape.index shape.delete_instance(True) Shape.update(index=Shape.index - 1).where((Shape.layer == layer) & (Shape.index >= old_index)).execute() if layer.player_visible: await sio.emit( "Shape.Remove", data["shape"], room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, ) else: for csid in game_state.get_sids(player=pr.room.creator, active_location=pr.active_location): if csid == sid: continue await sio.emit("Shape.Remove", data["shape"], room=csid, namespace=GAME_NS)
async def add_shape_owner(sid: str, data: ServerShapeOwner): pr: PlayerRoom = game_state.get(sid) try: shape = Shape.get(uuid=data["shape"]) except Shape.DoesNotExist as exc: logger.warning( f"Attempt to add owner to unknown shape by {pr.player.name} [{data['shape']}]" ) raise exc if not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to change asset ownership of a shape it does not own" ) return target_user = User.by_name(data["user"]) if target_user is None: logger.warning( f"Attempt to add unknown user as owner to shape by {pr.player.name} [{data['user']}]" ) return # Adding the DM as user is redundant and can only lead to confusion if target_user == pr.room.creator: return if not ShapeOwner.get_or_none(shape=shape, user=target_user): ShapeOwner.create( shape=shape, user=target_user, edit_access=data["edit_access"], movement_access=data["movement_access"], vision_access=data["vision_access"], ) await send_client_initiatives(pr, target_user) await sio.emit( "Shape.Owner.Add", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, ) if not (shape.default_vision_access or shape.default_edit_access): for sid in game_state.get_sids( player=target_user, active_location=pr.active_location ): await sio.emit( "Shape.Set", shape.as_dict(target_user, False), room=sid, namespace=GAME_NS, )
async def set_locked_game_state(sid: str, is_locked: bool): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to set the locked game_state.") return pr.room.is_locked = is_locked pr.room.save() for psid in game_state.get_sids(room=pr.room): if game_state.get(psid).role != Role.DM: await sio.disconnect(psid, namespace=GAME_NS)
async def kick_player(sid: int, player_id: int): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to refresh the invitation code.") return pr = PlayerRoom.get_or_none(player=player_id, room=pr.room) if pr: for psid in game_state.get_sids(player=pr.player, room=pr.room): await sio.disconnect(psid, namespace=GAME_NS) pr.delete_instance(True)
async def update_aura(sid: str, data: AuraDelta): pr: PlayerRoom = game_state.get(sid) shape = get_shape_or_none(pr, data["shape"], "Aura.Update") if shape is None: return aura = Aura.get_by_id(data["uuid"]) changed_visible = aura.visible != data.get("visible", aura.visible) update_model_from_dict(aura, data) aura.save() owners = [*get_owner_sids(pr, shape, skip_sid=sid)] for psid in owners: await sio.emit( "Shape.Options.Aura.Update", data, room=psid, namespace=GAME_NS, ) for psid in game_state.get_sids(active_location=pr.active_location, skip_sid=sid): if psid in owners: continue if changed_visible: if aura.visible: await sio.emit( "Shape.Options.Aura.Create", { "shape": shape.uuid, **aura.as_dict() }, room=psid, namespace=GAME_NS, ) else: await sio.emit( "Shape.Options.Aura.Remove", { "shape": shape.uuid, "value": aura.uuid }, room=psid, namespace=GAME_NS, ) else: await sio.emit( "Shape.Options.Aura.Update", data, room=psid, namespace=GAME_NS, )
async def update_tracker(sid: str, data: TrackerDelta): pr: PlayerRoom = game_state.get(sid) shape = get_shape_or_none(pr, data["shape"], "Tracker.Update") if shape is None: return tracker = Tracker.get_by_id(data["uuid"]) changed_visible = tracker.visible != data.get("visible", tracker.visible) update_model_from_dict(tracker, data) tracker.save() owners = [*get_owner_sids(pr, shape, skip_sid=sid)] for psid in owners: await sio.emit( "Shape.Options.Tracker.Update", data, room=psid, namespace=GAME_NS, ) for psid in game_state.get_sids(active_location=pr.active_location, skip_sid=sid): if psid in owners: continue if changed_visible: if tracker.visible: await sio.emit( "Shape.Options.Tracker.Create", { "shape": shape.uuid, **tracker.as_dict() }, room=psid, namespace=GAME_NS, ) else: await sio.emit( "Shape.Options.Tracker.Remove", { "shape": shape.uuid, "value": tracker.uuid }, room=psid, namespace=GAME_NS, ) else: await sio.emit( "Shape.Options.Tracker.Update", data, room=psid, namespace=GAME_NS, )
async def add_filter(sid: str, uuid: str): pr: PlayerRoom = game_state.get(sid) label = Label.get_or_none(uuid=uuid) LabelSelection.create(label=label, user=pr.player, room=pr.room) for psid in game_state.get_sids(skip_sid=sid, room=pr.room): if game_state.get_user(psid) == pr.player: await sio.emit("Labels.Filter.Add", uuid, room=psid, namespace=GAME_NS)
async def rename_location(sid: int, data: Dict[str, str]): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to rename a location.") return location = Location[data["id"]] location.name = data["new"] location.save() for player_room in pr.room.players: for psid in game_state.get_sids(skip_sid=sid, player=player_room.player): await sio.emit("Location.Rename", data, room=psid, namespace="/planarally")
async def send_client_initiatives(pr: PlayerRoom, target_user: User = None, skip_sid=None) -> None: for room_player in pr.room.players: if target_user is None or target_user == room_player.player: for psid in game_state.get_sids(player=room_player.player, room=pr.room): if psid == skip_sid: continue await sio.emit( "Initiative.Set", get_client_initiatives(room_player.player, pr.active_location), room=psid, namespace="/planarally", )
async def remove_filter(sid: str, uuid: str): pr: PlayerRoom = game_state.get(sid) label = Label.get_or_none(uuid=uuid) ls = LabelSelection.get_or_none(label=label, room=pr.room, user=pr.player) if ls: ls.delete_instance(True) for psid in game_state.get_sids(skip_sid=sid, room=pr.room): if game_state.get_user(psid) == pr.player: await sio.emit("Labels.Filter.Remove", uuid, room=psid, namespace=GAME_NS)
async def unarchive_location(sid: str, location_id: int): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to unarchive a location.") return location = Location.get_by_id(location_id) location.archived = False location.save() for player_room in pr.room.players: for psid in game_state.get_sids(skip_sid=sid, player=player_room.player): await sio.emit("Location.Unarchive", location_id, room=psid, namespace=GAME_NS)
async def rename_location(sid: str, data: LocationRenameData): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to rename a location.") return location = Location.get_by_id(data["location"]) location.name = data["name"] location.save() for player_room in pr.room.players: for psid in game_state.get_sids(skip_sid=sid, player=player_room.player): await sio.emit("Location.Rename", data, room=psid, namespace=GAME_NS)
async def set_location_options(sid: str, data: LocationOptionsData): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to set a room option") return if data.get("location", None) is None: options = pr.room.default_options else: loc = Location.get_by_id(data["location"]) if loc.options is None: loc.options = LocationOptions.create( unit_size=None, unit_size_unit=None, grid_type=None, use_grid=None, full_fow=None, fow_opacity=None, fow_los=None, vision_mode=None, vision_min_range=None, vision_max_range=None, ) loc.save() options = loc.options update_model_from_dict(options, data["options"]) options.save() if data.get("location", None) is None: for sid in game_state.get_sids(skip_sid=sid, room=pr.room): await sio.emit("Location.Options.Set", data, room=sid, namespace=GAME_NS) else: await sio.emit( "Location.Options.Set", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def set_visibility(sid: str, data: LabelVisibilityMessage): pr: PlayerRoom = game_state.get(sid) label = Label.get_or_none(uuid=data["uuid"]) if label is None: logger.warn(f"{pr.player.name} tried to change a non-existing label.") return if label.user != pr.player: logger.warn(f"{pr.player.name} tried to change another user's label.") return label.visible = data["visible"] label.save() for psid in game_state.get_sids(skip_sid=sid, room=pr.room): if game_state.get_user(psid) == pr.player: await sio.emit( "Label.Visibility.Set", { "user": label.pr.player.name, **data }, room=psid, namespace=GAME_NS, ) else: if data["visible"]: await sio.emit("Label.Add", label.as_dict(), room=psid, namespace=GAME_NS) else: await sio.emit( "Label.Delete", { "uuid": label.uuid, "user": label.user.name }, room=psid, namespace=GAME_NS, )
async def set_locations_order(sid: int, locations: List[int]): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to reorder locations.") return for i, idx in enumerate(locations): l: Location = Location[idx] l.index = i + 1 l.save() for player_room in pr.room.players: if player_room.role != Role.DM: continue for psid in game_state.get_sids(skip_sid=sid, player=player_room.player): await sio.emit( "Locations.Order.Set", locations, room=psid, namespace=GAME_NS )
async def add_new_location(sid: str, location: str): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to add a new location") return new_location = Location.create(room=pr.room, name=location, index=pr.room.locations.count()) new_location.create_floor() for psid in game_state.get_sids(player=pr.player, active_location=pr.active_location): sio.leave_room(psid, pr.active_location.get_path(), namespace=GAME_NS) sio.enter_room(psid, new_location.get_path(), namespace=GAME_NS) await load_location(psid, new_location) pr.active_location = new_location pr.save()
async def kick_player(sid: str, player_id: int): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning(f"{pr.player.name} attempted to refresh the invitation code.") return pr = PlayerRoom.get_or_none(player=player_id, room=pr.room) if pr is None: return creator: User = pr.room.creator if pr.player != creator and creator == pr.player: logger.warning(f"{pr.player.name} attempted to kick the campaign creator") return for psid in game_state.get_sids(player=pr.player, room=pr.room): await sio.disconnect(psid, namespace=GAME_NS) pr.delete_instance(True)
async def move_shape_order(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) shape = Shape.get(uuid=data["shape"]["uuid"]) layer = shape.layer if pr.role != Role.DM and not layer.player_editable: logger.warning( f"{pr.player.name} attempted to move a shape order on a dm layer") return target = data["index"] sign = 1 if target < shape.index else -1 case = Case( None, ( (Shape.index == shape.index, target), ((sign * Shape.index) < (sign * shape.index), (Shape.index + (sign * 1))), ), Shape.index, ) Shape.update(index=case).where(Shape.layer == layer).execute() if layer.player_visible: await sio.emit( "Shape.Order.Set", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, ) else: for csid in game_state.get_sids(player=pr.room.creator, room=pr.room): if csid == sid: continue await sio.emit("Shape.Order.Set", data["shape"], room=csid, namespace=GAME_NS)
async def add(sid: str, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) label = Label.get_or_none(uuid=data) if label is not None: logger.warn( f"{pr.player.name} tried to add a label with an id that already exists." ) return if data["user"] != pr.player.name: logger.warn(f"{pr.player.name} tried to add a label for someone else.") return data["user"] = User.by_name(data["user"]) label = Label.create(**data) for psid in game_state.get_sids(skip_sid=sid, room=pr.room): if game_state.get_user(psid) == pr.player or label.visible: await sio.emit("Label.Add", label.as_dict(), room=psid, namespace=GAME_NS)
async def change_shape_layer(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) if pr.role != Role.DM: logger.warning( f"{pr.player.name} attempted to move the layer of a shape") return floor = Floor.get(location=pr.active_location, name=data["floor"]) layer = Layer.get(floor=floor, name=data["layer"]) shape = Shape.get(uuid=data["uuid"]) old_layer = shape.layer old_index = shape.index if old_layer.player_visible and not layer.player_visible: for room_player in pr.room.players: if room_player.role == Role.DM: continue for psid in game_state.get_sids( player=room_player.player, active_location=pr.active_location): if psid == sid: continue await sio.emit( "Shape.Remove", shape.as_dict(room_player.player, False), room=psid, namespace=GAME_NS, ) shape.layer = layer shape.index = layer.shapes.count() shape.save() Shape.update(index=Shape.index - 1).where((Shape.layer == old_layer) & (Shape.index >= old_index)).execute() if old_layer.player_visible and layer.player_visible: await sio.emit( "Shape.Layer.Change", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, ) else: for room_player in pr.room.players: is_dm = room_player.role == Role.DM for psid in game_state.get_sids( player=room_player.player, active_location=pr.active_location): if psid == sid: continue if is_dm: await sio.emit( "Shape.Layer.Change", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, ) elif layer.player_visible: await sio.emit( "Shape.Add", shape.as_dict(room_player.player, False), room=psid, namespace=GAME_NS, )