async def _destroy_instance(entity: Entity, parent_alias: str, entity_id_to_delete: int, force=False): from core.src.world.builder import world_repository if not await world_repository.entity_exists(entity_id_to_delete): await emit_msg(entity, 'Entity does not exists, use cleanup (bug)') return entity_to_delete = Entity(entity_id_to_delete) await load_components(entity_to_delete, (SystemComponent, 'instance_of', 'character')) system_component = entity_to_delete.get_component(SystemComponent) if system_component.character: await emit_msg(entity, 'Cannot destroy characters with this command') return if force != '--force': if system_component.instance_of and (parent_alias != system_component.instance_of): await emit_msg( entity, 'Entity {} is not type <{}>, it\'s <{}> instead'.format( entity_id_to_delete, parent_alias, system_component.instance_of)) return res = await world_repository.delete_entity(entity_id_to_delete) return res
async def emit_room_sys_msg(entity: Entity, event_type: str, details: typing.Dict, room=None, include_origin=True): from core.src.world.builder import transport assert isinstance(details, dict) room = room or (entity.get_room() or await get_current_room(entity)) listeners = await get_eligible_listeners_for_room(room) if listeners: listeners = listeners if not include_origin else [e for e in listeners if e.entity_id != entity.entity_id] listeners and ( await batch_load_components((SystemComponent, 'connection'), PositionComponent, entities=listeners) ) futures = [] for entity in listeners: position = entity.get_component(PositionComponent) if position.coord == room.position.coord and entity.get_component(SystemComponent).connection: payload = { "event": event_type, "target": "entity", "details": details, "position": room.position.value } futures.append( transport.send_system_event( entity.get_component(SystemComponent).connection.value, payload ) ) await asyncio.gather(*futures)
async def _test_stuff_struct_component(self): class TestComponent2(StructComponent): enum = ComponentTypeEnum.SYSTEM meta = ( ('weirdstuff', str), ('manystuffhere', list), ('integerrr', int), ('boolean', bool), ('a', dict) ) class TestComponent(StructComponent): enum = ComponentTypeEnum.INVENTORY meta = ( ('weirdstuff', str), ('manystuffhere', list), ('integerrr', int), ('boolean', bool), ('a', dict) ) indexes = ('weirdstuff', ) ent = Entity(444) c3 = TestComponent().weirdstuff.set('weirdstuff').a.set('key', 'value').manystuffhere.append(3) c4 = TestComponent2().weirdstuff.set('weirdstuff2').a.set('key', 'value2').manystuffhere.append(6) ent.set_for_update(c3).set_for_update(c4) await self.sut.update_entities(ent) res = await self.sut.read_struct_components_for_entity(444, TestComponent, TestComponent2) self.assertEqual(res[c3.enum].weirdstuff, 'weirdstuff') self.assertEqual(res[c3.enum].a, {'key': 'value'}) self.assertEqual(res[c4.enum].weirdstuff, 'weirdstuff2') self.assertEqual(res[c4.enum].a, {'key': 'value2'}) self.test_success = True
async def cast_entity( entity: Entity, position: PositionComponent, update=True, on_connect=False, reason=None ): assert isinstance(position, PositionComponent) from core.src.world.builder import events_subscriber_service, events_publisher_service loop = asyncio.get_event_loop() if update: current_position = await get_components_for_entity(entity, (PositionComponent, 'coord')) position.add_previous_position(current_position) entity = entity.set_for_update(position).set_room(Room(position)) update_response = await update_entities(entity.set_for_update(position)) if not update_response: LOGGER.core.error( 'Impossible to cast entity {}'.format(entity.entity_id)) return area = Area(position).make_coordinates() listeners = await get_eligible_listeners_for_area(area) entity.entity_id in listeners and listeners.remove(entity.entity_id) if on_connect: await events_publisher_service.on_entity_appear_position(entity, position, reason, targets=listeners) loop.create_task(events_subscriber_service.subscribe_events(entity)) else: pass await events_publisher_service.on_entity_change_position(entity, position, reason, targets=listeners) entity.set_component(position) return True
def move_entity_from_container( entity: Entity, target: (PositionComponent, InventoryComponent), current_owner: Entity = None ): current_position = entity.get_component(PositionComponent) if current_position.parent_of: assert current_owner and current_owner.entity_id == current_position.parent_of, ( current_owner and current_owner.entity_id, current_position.parent_of ) current_owner.get_component(InventoryComponent).content.remove(entity.entity_id) current_owner.set_for_update(current_owner.get_component(InventoryComponent)) if isinstance(target, InventoryComponent): target_owner = target.owned_by() new_position = PositionComponent().parent_of.set(target_owner.entity_id).coord.null() new_position.add_previous_position(current_position) target.content.append(entity.entity_id) target_owner.set_for_update(target) entity.set_for_update(new_position) elif isinstance(target, PositionComponent): assert target.coord.value new_position = PositionComponent().parent_of.null().coord.set(target.coord.value) entity.set_for_update(new_position) else: raise ValueError('Target must be type PosComponent or ContainerComponent, is: %s' % target) return entity
async def _test_selective_repo_queries(self): class TestComponent(StructComponent): enum = 1 key = enum meta = ( ('weirdstuff', str), ('manystuffhere', list), ('integerrr', int), ('boolean', bool), ('a', dict) ) c = TestComponent().weirdstuff.set('weirdstuff').a.set('key', 'value').manystuffhere.append(3) entity = Entity(123) entity.set_for_update(c) await self.sut.update_entities(entity) res = await self.sut.read_struct_components_for_entities([123], (TestComponent, 'weirdstuff')) r = res[123][c.enum] self.assertEqual(r.weirdstuff, 'weirdstuff') self.assertEqual(r.manystuffhere, []) self.assertEqual(r.integerrr, 0) self.assertEqual(r.boolean, False) self.assertEqual(r.a, {}) self.test_success = True
async def get_current_room(entity: Entity, populate=True): from core.src.world.builder import map_repository not entity.get_component(PositionComponent) and await load_components(entity, PositionComponent) room = await map_repository.get_room(entity.get_component(PositionComponent), populate=populate) populate and await room.populate_content() entity.set_room(room) return room
async def on_message(self, message: typing.Dict): assert message['c'] == 'cmd' if message['n'] not in self._enabled_channels: LOGGER.core.error('Error, message received on closed channel: %s', message) return try: data = message['d'].strip().split(' ') if not data: raise TypeError('Empty command?') entity = Entity(message['e_id'], itsme=True) entity.set_component(SystemComponent(connection=message['n'])) command = self._commands[data[0].lower()] if getattr(command, 'get_self', False): await self._commands[data[0]](entity, *data) else: await self._commands[data[0]](entity, *data[1:]) except KeyError as exc: if settings.RUNNING_TESTS: raise await self._on_error(message, "Command not found: %s" % data[0]) LOGGER.core.exception('Unhandled exception %s', exc) except TypeError as exc: if settings.RUNNING_TESTS: raise await self._on_error(message, "Command error: %s" % str(exc)) LOGGER.core.exception('Unhandled exception %s', exc) except Exception as exc: LOGGER.core.exception('Unhandled exception %s', exc) print(exc)
async def on_event(self, entity_id: int, message: typing.Dict, room: typing.Tuple, transport_id: str): room = PositionComponent(coord='{},{},{}'.format(*room)) entity = Entity(entity_id).set_component( SystemComponent().connection.set(transport_id)) await load_components(entity, PositionComponent) curr_pos = entity.get_component(PositionComponent) interest_type = await self._get_message_interest_type( entity, room, curr_pos) if not interest_type.value: return await self.publish_event(entity, message, room, interest_type, curr_pos)
async def clean_rooms_from_stales_instances(instance_type='character'): from core.src.world.components.system import SystemComponent from core.src.world.builder import world_repository from core.src.world.utils.entity_utils import batch_load_components from core.src.world.actions.system.disconnect import disconnect_entity from core.src.world.builder import map_repository entity_ids_with_connection_component_active = await world_repository.get_entity_ids_with_components_having_value( (SystemComponent, 'instance_of', instance_type) ) if not entity_ids_with_connection_component_active: return [] entities = [Entity(eid) for eid in entity_ids_with_connection_component_active] await batch_load_components(PositionComponent, SystemComponent, entities=entities) entities_without_connection_component_and_position = [ e for e in entities if not e.get_component(SystemComponent).connection and e.get_component(PositionComponent).coord ] rooms = await map_repository.get_rooms( *(e.get_component(PositionComponent) for e in entities_without_connection_component_and_position) ) stales = [] for i, room in enumerate(rooms): if entities_without_connection_component_and_position[i].entity_id in room.entity_ids: stales.append(entities_without_connection_component_and_position[i]) LOGGER.core.error('Error, found stales entities: %s' % str([x.entity_id for x in stales])) for entity in stales: await disconnect_entity(entity, msg=False)
async def _get_character_movement_message(self, entity, message, interest_type, curr_pos) -> typing.Dict: assert interest_type evaluated_emitter_entity = await load_components( Entity(message['en']), AttributesComponent) payload = { "event": "move", "entity": { "name": evaluated_emitter_entity.get_component( AttributesComponent).name, "id": message['en'] }, 'from': message['prev'], 'to': message['curr'] } if message['curr'] == curr_pos.list_coordinates: assert message['prev'] != curr_pos assert interest_type == InterestType.LOCAL payload['action'] = "join" payload['direction'] = self._gather_movement_direction( message, "join") elif message['prev'] == curr_pos.list_coordinates: assert message['curr'] != curr_pos assert interest_type != InterestType.LOCAL payload['action'] = "leave" payload['direction'] = self._gather_movement_direction( message, "leave") else: raise ValueError('This should not be here: %s (%s)' % (message, curr_pos.value)) return payload
async def create_character(sid, payload): token = auth_service.decode_session_token(payload['token']) assert token['context'] == 'world:create' system_component = SystemComponent()\ .instance_of.set('character')\ .created_at.set(int(time.time()))\ .receive_events.enable()\ .user_id.set(token['data']['user_id']) attributes = AttributesComponent().name.set(payload['name']).keyword.set('uomo') entity = Entity() \ .set_for_update(system_component) \ .set_for_update(attributes) entity = await world_repository.save_entity(entity) """ URGENT - TODO - FIX - Completely move characters allocation outside of SQL. Create a "UserID Component" to pair the character in the ECS with the ecosystem uuid, as we do for the connection. """ from core.src.auth.database import init_db, db init_db(db) character_id = psql_character_repository.store_new_character( token['data']['user_id'], payload["name"] ).character_id try: db.close() except: # FIXME - This shouldn't be here, but we miss the "store_new_character" HTTP endpoint yet. pass """ Fix ends here, probably """ redis_characters_index_repository.set_entity_id(character_id, entity.entity_id) await sio.emit('create', {'success': True, 'character_id': character_id}, to=sid)
async def look_at_target(entity: Entity, *arguments: str): if len(arguments) > 1: await emit_msg(entity, 'Command error - Nested targets not implemented yet') return target_entity = await search_entity_in_sight_by_keyword( entity, arguments[0]) if not target_entity: await emit_msg(entity, messages.missing_target()) elif entity.entity_id == target_entity.entity_id: await emit_msg(entity, messages.self_look()) elif not await ensure_same_position(entity, target_entity): await emit_msg(entity, messages.missing_target()) else: if await check_entity_can_receive_messages(target_entity): # Avoid to send messages to... knives, for example :-) await emit_msg( target_entity, messages.entity_looks_at_you( entity.get_component(AttributesComponent).keyword)) await emit_room_msg( origin=entity, target=target_entity, message_template=messages.entity_looks_at_entity_template()) await emit_msg( entity, messages.look_at_entity( target_entity.get_component(AttributesComponent).keyword)), await emit_sys_msg(entity, "look", target_entity)
async def populate_content(self): entities = [Entity(eid) for eid in self.entity_ids] if not entities: return self await batch_load_components(AttributesComponent, entities=entities) for entity in entities: self.add_entity(entity) return self
async def follow(entity: Entity, *arguments: str): from core.src.world.builder import follow_system_manager if not len(arguments): return await unfollow(entity) assert len(arguments) == 1 target_entity = await search_entity_in_sight_by_keyword( entity, arguments[0]) if not target_entity: await emit_msg(entity, messages.target_not_found()) elif entity.entity_id == target_entity.entity_id: if follow_system_manager.is_following_someone(entity.entity_id): await unfollow(entity) else: await emit_msg(entity, messages.not_following_anyone()) elif follow_system_manager.is_follow_repetition(entity.entity_id, target_entity.entity_id): await emit_msg(entity, messages.already_following_that_target()) elif follow_system_manager.is_follow_loop(entity, target_entity): await emit_msg(entity, messages.follow_is_loop()) elif not await ensure_same_position(entity, target_entity): await emit_msg(entity, messages.target_not_found()) else: previous_target = follow_system_manager.get_follow_target( entity.entity_id) if previous_target: await emit_msg( previous_target, messages.entity_stop_following_you( entity.get_component(AttributesComponent).keyword)) follow_system_manager.stop_following(entity.entity_id) follow_system_manager.follow_entity(entity.entity_id, target_entity.entity_id) await asyncio.gather( emit_msg( entity, messages.follow_entity( target_entity.get_component(AttributesComponent).keyword)), emit_msg( target_entity, messages.entity_is_following_you( entity.get_component(AttributesComponent).keyword))) await emit_room_msg( origin=entity, target=target_entity, message_template=messages.entity_follows_entity_template(), )
async def whoami(entity: Entity): await load_components(entity, AttributesComponent) await emit_msg( entity, json.dumps({ "event": "whoami", "id": entity.entity_id, "name": entity.get_component(AttributesComponent).name.value }))
async def on_message(self, message: typing.Dict): entity = Entity(message['e_id'], itsme=True).set_component( SystemComponent().connection.set(message['n'])) if message['c'] == 'connected': await self.on_connect(entity) elif message['c'] == 'disconnected': await self.on_disconnect(entity) else: raise ValueError('wtf?!')
async def get_room_at_direction(entity: Entity, direction_enum, populate=True): from core.src.world.builder import map_repository delta = direction_to_coords_delta(direction_enum) if not delta: return await load_components(entity, PositionComponent) look_cords = apply_delta_to_position(entity.get_component(PositionComponent), delta) room = await map_repository.get_room(look_cords, populate=populate) populate and await room.populate_content() return room
async def get_eligible_listeners_for_room(pos: (Room, PositionComponent)) -> typing.List[Entity]: """ Returns the list of entities ids that are ables to receive messages in the selected room. The "room" argument is a PosComponent or a Room Object. """ if isinstance(pos, Room): pos = pos.position from core.src.world.builder import map_repository entities_room = [Entity(e) for e in await map_repository.get_room_content(pos)] if not entities_room: return [] await batch_load_components((SystemComponent, 'receive_events'), entities=entities_room) return [e for e in entities_room if e.get_component(SystemComponent).receive_events]
async def on_disconnect(self, entity: Entity): current_connection = ( await self.world_repository.read_struct_components_for_entity( entity.entity_id, (SystemComponent, 'connection')))[SystemComponent.enum] if current_connection.connection.value != entity.get_component( SystemComponent).connection.value: return await disconnect_entity(entity, msg=False) self.events_subscriber_service.remove_observer_for_entity_id( entity.entity_id) self.manager.remove_transport(entity.entity_id) await self.events_subscriber_service.unsubscribe_all(entity)
async def drop(entity: Entity, keyword: str): await load_components(entity, PositionComponent, InventoryComponent) inventory = entity.get_component(InventoryComponent) items = await search_entities_in_container_by_keyword(inventory, keyword) msgs_stack = get_stacker() items_to_drop = [] for item in items: items_to_drop.append( move_entity_from_container( item, target=entity.get_component(PositionComponent), current_owner=entity)) if not items_to_drop: await emit_msg(entity, messages.target_not_found()) return entity.set_for_update(inventory) msgs_stack.add( emit_sys_msg(entity, 'remove_items', messages.items_to_message(items_to_drop)), emit_room_sys_msg(entity, 'add_items', messages.items_to_message(items_to_drop))) if len(items_to_drop) == 1: msgs_stack.add( emit_msg(entity, messages.on_drop_item(items[0])), emit_room_msg(origin=entity, message_template=messages.on_entity_drop_item( items[0]))) else: msgs_stack.add( emit_msg(entity, messages.on_drop_multiple_items()), emit_room_msg( origin=entity, message_template=messages.on_entity_drops_multiple_items())) if not await update_entities(entity, *items_to_drop): await emit_msg(entity, messages.target_not_found()) msgs_stack.cancel() else: await msgs_stack.execute()
async def check_entities_connection_status() -> typing.List[typing.Dict]: """ Check the match between the transport repository and the ECS status. If Entities with Connection component valued are found, but there is no match in the repository, the channel is removed from the ECS and the entity is removed from the room. Return details on the still active entities. """ from core.src.world.builder import world_repository from core.src.world.components.system import SystemComponent from core.src.world.builder import channels_repository from core.src.world.builder import map_repository, cmds_observer entity_ids_with_connection_component_active = await world_repository.get_entity_ids_with_valued_components( (SystemComponent, 'connection') ) if not entity_ids_with_connection_component_active: return [] entities = [Entity(eid) for eid in entity_ids_with_connection_component_active] await batch_load_components(PositionComponent, (SystemComponent, 'connection'), entities=entities) components_values = [] entities_by_id = {} for entity in entities: components_values.append(entity.get_component(SystemComponent).connection.value) entities_by_id[entity.entity_id] = entity to_update = [] online = [] if not components_values: return online channels = channels_repository.get_many(*components_values) for i, ch in enumerate(channels.values()): if not ch: entity = entities[i] to_update.append(entity) await map_repository.remove_entity_from_map( entity_ids_with_connection_component_active[i], entity.get_component(PositionComponent) ) else: cmds_observer.enable_channel(ch.id) online.append( { 'entity_id': entity_ids_with_connection_component_active[i], 'channel_id': ch.id } ) await world_repository.update_entities(*to_update) return online
async def on_connect(self, entity: Entity): connection_id = entity.get_component(SystemComponent).connection.value self.events_subscriber_service.add_observer_for_entity_id( entity.entity_id, self.pubsub_observer) await update_entities( entity.set_for_update( SystemComponent().connection.set(connection_id))) await load_components(entity, PositionComponent) if not entity.get_component(PositionComponent).coord: await cast_entity(entity, get_base_room_for_entity(entity), on_connect=True, reason="connect") self.loop.create_task(self.greet(entity)) else: await cast_entity(entity, entity.get_component(PositionComponent), update=False, on_connect=True, reason="connect") self.manager.set_transport(entity.entity_id, connection_id) self.commands_observer.enable_channel(connection_id) self.loop.create_task(look(entity)) self.loop.create_task(getmap(entity))
async def emit_room_msg(origin: Entity, message_template, target: Entity = None, room=None): """ Emit a room message in the same room of "origin". The room can be overridden with the room= keyword argument, accepting a Room type as input. The message template must have a mandatory {origin} and an optional {target} placeholders. origin and target parameters must be type Entity, with the AttributesComponent loaded. origin is mandatory, target is optional. The emitted message type is a string, the target is the client text field. """ from core.src.world.builder import transport room = room or (origin.get_room() or await get_current_room(origin)) listeners = await get_eligible_listeners_for_room(room) listeners = [ listener for listener in listeners if listener.entity_id not in ( origin and origin.entity_id, target and target.entity_id ) ] if not listeners: return await batch_load_components((SystemComponent, 'connection'), PositionComponent, entities=listeners) futures = [] for entity in listeners: position = entity.get_component(PositionComponent) if position.coord == room.position.coord and entity.get_component(SystemComponent).connection: # TODO - Evaluate VS entity memory futures.append( transport.send_message( entity.get_component(SystemComponent).connection.value, message_template.format( origin=origin.get_component(AttributesComponent).keyword, target=target and target.get_component(AttributesComponent).keyword ) ) ) await asyncio.gather(*futures)
async def _do_follow(self, follower_id: int, event: typing.Dict): current_followed_id = self._follow_by_follower.get(follower_id) if current_followed_id != event['entity']['id']: LOGGER.core.error('Error on follow system') return entity = await load_components(Entity(follower_id), SystemComponent, PositionComponent) if entity.get_component(PositionComponent).list_coordinates != event['from']: LOGGER.core.error('Error on follow system') return await do_move_entity( entity, Room(PositionComponent().set_list_coordinates(event['to'])), None, reason="movement", emit_message=False )
async def ensure_same_position(self_entity: Entity, *entities: Entity) -> bool: """ Ensures two or more entities are in the same position. Return a boolean. """ assert self_entity.itsme self_pos = self_entity.get_component(PositionComponent) await batch_load_components(PositionComponent, entities=entities) for e in entities: position = e.get_component(PositionComponent) if position.coord and position.coord != self_pos.coord: return False elif position.parent_of and position.parent_of != self_entity.entity_id: return False elif not position.parent_of and not position.coord: LOGGER.core.error('Cannot determinate if an item is in the same position as previous declared') return False return True
def get_instance_of(self, name: str, entity: Entity) -> typing.Optional[Entity]: e = Entity() data = self._local_copy.get(name) if not data: return system_component = SystemComponent()\ .instance_of.set(data['libname'])\ .created_at.set(int(time.time()))\ .instance_by.set(entity.entity_id) e.set_for_update(system_component) for component in data['components']: comp_type = get_component_by_type(component) if comp_type.has_default: e.set_for_update(comp_type().activate()) else: e.set_for_update( comp_type(value=data['components'][component])) return e
async def put(entity: Entity, keyword: str, target: str): await load_components(entity, PositionComponent, InventoryComponent) inventory = entity.get_component(InventoryComponent) items = await search_entities_in_container_by_keyword(inventory, keyword) target_entity = await search_entity_in_sight_by_keyword( entity, target, filter_by=InventoryComponent, include_self=False ) if not target_entity: await emit_msg(entity, messages.target_not_found()) return msgs_stack = get_stacker() items_to_drop = [] for item in items: items_to_drop.append( move_entity_from_container( item, target=target_entity.get_component(InventoryComponent), current_owner=entity ) ) if not items_to_drop: await emit_msg(entity, messages.target_not_found()) return msgs_stack.add( emit_sys_msg(entity, 'remove_items', messages.items_to_message(items_to_drop)), #emit_room_sys_msg(entity, 'add_items', messages.items_to_message(items_to_drop)) # todo event for container ) if len(items_to_drop) == 1: msgs_stack.add( emit_msg(entity, messages.on_put_item(items[0], target_entity)), emit_room_msg(origin=entity, message_template=messages.on_entity_put_item(items[0], target_entity)) ) else: msgs_stack.add( emit_msg(entity, messages.on_put_multiple_items(target_entity)), emit_room_msg(origin=entity, message_template=messages.on_entity_put_multiple_items(target_entity)) ) if not await update_entities(entity, target_entity, *items_to_drop): await emit_msg(entity, messages.target_not_found()) msgs_stack.cancel() else: await msgs_stack.execute()
async def _create_instance(entity: Entity, parent_alias: str, *args): from core.src.world.builder import world_repository, library_repository instanced = library_repository.get_instance_of(parent_alias, entity) if not instanced: await emit_msg(entity, 'Cannot obtain instance of {}'.format(parent_alias)) return if args: location = args[0] if location not in ('.', '@here'): await emit_msg( entity, 'Error, location {} invalid (allowed location: ".")'.format( location)) await load_components(entity, PositionComponent) instanced.set_for_update(entity.get_component(PositionComponent)) else: await emit_msg(entity, 'Inventory not implemented, please specify a location') await world_repository.save_entity(instanced) return instanced
def _check_bounds_for_update(pipeline: RedisLUAPipeline, entity: Entity): for bound in entity.bounds(): assert bound.is_struct assert bound.bounds for k, bb in bound.bounds.items(): for b in bb: if isinstance(b, StructSubtypeListAction): assert b.type == 'remove' key = 'c:{}:zs:e:{}:{}'.format(bound.enum, entity.entity_id, k) values = b.values if len(values) == 1: pipeline.allocate_value().zscan(key, cursor=0, match=values[0]) v, check = "[2][1]", str(values[0]) else: inter_seed = pipeline.zprepareinter(key, values) pipeline.allocate_value().zfetchinter(inter_seed) v, check = "", values pipeline.add_if_equal(check, value_selector=v) else: raise ValueError('Unknown bound') bound.remove_bounds()