Flask Socket-IO 自定义对象无论客户端操作如何均被添加
编辑:我发现我的问题根本不是出在Flask-SocketIO上。我的Player类指向了一个默认字典,每个对象实例都在修改这个默认字典,而不是创建一个新的字典。一旦系统允许我,我会标记为已解决。
我正在尝试用Flask-SocketIO构建一个小型的自定义文本角色扮演游戏引擎,作为客户端和服务器之间的通信接口。目前我想在玩家进入一个房间时,将“玩家”对象添加到“房间”对象的内容属性中(并在他们离开时移除这个对象)。但是,如果有多个客户端连接到服务器,一旦多个玩家开始移动,就会把每个玩家都添加到任何一个玩家进入的房间里。我觉得这可能和我使用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)
1 个回答
0
我自己解决了问题。问题出在每个房间对象实例都指向同一个默认字典,并且都在使用这个字典里的值。