async def set_initiative_value(sid: str, data: ServerSetInitiativeValue): pr: PlayerRoom = game_state.get(sid) shape = Shape.get_or_none(uuid=data) if shape is not None and not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to remove initiative of an asset it does not own" ) return with db.atomic(): location_data = Initiative.get(location=pr.active_location) json_data = json.loads(location_data.data) for initiative in json_data: if initiative["shape"] == data["shape"]: initiative["initiative"] = data["value"] break json_data = sort_initiative(json_data, location_data.sort) location_data.data = json.dumps(json_data) location_data.save() await sio.emit( "Initiative.Set", location_data.as_dict(), room=pr.active_location.get_path(), namespace=GAME_NS, )
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 remove_initiative(sid: str, data: str): pr: PlayerRoom = game_state.get(sid) shape = Shape.get_or_none(uuid=data) if shape is not None and not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to remove initiative of an asset it does not own" ) return with db.atomic(): location_data = Initiative.get(location=pr.active_location) json_data = json.loads(location_data.data) location_data.data = json.dumps([ initiative for initiative in json_data if initiative["shape"] != data ]) location_data.save() await sio.emit( "Initiative.Remove", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_initiative_round(sid: str, data: int): pr: PlayerRoom = game_state.get(sid) location_data = Initiative.get(location=pr.active_location) if pr.role != Role.DM: json_data = json.loads(location_data.data) if not has_ownership( Shape.get_or_none(uuid=json_data[location_data.turn]["shape"]), pr): logger.warning( f"{pr.player.name} attempted to advance the initiative tracker" ) return with db.atomic(): location_data.round = data location_data.save() await sio.emit( "Initiative.Round.Update", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_shape_options(sid: str, data: OptionUpdateList): pr: PlayerRoom = game_state.get(sid) shapes: List[Tuple[Shape, OptionUpdate]] = [] for sh in data["options"]: shape = Shape.get_or_none(Shape.uuid == sh["uuid"]) if shape is not None and not has_ownership(shape, pr, movement=True): logger.warning( f"User {pr.player.name} attempted to change options for a shape it does not own." ) return shapes.append((shape, sh)) if not data["temporary"]: with db.atomic(): for db_shape, data_shape in shapes: db_shape.options = data_shape["option"] db_shape.save() await sio.emit( "Shapes.Options.Update", data["options"], room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_shape_position(sid: str, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) if data["temporary"] and not has_ownership_temp(data["shape"], pr): logger.warning( f"User {pr.player.name} attempted to move a shape it does not own." ) return shape, layer = await _get_shape(data, pr) # Overwrite the old data with the new data if not data["temporary"]: if not has_ownership(shape, pr): logger.warning( f"User {pr.player.name} attempted to move a shape it does not own." ) return with db.atomic(): # Shape update_model_from_dict(shape, reduce_data_to_model(Shape, data["shape"])) shape.save() if shape.type_ == "polygon": # Subshape type_instance = shape.subtype # no backrefs on these tables type_instance.update_from_dict(data["shape"], ignore_unknown=True) type_instance.save() await sync_shape_update(layer, pr, data, sid, shape)
async def remove_initiative_effect(sid: str, data: ServerRemoveInitiativeEffectActor): pr: PlayerRoom = game_state.get(sid) if not has_ownership(Shape.get_or_none(uuid=data["shape"]), pr): logger.warning( f"{pr.player.name} attempted to remove an initiative effect") return location_data = Initiative.get(location=pr.active_location) with db.atomic(): json_data = json.loads(location_data.data) for initiative in json_data: if initiative["shape"] == data["shape"]: initiative["effects"].pop(data["index"]) location_data.data = json.dumps(json_data) location_data.save() await sio.emit( "Initiative.Effect.Remove", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def set_locked(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) try: shape: Shape = Shape.get(uuid=data["shape"]) except Shape.DoesNotExist as exc: logger.warning( f"Attempt to update locked state of 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 locked state of a shape it does not own" ) return shape.is_locked = data["is_locked"] shape.save() await sio.emit( "Shape.Options.Locked.Set", data, skip_sid=sid, room=pr.active_location.get_path(), namespace=GAME_NS, )
async def update_shape_positions(sid: str, data: PositionUpdateList): pr: PlayerRoom = game_state.get(sid) shapes: List[Tuple[Shape, PositionUpdate]] = [] for sh in data["shapes"]: shape = Shape.get_or_none(Shape.uuid == sh["uuid"]) if shape is not None and not has_ownership(shape, pr, movement=True): logger.warning( f"User {pr.player.name} attempted to move a shape it does not own." ) return shapes.append((shape, sh)) if not data["temporary"]: with db.atomic(): for db_shape, data_shape in shapes: points = data_shape["position"]["points"] db_shape.x = points[0][0] db_shape.y = points[0][1] db_shape.angle = data_shape["position"]["angle"] db_shape.save() if len(points) > 1: # Subshape type_instance = db_shape.subtype type_instance.set_location(points[1:]) await sio.emit( "Shapes.Position.Update", data["shapes"], room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_initiative_option(sid: str, data: ServerInitiativeOption): pr: PlayerRoom = game_state.get(sid) shape = Shape.get_or_none(uuid=data["shape"]) if not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to change initiative of an asset it does not own" ) return location_data = Initiative.get_or_none(location=pr.active_location) if location_data is None: logger.error( "Initiative updated for location without initiative tracking") return json_data = json.loads(location_data.data) with db.atomic(): for i, initiative_data in enumerate(json_data): if initiative_data["shape"] == data["shape"]: json_data[i][data["option"]] = data["value"] break location_data.data = json.dumps(json_data) location_data.save() await sio.emit( "Initiative.Option.Set", data, skip_sid=sid, room=pr.active_location.get_path(), 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 remove_shapes(sid: str, data: TemporaryShapesList): pr: PlayerRoom = game_state.get(sid) if data["temporary"]: # This stuff is not stored so we cannot do any server side validation /shrug for shape in data["uuids"]: game_state.remove_temp(sid, shape) else: # Use the server version of the shapes. try: shapes: List[Shape] = [ s for s in Shape.select().where(Shape.uuid << data["uuids"]) ] except Shape.DoesNotExist: logger.warning( f"Attempt to update unknown shape by {pr.player.name}") return layer = shapes[0].layer group_ids = set() for shape in shapes: if not has_ownership(shape, pr): logger.warning( f"User {pr.player.name} tried to update a shape it does not own." ) return if shape.group: group_ids.add(shape.group) old_index = shape.index shape.delete_instance(True) Shape.update(index=Shape.index - 1).where((Shape.layer == layer) & (Shape.index >= old_index)).execute() for group_id in group_ids: await remove_group_if_empty(group_id) await sio.emit( "Shapes.Remove", data["uuids"], room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_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 update owner of 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 update unknown user as owner to shape by {pr.player.name} [{data['user']}]" ) return try: so = ShapeOwner.get(shape=shape, user=target_user) except ShapeOwner.DoesNotExist as exc: logger.warning( f"Attempt to update unknown shape-owner relation by {pr.player.name}" ) return so.shape = shape so.user = target_user so.edit_access = data["edit_access"] so.movement_access = data["movement_access"] so.vision_access = data["vision_access"] so.save() await sio.emit( "Shape.Owner.Update", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
def get_shape_or_none(pr: PlayerRoom, shape_id: str, action: str) -> Union[Shape, None]: try: shape: Shape = Shape.get(uuid=shape_id) except Shape.DoesNotExist as exc: logger.warning( f"Attempt by {pr.player.name} on unknown shape. {{method: {action}, shape id: {shape_id}}}" ) raise exc if not has_ownership(shape, pr): logger.warning( f"Attempt by {pr.player.name} on shape they do not own. {{method: {action}, shape id: {shape_id}}}" ) return None return shape
async def update_default_shape_owner(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) try: shape: Shape = Shape.get(uuid=data["shape"]) except Shape.DoesNotExist as exc: logger.warning( f"Attempt to update owner of 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 if "edit_access" in data: shape.default_edit_access = data["edit_access"] if "vision_access" in data: shape.default_vision_access = data["vision_access"] shape.save() await sio.emit( "Shape.Owner.Default.Update", data, room=pr.active_location.get_path(), skip_sid=sid, namespace="/planarally", ) if shape.default_vision_access or shape.default_edit_access: for sid, player in game_state.get_users( active_location=pr.active_location): await sio.emit( "Shape.Set", shape.as_dict(player, player.name == pr.room.creator), room=sid, namespace="/planarally", )
async def remove_initiative_effect(sid: str, data: ServerInitiativeEffectActor): pr: PlayerRoom = game_state.get(sid) if not has_ownership(Shape.get_or_none(uuid=data["actor"]), pr): logger.warning( f"{pr.player.name} attempted to remove an initiative effect") return with db.atomic(): effect = InitiativeEffect.get(uuid=data["effect"]["uuid"]) effect.delete_instance() await sio.emit( "Initiative.Effect.Remove", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_initiative_turn(sid: str, turn: int): pr: PlayerRoom = game_state.get(sid) location_data: Initiative = Initiative.get(location=pr.active_location) json_data = json.loads(location_data.data) if pr.role != Role.DM and not has_ownership( Shape.get_or_none(uuid=json_data[location_data.turn]["shape"]), pr): logger.warning( f"{pr.player.name} attempted to advance the initiative tracker") return with db.atomic(): nextTurn = turn > location_data.turn location_data.turn = turn for i, effect in enumerate(json_data[turn]["effects"][-1:]): try: turns = int(effect["turns"]) if turns <= 0 and nextTurn: json_data[turn]["effects"].pop(i) elif turns > 0 and nextTurn: effect["turns"] = str(turns - 1) else: effect["turns"] = str(turns + 1) except ValueError: # For non-number inputs do not update the effect pass location_data.data = json.dumps(json_data) location_data.save() await sio.emit( "Initiative.Turn.Update", turn, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_initiative_effect(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) if not has_ownership(Shape.get_or_none(uuid=data["actor"]), pr): logger.warning( f"{pr.player.name} attempted to update an initiative effect") return with db.atomic(): effect = InitiativeEffect.get(uuid=data["effect"]["uuid"]) update_model_from_dict( effect, reduce_data_to_model(InitiativeEffect, data["effect"])) effect.save() await sio.emit( "Initiative.Effect.Update", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def update_default_shape_owner(sid: str, data: ServerShapeDefaultOwner): pr: PlayerRoom = game_state.get(sid) try: shape: Shape = Shape.get(uuid=data["shape"]) except Shape.DoesNotExist as exc: logger.warning( f"Attempt to update owner of 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 if "edit_access" in data: shape.default_edit_access = data["edit_access"] if "vision_access" in data: shape.default_vision_access = data["vision_access"] if "movement_access" in data: shape.default_movement_access = data["movement_access"] shape.save() # We need to send each player their new view of the shape which includes the default access fields, # so there is no use in sending those separately for sid, player in game_state.get_users(active_location=pr.active_location, skip_sid=sid): await sio.emit( "Shape.Set", shape.as_dict(player, game_state.get(sid).role == Role.DM), room=sid, namespace=GAME_NS, ) await send_client_initiatives(pr, player)
async def new_initiative_effect(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) if not has_ownership(Shape.get_or_none(uuid=data["actor"]), pr): logger.warning( f"{pr.player.name} attempted to create a new initiative effect") return InitiativeEffect.create( initiative=data["actor"], uuid=data["effect"]["uuid"], name=data["effect"]["name"], turns=data["effect"]["turns"], ) await sio.emit( "Initiative.Effect.New", data, room=pr.active_location.get_path(), skip_sid=sid, namespace=GAME_NS, )
async def delete_shape_owner(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) try: shape = Shape.get(uuid=data["shape"]) except Shape.DoesNotExist as exc: logger.warning( f"Attempt to delete owner of 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 delete unknown user as owner to shape by {pr.player.name} [{data['user']}]" ) return try: so = (ShapeOwner.delete().where((ShapeOwner.shape == shape) & ( ShapeOwner.user == target_user)).execute()) except Exception as e: logger.warning( f"Could not delete shape-owner relation by {pr.player.name}") await sio.emit( "Shape.Owner.Delete", data, room=pr.active_location.get_path(), skip_sid=sid, namespace="/planarally", )
async def add_initiative(sid: str, data: ServerInitiativeData): pr: PlayerRoom = game_state.get(sid) shape = Shape.get_or_none(uuid=data) if shape is not None and not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to add initiative to an asset it does not own" ) return with db.atomic(): location_data, _ = Initiative.get_or_create( location=pr.active_location, defaults={ "round": 0, "turn": 0, "data": "[]" }) json_data = json.loads(location_data.data) for initiative in json_data: if initiative["shape"] == data["shape"]: initiative.update(**data) break else: json_data.append(data) location_data.data = json.dumps(json_data) location_data.save() await sio.emit( "Initiative.Set", location_data.as_dict(), room=pr.active_location.get_path(), namespace=GAME_NS, )
async def remove_initiative(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) shape = Shape.get_or_none(uuid=data) if shape is not None and not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to remove initiative of an asset it does not own" ) return initiative = Initiative.get_or_none(uuid=data) location_data = InitiativeLocationData.get_or_none( location=pr.active_location) if initiative: with db.atomic(): Initiative.update( index=Initiative.index - 1).where((Initiative.location_data == location_data) & (Initiative.index >= initiative.index)) initiative.delete_instance(True) await send_client_initiatives(pr)
async def update_shape(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) if data["temporary"] and not has_ownership_temp(data["shape"], pr): logger.warning( f"User {pr.player.name} tried to update a shape it does not own.") return # todo clean up this mess that deals with both temporary and non temporary shapes shape, layer = await _get_shape(data, pr) # Overwrite the old data with the new data if not data["temporary"]: if not has_ownership(shape, pr): logger.warning( f"User {pr.player.name} tried to update a shape it does not own." ) return with db.atomic(): # Shape update_model_from_dict(shape, reduce_data_to_model(Shape, data["shape"])) shape.save() # Subshape type_instance = shape.subtype # no backrefs on these tables type_instance.update_from_dict(data["shape"], ignore_unknown=True) type_instance.save() # Trackers old_trackers = {tracker.uuid for tracker in shape.trackers} new_trackers = { tracker["uuid"] for tracker in data["shape"]["trackers"] } for tracker_id in old_trackers | new_trackers: remove = tracker_id in old_trackers - new_trackers if not remove: tracker = next(tr for tr in data["shape"]["trackers"] if tr["uuid"] == tracker_id) reduced = reduce_data_to_model(Tracker, tracker) reduced["shape"] = shape if tracker_id in new_trackers - old_trackers: Tracker.create(**reduced) continue tracker_db = Tracker.get(uuid=tracker_id) if remove: tracker_db.delete_instance(True) else: update_model_from_dict(tracker_db, reduced) tracker_db.save() # Auras old_auras = {aura.uuid for aura in shape.auras} new_auras = {aura["uuid"] for aura in data["shape"]["auras"]} for aura_id in old_auras | new_auras: remove = aura_id in old_auras - new_auras if not remove: aura = next(au for au in data["shape"]["auras"] if au["uuid"] == aura_id) reduced = reduce_data_to_model(Aura, aura) reduced["shape"] = shape if aura_id in new_auras - old_auras: Aura.create(**reduced) continue aura_db = Aura.get_or_none(uuid=aura_id) if remove: aura_db.delete_instance(True) else: update_model_from_dict(aura_db, reduced) aura_db.save() # Labels for label in data["shape"]["labels"]: label_db = Label.get_or_none(uuid=label["uuid"]) reduced = reduce_data_to_model(Label, label) reduced["user"] = User.by_name(reduced["user"]) if label_db: update_model_from_dict(label_db, reduced) label_db.save() else: Label.create(**reduced) old_labels = { shape_label.label.uuid for shape_label in shape.labels } new_labels = set(label["uuid"] for label in data["shape"]["labels"]) for label in old_labels ^ new_labels: if label == "": continue if label in new_labels: ShapeLabel.create(shape=shape, label=Label.get(uuid=label)) else: ShapeLabel.get(label=Label.get(uuid=label), shape=shape).delete_instance(True) await sync_shape_update(layer, pr, data, sid, shape)
async def update_initiative(sid: int, data: Dict[str, Any]): pr: PlayerRoom = game_state.get(sid) shape = Shape.get_or_none(uuid=data["uuid"]) if not has_ownership(shape, pr): logger.warning( f"{pr.player.name} attempted to change initiative of an asset it does not own" ) return location_data = InitiativeLocationData.get_or_none( location=pr.active_location) if location_data is None: location_data = InitiativeLocationData.create( location=pr.active_location, turn=data["uuid"], round=1) initiatives = Initiative.select().where( Initiative.location_data == location_data) initiative = Initiative.get_or_none(uuid=data["uuid"]) # Create new initiative if initiative is None: with db.atomic(): # Update indices try: index = (initiatives.where( Initiative.initiative >= data["initiative"]).order_by( -Initiative.index)[0].index + 1) except IndexError: index = 0 else: Initiative.update( index=Initiative.index + 1).where((Initiative.location_data == location_data) & (Initiative.index >= index)) # Create model instance initiative = dict_to_model(Initiative, reduce_data_to_model(Initiative, data)) initiative.location_data = location_data initiative.index = index initiative.save(force_insert=True) # Update initiative else: with db.atomic(): if data["initiative"] != initiative.initiative: # Update indices old_index = initiative.index try: new_index = (initiatives.where( Initiative.initiative >= data["initiative"]).order_by( -Initiative.index)[0].index) except IndexError: new_index = 0 else: if new_index < old_index: new_index += 1 if old_index != new_index: # SIGN=1 IF old_index > new_index WHICH MEANS the initiative is increased # SIGN=-1 IF old_index < new_index WHICH MEANS the initiative is decreased sign = (old_index - new_index) // abs(old_index - new_index) indices = [0, old_index, new_index] update = Initiative.update( index=Initiative.index + sign).where((Initiative.location_data == location_data) & (Initiative.index <= indices[sign]) & (Initiative.index >= indices[-sign])) update.execute() data["index"] = new_index # Update model instance update_model_from_dict(initiative, reduce_data_to_model(Initiative, data)) initiative.save() data["index"] = initiative.index await send_client_initiatives(pr)