无论客户端操作如何,Flask Socket-IO 自定义对象都会被添加

问题描述 投票:0回答:1

我正在尝试使用 Flask-SocketIO 作为客户端和服务器之间的通信接口来构建一个小型自定义文本 RPG 引擎。我目前正在尝试在玩家移入房间时将“玩家”对象添加到“房间”对象的内容属性(并在他们随后离开时删除该对象)。然而,如果多个客户端连接到服务器,一旦多个玩家开始移动,它就会将每个玩家添加到任何玩家进入的每个房间。我有一种感觉,它与我使用 Flask-SocketIO 事件处理将客户端命令传递给玩家/房间方法的方式有关,但我不确定到底发生了什么。我觉得每当客户端发送数据时,都会触发该方法为每个玩家将玩家添加到房间的内容中,但我还没有看到任何其他方法的任何重复。

玩家可以毫无错误地四处移动,并且说话和看起来都可以按预期工作。我在这里有点不知所措。非常感谢任何帮助或建议。

连接事件

client_list = [] #List of clients currently connected
world = objects.World() #Instatiating world class to hold all rooms, players, and characters

@socketio.on('connect')
def connect():
    active_player = current_user.accounts.filter(PlayerAccount.is_active == True).first() #Pulls the active player information
    if not active_player.player_info: #Checks to see if the active player is a new player
        player = objects.Player(id=active_player.id, name=active_player.player_name, description="A newborn player, fresh to the world.", account=active_player.user_id)
        print(f'id = {player.id}, name = {player.name}, description = {player.description}, health = {player.health}, level = {player.level}, stats = {player.stats}, location = {player.location}, inventory = {player.inventory}') #Creates a new player object
        active_player.player_info = dill.dumps(player) #Pickles and writes new player object to active player info
        active_player.save() #Saves pickled data to player database
    else:
        player = dill.loads(active_player.player_info) #Loads pickled data in to the player
    username = player.name
    location = player.location
    player.session_id = request.sid
    client_list.append(player.session_id)
    world.players.update({player.id: player})
    print(f'client list is {client_list}')
    print(f'players connected is {world.players}')
    session['player_id'] = player.id
    join_room(location)
    print(player.location)
    print(player, world.rooms[location].name, world.rooms[location].contents['Players'])
    socketio.emit('event', {'message': f'{username} has connected to the server'})

客户端发送命令的事件和方法路由函数

@socketio.event
def client(data):
    current_player = events.world.players[session.get('player_id')]
    current_room = events.world.rooms[current_player.location]
    content = {
        'player': current_player,
        'room': current_room,
        'command': data['command'],
        'data': data['data'],
    }

    if content['command'] == 'say':
        say(content['player'], content['data'])

    if content['command'] in ['move', 'go', 'north', 'south', 'east', 'west', 'n', 's', 'e', 'w']:
        if not content['data']:
            content['data'] = content['command']
        if content['data'] == 'n':
            content['data'] = 'north'
        if content['data'] == 's':
            content['data'] = 'south'
        if content['data'] == 'e':
            content['data'] = 'east'
        if content['data'] == 'w':
            content['data'] = 'west'
        move(player=content['player'], direction=content['data'], room=content['room'])

    if content['command'] == 'look' or content['command'] == 'l':
        look(player=content['player'], data=content['data'], room=content['room'])

    if content['command'] == 'test':
        test(content['player'], content['data'])

    if content['command'] == 'save':
        save(content['player'], content['data'])

def say(player, data):
    player.speak(data)

def move(player, direction, room):
    player.move(direction=direction, room=room)

def look(player, room, data=''):
    player.look(data=data, room=room)

世界、房间和玩家的对象类

class World():
    def __init__(self) -> None:
        with open('app/data/room_db.pkl', 'rb') as dill_file:
            rooms = dill.load(dill_file)
            self.rooms = rooms
            self.players = {}

    def world_test(self):
        print(f'World initialized with {self.rooms}')
        socketio.emit('event', {'message': self.rooms['0,0'].description})

    def world_save(self):
        with open('app/data/world_db.pkl', 'wb') as dill_file:
            dill.dump(self, dill_file)
        socketio.emit('event', {'message': 'world saved'})

    def room_save(self):
        with open('app/data/room_db.pkl', 'wb') as dill_file:
            dill.dump(self.rooms, dill_file)
        socketio.emit('event', {'message': 'rooms saved'})


#Overall class for any interactable object in the world
class Entity():
    def __init__(self, name, description) -> None:
        self.name = name #Shorthand name for an entity
        self.description = description #Every entity needs to be able to be looked at

    #Test function currently, but every entity needs to be able to describe itself when looked at
    def describe(self):
        pass

#Class for rooms. Rooms should contain all other objects (NPCs, Items, Players, anything else that gets added)
class Room(Entity):
    id = itertools.count()
    def __init__(self, name, description, position, exits, icon, contents={'NPCs': {}, 'Players': {}, 'Items': {}}) -> None:
        super().__init__(name, description)
        self.id = next(Room.id)
        self.position = position #Coordinates in the grid system for a room, will be used when a character moves rooms
        self.exits = exits #List of rooms that are connected to this room. Should be N,S,E,W but may expand so a player can "move/go shop or someting along those lines"
        self.icon = icon #Icon for the world map, should consist of two ASCII characters (ie: "/\" for a mountain)
        self.contents = contents #Dictionary containing all NPCs, Players, and Items currently in the room. Values will be modified depending on character movement, NPC generation, and item movement

    def describe_contents(self, caller):
        output = ''
        print('test')
        print(f'room contents is {self.contents["Players"]}')
        return output


#Broad class for any entity capable of independent and autonomous action that affects the world in some way
default_stats = {
'strength': 10,
'endurance': 10,
'intelligence': 10,
'wisdom': 10,
'charisma': 10,
'agility': 10
}
class Character(Entity):
    def __init__(self, name, description, health=100, level=1, location='0,0', stats=default_stats, deceased=False, inventory = []) -> None:
        super().__init__(name, description)
        self.health = health #All characters should have a health value
        self.level = level #All characters should have a level value
        self.location = location #All characters should have a location, reflecting their current room and referenced when moving
        self.stats = stats #All characters should have a stat block.
        self.deceased = deceased #Indicator of if a character is alive or not. If True, inventory can be looted
        self.inventory = inventory #List of items in character's inventory. May swap to a dictionary of lists so items can be placed in categories


#Class that users control to interact with the world. Unsure if I need to have this mixed in with the models side or if it would be easier to pickle the entire class and pass that to the database?
class Player(Character):
    def __init__(self, id, account, name, description, health=100, level=1, location='0,0', stats=default_stats, deceased=False, inventory=[]) -> None:
        super().__init__(name, description, health, level, location, stats, deceased, inventory)
        self.id = id
        self.account = account #User account associated with the player character
        self.session_id = '' #Session ID so messages can be broadcast to players without other members of a room or server seeing the message. Session ID is unique to every connection, so part of the connection process must be to assign the new value to the player's session_id

    def connection(self):
        events.world.rooms[self.location].contents['Players'].update({self.id: self})

    def disconnection(self):
        pass

    def look(self, data, room):
        if data == '':
            socketio.emit('event', {'message': self.location}, to=self.session_id)
            socketio.emit('event', {'message': room.description}, to=self.session_id)
            socketio.emit('event', {'message': room.describe_contents(self)}, to=self.session_id)
        else:
            socketio.emit('event', {'message': 'this will eventually be a call to a class\'s .description to return a look statement.'}, to=self.session_id)
    
    def speak(self, data):
        socketio.emit('event', {'message': f'{self.name} says "{data}"'}, room=self.location, include_self=False)
        socketio.emit('event', {'message': f'You say "{data}"'}, to=self.session_id)

    def move(self, direction, room):
        if direction not in room.exits:
            socketio.emit('event', {'message': 'You can\'t go that way.'}, to=self.session_id)
            return
        leave_room(self.location)
        socketio.emit('event', {'message': f'{self.name} moves towards the {direction}'}, room=self.location)
        if self.id in room.contents['Players']:
            print(f"{room.contents['Players'][self.id].name} removed from {room.name}, object: {id(room)}")
            print(events.world)
            del room.contents['Players'][self.id]
            print(room.contents)


        lat = int(self.location[:self.location.index(',')])
        lon = int(self.location[self.location.index(',')+1:])
        if direction == 'n' or direction == 'north':
            lon += 1
            socketio.emit('event', {'message': 'You move towards the north'}, to=self.session_id)
        if direction == 's' or direction == 'south':
            lon -= 1
            socketio.emit('event', {'message': 'You move towards the south'}, to=self.session_id)
        if direction == 'e' or direction == 'east':
            lat += 1
            socketio.emit('event', {'message': 'You move towards the east'}, to=self.session_id)
        if direction == 'w' or direction == 'west':
            lat -= 1
            socketio.emit('event', {'message': 'You move towards the west'}, to=self.session_id)

        new_location = f'{lat},{lon}'
        came_from = [i for i in events.world.rooms[new_location].exits if events.world.rooms[new_location].exits[i]==self.location]
        socketio.emit('event', {'message': f'{self.name} arrives from the {came_from[0]}'}, room=new_location)
        socketio.sleep(.5)
        self.location = new_location
        join_room(self.location)
        events.world.rooms[self.location].contents['Players'][self.id] = self
        socketio.emit('event', {'message': events.world.rooms[self.location].description}, to=self.session_id)
python flask game-engine flask-socketio
1个回答
0
投票

解决了我自己的问题。造成此问题的原因是每个房间对象实例都指向相同的默认字典并使用这些值。

© www.soinside.com 2019 - 2024. All rights reserved.