def test_game_info_data_hidding(self): ''' player hero always must show actual data enemy hero always must show data on statrt of the turn ''' self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) hero_1 = heroes_logic.load_hero(account_id=self.account_1.id) hero_2 = heroes_logic.load_hero(account_id=self.account_2.id) hero_1.pvp.set_energy(1) heroes_logic.save_hero(hero_1) hero_2.pvp.set_energy(2) heroes_logic.save_hero(hero_2) data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['account']['hero']['pvp']['energy'], 1) self.assertEqual(data['enemy']['hero']['pvp']['energy'], 0) hero_2.pvp.store_turn_data() heroes_logic.save_hero(hero_2) data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['enemy']['hero']['pvp']['energy'], 2)
def test_is_old__not_own_hero(self): self.assertFalse(form_game_info(self.account_1, is_own=False)['account']['is_old']) TimePrototype(turn_number=666).save() self.assertTrue(form_game_info(self.account_1, is_own=False)['account']['is_old']) heroes_logic.save_hero(heroes_logic.load_hero(account_id=self.account_1.id)) self.assertFalse(form_game_info(self.account_1, is_own=False)['account']['is_old'])
def test_is_old(self): self.assertFalse(form_game_info(self.account_1, is_own=True)['account']['is_old']) turn.set(666) self.assertTrue(form_game_info(self.account_1, is_own=True)['account']['is_old']) heroes_logic.save_hero(heroes_logic.load_hero(account_id=self.account_1.id)) self.assertFalse(form_game_info(self.account_1, is_own=True)['account']['is_old'])
def test_is_old__not_own_hero(self): self.assertFalse(form_game_info(self.account_1, is_own=False)['account']['is_old']) TimePrototype(turn_number=666).save() self.assertTrue(form_game_info(self.account_1, is_own=False)['account']['is_old']) heroes_logic.save_hero(heroes_logic.load_hero(account_id=self.account_1.id)) self.assertFalse(form_game_info(self.account_1, is_own=False)['account']['is_old'])
def test_is_old__not_own_hero(self): self.assertFalse( form_game_info(self.account_1, is_own=False)['account']['is_old']) TimePrototype(turn_number=666).save() self.assertTrue( form_game_info(self.account_1, is_own=False)['account']['is_old']) HeroPrototype.get_by_account_id(self.account_1.id).save() self.assertFalse( form_game_info(self.account_1, is_own=False)['account']['is_old'])
def test_not_own_hero_get_cached_data__not_cached(self): hero = heroes_logic.load_hero(account_id=self.account_1.id) with mock.patch('the_tale.game.heroes.objects.Hero.cached_ui_info_for_hero', mock.Mock(return_value=self.create_not_own_ui_info(hero))) as cached_ui_info_for_hero: with mock.patch('the_tale.game.heroes.objects.Hero.ui_info', mock.Mock(return_value=self.create_not_own_ui_info(hero))) as ui_info: form_game_info(self.account_1, is_own=False) self.assertEqual(cached_ui_info_for_hero.call_count, 1) self.assertEqual(cached_ui_info_for_hero.call_args, mock.call(account_id=self.account_1.id, recache_if_required=False, patch_turns=None, for_last_turn=True)) self.assertEqual(ui_info.call_count, 0)
def test_not_own_hero_get_cached_data__not_cached(self): hero = heroes_logic.load_hero(account_id=self.account_1.id) with mock.patch('the_tale.game.heroes.objects.Hero.cached_ui_info_for_hero', mock.Mock(return_value=self.create_not_own_ui_info(hero))) as cached_ui_info_for_hero: with mock.patch('the_tale.game.heroes.objects.Hero.ui_info', mock.Mock(return_value=self.create_not_own_ui_info(hero))) as ui_info: form_game_info(self.account_1, is_own=False) self.assertEqual(cached_ui_info_for_hero.call_count, 1) self.assertEqual(cached_ui_info_for_hero.call_args, mock.call(account_id=self.account_1.id, recache_if_required=False, patch_turns=None, for_last_turn=True)) self.assertEqual(ui_info.call_count, 0)
def test_game_info_data_hidding(self): ''' player hero always must show actual data enemy hero always must show data on statrt of the turn ''' self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) self.storage = LogicStorage() self.storage.load_account_data(self.account_1) self.storage.load_account_data(self.account_2) hero_1 = self.storage.accounts_to_heroes[self.account_1.id] hero_2 = self.storage.accounts_to_heroes[self.account_2.id] meta_action_battle = meta_actions.ArenaPvP1x1.create( self.storage, hero_1, hero_2) meta_action_battle.set_storage(self.storage) action_prototypes.ActionMetaProxyPrototype.create( hero=hero_1, _bundle_id=hero_1.actions.current_action.bundle_id, meta_action=meta_action_battle) action_prototypes.ActionMetaProxyPrototype.create( hero=hero_2, _bundle_id=hero_1.actions.current_action.bundle_id, meta_action=meta_action_battle) meta_action_battle.hero_1_pvp.set_energy(1) meta_action_battle.hero_2_pvp.set_energy(2) heroes_logic.save_hero(hero_1) heroes_logic.save_hero(hero_2) data = form_game_info(self.account_1, is_own=True) self.assertEqual( data['account']['hero']['action']['data']['pvp']['energy'], 1) self.assertEqual( data['enemy']['hero']['action']['data']['pvp']['energy'], 0) meta_action_battle.hero_2_pvp.store_turn_data() heroes_logic.save_hero(hero_2) data = form_game_info(self.account_1, is_own=True) self.assertEqual( data['enemy']['hero']['action']['data']['pvp']['energy'], 2)
def test_game_info_caching(self): self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) hero_1 = heroes_logic.load_hero(account_id=self.account_1.id) hero_2 = heroes_logic.load_hero(account_id=self.account_2.id) def get_ui_info(hero, **kwargs): if hero.id == hero_1.id: return {'actual_on_turn': hero_1.saved_at_turn, 'action': {'data': {'pvp__actual':'actual', 'pvp__last_turn':'last_turn'}}, 'changed_fields': [], 'ui_caching_started_at': 0} else: return self.create_not_own_ui_info(hero_2) with mock.patch('the_tale.game.heroes.objects.Hero.ui_info', get_ui_info): data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['account']['hero']['action']['data']['pvp'], 'actual') self.assertEqual(data['enemy']['hero']['action']['data']['pvp'], 'last_turn') self.assertFalse('pvp__actual' in data['account']['hero']['action']['data']['pvp']) self.assertFalse('pvp__last_turn' in data['account']['hero']['action']['data']['pvp']) self.assertFalse('pvp__actual' in data['enemy']['hero']['action']['data']['pvp']) self.assertFalse('pvp__last_turn' in data['enemy']['hero']['action']['data']['pvp']) self.assertNotEqual(data['enemy']['hero']['cards'], 'fake') self.assertEqual(data['enemy']['hero']['energy']['max'], 0) self.assertEqual(data['enemy']['hero']['energy']['value'], 0) self.assertEqual(data['enemy']['hero']['energy']['bonus'], 0) self.assertEqual(data['enemy']['hero']['energy']['discount'], 0)
def test_not_own_hero_get_cached_data(self): hero = heroes_logic.load_hero(account_id=self.account_1.id) with mock.patch( 'the_tale.game.heroes.objects.Hero.ui_info', mock.Mock(return_value=self.create_not_own_ui_info( hero))) as ui_info: data = form_game_info(self.account_1, is_own=False) self.assertEqual(data['account']['hero']['action']['data']['pvp'], 'last_turn') self.assertEqual(data['enemy'], None) self.assertFalse( 'pvp__actual' in data['account']['hero']['action']['data']['pvp']) self.assertFalse('pvp__last_turn' in data['account']['hero']['action'] ['data']['pvp']) self.assertNotEqual(data['account']['hero']['cards'], 'fake') self.assertEqual(data['account']['hero']['energy']['max'], 0) self.assertEqual(data['account']['hero']['energy']['value'], 0) self.assertEqual(data['account']['hero']['energy']['bonus'], 0) self.assertEqual(data['account']['hero']['energy']['discount'], 0) self.assertEqual(ui_info.call_count, 1) self.assertEqual(ui_info.call_args, mock.call(actual_guaranteed=False))
def test_own_hero_get_cached_data(self): hero = heroes_logic.load_hero(account_id=self.account_1.id) with mock.patch( 'the_tale.game.heroes.objects.Hero.cached_ui_info_for_hero', mock.Mock( return_value={ 'actual_on_turn': hero.saved_at_turn, 'pvp': 'actual', 'ui_caching_started_at': 0 })) as cached_ui_info_for_hero: with mock.patch( 'the_tale.game.heroes.objects.Hero.ui_info') as ui_info: data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['account']['hero']['pvp'], 'actual') self.assertEqual(data['enemy'], None) self.assertEqual(cached_ui_info_for_hero.call_count, 1) self.assertEqual( cached_ui_info_for_hero.call_args, mock.call(account_id=self.account_1.id, recache_if_required=True, patch_turns=None, for_last_turn=False)) self.assertEqual(ui_info.call_count, 0)
def test_game_info_caching(self): self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) hero_1 = heroes_logic.load_hero(account_id=self.account_1.id) hero_2 = heroes_logic.load_hero(account_id=self.account_2.id) def get_ui_info(hero, **kwargs): if hero.id == hero_1.id: return {'actual_on_turn': hero_1.saved_at_turn, 'pvp__actual':'actual', 'pvp__last_turn':'last_turn', 'changed_fields': [], 'ui_caching_started_at': 0} else: return self.create_not_own_ui_info(hero_2) with mock.patch('the_tale.game.heroes.objects.Hero.ui_info', get_ui_info): data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['account']['hero']['pvp'], 'actual') self.assertEqual(data['enemy']['hero']['pvp'], 'last_turn') self.assertFalse('pvp__actual' in data['account']['hero']['pvp']) self.assertFalse('pvp__last_turn' in data['account']['hero']['pvp']) self.assertFalse('pvp__actual' in data['enemy']['hero']['pvp']) self.assertFalse('pvp__last_turn' in data['enemy']['hero']['pvp']) self.assertNotEqual(data['enemy']['hero']['cards'], 'fake') self.assertEqual(data['enemy']['hero']['energy']['max'], 0) self.assertEqual(data['enemy']['hero']['energy']['value'], 0) self.assertEqual(data['enemy']['hero']['energy']['bonus'], 0) self.assertEqual(data['enemy']['hero']['energy']['discount'], 0)
def test_pvp_prepairing(self): self.pvp_create_battle(self.account_1, self.account_2, state=BATTLE_1X1_STATE.PREPAIRING) self.pvp_create_battle(self.account_2, self.account_1, state=BATTLE_1X1_STATE.PREPAIRING) data = form_game_info(self.account_1) self.assertEqual(data['mode'], 'pve') self.assertFalse(data['account']['in_pvp_queue']) self.assertEqual(data['enemy'], None)
def test_pvp_in_queue(self): self.pvp_create_battle(self.account_1, None) self.pvp_create_battle(self.account_2, None) data = form_game_info(self.account_1) self.assertEqual(data['mode'], 'pve') self.assertTrue(data['account']['in_pvp_queue']) self.assertEqual(data['enemy'], None)
def test_pvp_in_queue(self): self.pvp_create_battle(self.account_1, None) self.pvp_create_battle(self.account_2, None) data = form_game_info(self.account_1) self.assertEqual(data['mode'], 'pve') self.assertTrue(data['account']['in_pvp_queue']) self.assertEqual(data['enemy'], None)
def test_pvp_prepairing(self): self.pvp_create_battle(self.account_1, self.account_2, state=BATTLE_1X1_STATE.PREPAIRING) self.pvp_create_battle(self.account_2, self.account_1, state=BATTLE_1X1_STATE.PREPAIRING) data = form_game_info(self.account_1) self.assertEqual(data['mode'], 'pve') self.assertFalse(data['account']['in_pvp_queue']) self.assertEqual(data['enemy'], None)
def test_pvp_processing(self): self.pvp_create_battle(self.account_1, self.account_2, state=BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, state=BATTLE_1X1_STATE.PROCESSING) data = form_game_info(self.account_1) self.assertEqual(data['mode'], 'pvp') self.assertFalse(data['account']['in_pvp_queue']) self.assertFalse(data['enemy']['in_pvp_queue']) self.assertEqual(data['account']['id'], self.account_1.id) self.assertEqual(data['enemy']['id'], self.account_2.id)
def test_pvp_processing(self): self.pvp_create_battle(self.account_1, self.account_2, state=BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, state=BATTLE_1X1_STATE.PROCESSING) data = form_game_info(self.account_1) self.assertEqual(data['mode'], 'pvp') self.assertFalse(data['account']['in_pvp_queue']) self.assertFalse(data['enemy']['in_pvp_queue']) self.assertEqual(data['account']['id'], self.account_1.id) self.assertEqual(data['enemy']['id'], self.account_2.id)
def test_game_info_data_hidding(self): ''' player hero always must show actual data enemy hero always must show data on statrt of the turn ''' self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) self.storage = LogicStorage() self.storage.load_account_data(self.account_1) self.storage.load_account_data(self.account_2) hero_1 = self.storage.accounts_to_heroes[self.account_1.id] hero_2 = self.storage.accounts_to_heroes[self.account_2.id] meta_action_battle = meta_actions.ArenaPvP1x1.create(self.storage, hero_1, hero_2) meta_action_battle.set_storage(self.storage) action_prototypes.ActionMetaProxyPrototype.create(hero=hero_1, _bundle_id=hero_1.actions.current_action.bundle_id, meta_action=meta_action_battle) action_prototypes.ActionMetaProxyPrototype.create(hero=hero_2, _bundle_id=hero_1.actions.current_action.bundle_id, meta_action=meta_action_battle) meta_action_battle.hero_1_pvp.set_energy(1) meta_action_battle.hero_2_pvp.set_energy(2) heroes_logic.save_hero(hero_1) heroes_logic.save_hero(hero_2) data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['account']['hero']['action']['data']['pvp']['energy'], 1) self.assertEqual(data['enemy']['hero']['action']['data']['pvp']['energy'], 0) meta_action_battle.hero_2_pvp.store_turn_data() heroes_logic.save_hero(hero_2) data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['enemy']['hero']['action']['data']['pvp']['energy'], 2)
def test_own_hero_get_cached_data(self): hero = heroes_logic.load_hero(account_id=self.account_1.id) with mock.patch('the_tale.game.heroes.objects.Hero.cached_ui_info_for_hero', mock.Mock(return_value={'actual_on_turn': hero.saved_at_turn, 'pvp':'actual', 'ui_caching_started_at': 0})) as cached_ui_info_for_hero: with mock.patch('the_tale.game.heroes.objects.Hero.ui_info') as ui_info: data = form_game_info(self.account_1, is_own=True) self.assertEqual(data['account']['hero']['pvp'], 'actual') self.assertEqual(data['enemy'], None) self.assertEqual(cached_ui_info_for_hero.call_count, 1) self.assertEqual(cached_ui_info_for_hero.call_args, mock.call(account_id=self.account_1.id, recache_if_required=True, patch_turns=None, for_last_turn=False)) self.assertEqual(ui_info.call_count, 0)
def api_info(context): account = context.requested_account if account is None and context.account.is_authenticated: account = context.account data = game_logic.form_game_info(account=account, is_own=False if account is None else (context.account.id == account.id), client_turns=context.client_turns) if context.api_version in ('1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_9_to_1_8(data) if context.api_version in ('1.7', '1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_8_to_1_7(data) if context.api_version in ('1.6', '1.5', '1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_7_to_1_6(data) if context.api_version in ('1.5', '1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_6_to_1_5(data) if context.api_version in ('1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_5_to_1_4(data) if context.api_version in ('1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_4_to_1_3(data) if context.api_version in ('1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_3_to_1_2(data) if context.api_version in ('1.1', '1.0'): data = game_logic.game_info_from_1_2_to_1_1(data) if context.api_version == '1.0': data = game_logic.game_info_from_1_1_to_1_0(data) return dext_views.AjaxOk(content=data)
def test_not_own_hero_get_cached_data(self): hero = heroes_logic.load_hero(account_id=self.account_1.id) with mock.patch('the_tale.game.heroes.objects.Hero.ui_info', mock.Mock(return_value=self.create_not_own_ui_info(hero))) as ui_info: data = form_game_info(self.account_1, is_own=False) self.assertEqual(data['account']['hero']['action']['data']['pvp'], 'last_turn') self.assertEqual(data['enemy'], None) self.assertFalse('pvp__actual' in data['account']['hero']['action']['data']['pvp']) self.assertFalse('pvp__last_turn' in data['account']['hero']['action']['data']['pvp']) self.assertNotEqual(data['account']['hero']['cards'], 'fake') self.assertEqual(data['account']['hero']['energy']['max'], 0) self.assertEqual(data['account']['hero']['energy']['value'], 0) self.assertEqual(data['account']['hero']['energy']['bonus'], 0) self.assertEqual(data['account']['hero']['energy']['discount'], 0) self.assertEqual(ui_info.call_count, 1) self.assertEqual(ui_info.call_args, mock.call(actual_guaranteed=False))
def test_is_old__pvp(self): self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) hero_1 = heroes_logic.load_hero(account_id=self.account_1.id) hero_2 = heroes_logic.load_hero(account_id=self.account_2.id) self.assertFalse(form_game_info(self.account_1)['account']['is_old']) self.assertFalse(form_game_info(self.account_1)['enemy']['is_old']) TimePrototype(turn_number=666).save() self.assertTrue(form_game_info(self.account_1)['account']['is_old']) self.assertTrue(form_game_info(self.account_1)['enemy']['is_old']) heroes_logic.save_hero(hero_1) heroes_logic.save_hero(hero_2) self.assertFalse(form_game_info(self.account_1)['account']['is_old']) self.assertFalse(form_game_info(self.account_1)['enemy']['is_old'])
def test_is_old__pvp(self): self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) hero_1 = heroes_logic.load_hero(account_id=self.account_1.id) hero_2 = heroes_logic.load_hero(account_id=self.account_2.id) self.assertFalse(form_game_info(self.account_1)['account']['is_old']) self.assertFalse(form_game_info(self.account_1)['enemy']['is_old']) TimePrototype(turn_number=666).save() self.assertTrue(form_game_info(self.account_1)['account']['is_old']) self.assertTrue(form_game_info(self.account_1)['enemy']['is_old']) heroes_logic.save_hero(hero_1) heroes_logic.save_hero(hero_2) self.assertFalse(form_game_info(self.account_1)['account']['is_old']) self.assertFalse(form_game_info(self.account_1)['enemy']['is_old'])
def test_is_old__pvp(self): self.pvp_create_battle(self.account_1, self.account_2, BATTLE_1X1_STATE.PROCESSING) self.pvp_create_battle(self.account_2, self.account_1, BATTLE_1X1_STATE.PROCESSING) hero_1 = HeroPrototype.get_by_account_id(self.account_1.id) hero_2 = HeroPrototype.get_by_account_id(self.account_2.id) self.assertFalse(form_game_info(self.account_1)['account']['is_old']) self.assertFalse(form_game_info(self.account_1)['enemy']['is_old']) TimePrototype(turn_number=666).save() self.assertTrue(form_game_info(self.account_1)['account']['is_old']) self.assertTrue(form_game_info(self.account_1)['enemy']['is_old']) hero_1.save() hero_2.save() self.assertFalse(form_game_info(self.account_1)['account']['is_old']) self.assertFalse(form_game_info(self.account_1)['enemy']['is_old'])
def test_account__other(self): data = form_game_info(self.account_2, is_own=True) self.assertEqual(data['mode'], 'pve') self.assertFalse(data['account']['in_pvp_queue']) self.assertEqual(data['account']['id'], self.account_2.id) self.assertEqual(data['enemy'], None)
def api_info(context): ''' Информация о текущем ходе и герое - **адрес:** /game/api/info - **http-метод:** GET - **версии:** 1.5 - **параметры:** * GET: account — идентификатор аккаунта * GET: client_turns — номера ходов, по отношению к которым можно вернуть сокращённую информацию о герое (только изменённые с этого времени поля). - **возможные ошибки**: нет Если параметр account не будет указан, то вернётся информация об игре текущего пользователя, а на запрос от неавторизованного пользователя — общая информация об игре (без информации об аккаунте и герое). Часть информации в ответе является личной и доступна только залогиненному игроку, для остальных на её месте будет валидная с точки зрения формата заглушка. Такая информация обозначена следующим образом: **[личная информация]**. Полный ответ имеет большой размер, поэтому реализован следующий механизм его сжатия: - в параметре client_turns можно передать список номеров ходов (через запятую), для которых на клиенте есть полная информация; - если сервер сможет, в ответе он вернёт только изменившуюся информацию о герое; - сокращению подвергается только информация в <hero_info>; - сокращение происходит удалением неизменившихся полей <hero_info> (только на верхнем уровне, без рекурсии); - чтобы получить полную информацию, скопируйте недостаующие поля из закэшированной на стороне клиента информации для хода, указанного в .account.hero.patch_turn; - сервер не гарантирует, что вернёт сокращённую информацию; - сервер может вернуть патч для любого из переданных в client_turns ходов; - имеет смысл в параметре client_turns передавать последние 2-3 хода; - обратите внимание, сжатие ответа применяется и к информации о противнике в PvP! Поэтому первый запрос при PvP всегда должен требовать полную информацию. Формат данных в ответе: { "mode": "pve"|"pvp", // режим героя "turn": { // информация о номере хода "number": <целое число>, // номер хода "verbose_date": "строка", // дата для игроков (в мире Сказки) "verbose_time": "строка" // время для игроков (в мире Сказки) }, "game_state": <целое число>, // состояние игры (остановлена/запущена, см. в описании API) "map_version": "строка", // версия актуальной карты игры "account": <account_info>|null, // информация о запрашиваемом аккаунте и герое "enemy": <account_info>|null // информация о противнике, если идёт pvp сражение } <account_info> = { "new_messages": <целое число>, // количество личных сообщений "id": <целое число>, // идентификатор аккаунта "last_visit": <timestamp>, // примерное время последнего посещения игры "is_own": true|false, // информация о собственном герое или о чужом "is_old": true|false, // информация устаревшая или нет "hero": <hero_info>, // информация о герое "in_pvp_queue": true|false // находится ли герой в очереди на арену } <hero_info> = { "patch_turn": null|<целое число>, // номер хода, для которого возвращается патч или null, если информация полная "energy":{ // энергия игрока [личная информация] "bonus": <целое число>, // дополнительная энергия "max": <целое число>, // максимальное количество "value": <целое число>, // текущее количество "discount": <целое число> // скидка энергии при её трате (например, от использования артефактов) }, "equipment":{ // экипировка героя, словарь <идентификатор типа экипировки, информация об артефакте> "<целое число>": <artifact_info> // идентификатор типа экипировки: информация об артефакте }, "cards":{ // карты судьбы [личная информация] "cards": [ // список карт <card_info> // информация о карте ], "help_count": <целое число>, // сколько помощи накоплено для получения новой карты "help_barrier": <целое число> // сколько всего помощи надо накопить для новой карты }, "companion": <companion_info>|null,// информация о спутнике "bag":{ // содержимое рюкзака, словарь <внутренний идентификатор предмета, описание> () "<целое число>": <artifact_info> // идентификатор слота: информация об артефакте }, "base":{ // базовые параметры героя "experience": <целое число>, // текущий опыт "race": <целое число>, // раса "health": <целое число>, // здоровье "name": "строка", // имя героя "level": <целое число>, // уровень героя "gender": <целое число>, // пол "experience_to_level": <целое число>, // абсолютное количество опыта до следующего уровня "max_health": <целое число>, // максимальное количество здоровья "destiny_points": <целое число> // сколько способностей сейчас может выбрать "money": <целое число>, // количество денег у героя "alive": true|false, // жив герой или мёртв }, "secondary":{ // второстепенные параметры "max_bag_size": <целое число>, // максимальный размер рюкзака "power": [<целое число>, <целое число>], // физическая сила, магическая сила "move_speed": <дробное число>, // скорость движения "loot_items_count": <целое число>, // количество лута в рюкзаке "initiative": <дробное число> // инициатива героя }, "diary": "строка", // версия дневника героя, если она изменилась, необходимо перезапросить дневни "messages":[ // сообщения из журнала [ // запись в задании <timestamp>, // timestamp создания сообщения "строка", // текстовое описание времени в игре "строка", // текст <целое число>|null, // идентификатор типа фразы, найти идентификатор типа фразы можно в адресе страницы лингвистики с фразами этого типа {"строка": "строка"} // словарь соотношения переменных и их значений (ВНИМАНИЕ! перечень переменных может изменяться без изменения версии этого метода) ] ], "habits": { // черты "строка": { // идентификатор черты "verbose": "строка", // текущее текстовое значение черты для игрока (название характера) "raw": <дробное число> // текущее числовое значение черты } }, "quests": { // информация о заданиях "quests": [ // список глобальных заданий { "line": [ // список «базовых» заданий (цепочка последовательных заданий) { "type": "строка", // тип задания "uid": "строка", // уникальный идентификатор задания "name": "строка", // название задания "action": "строка", // описание текущего действия героя в задании "choice": "строка"|null, // текущий выбор героя в задании "choice_alternatives": [ // альтернативные выборы [ "строка", // уникальный идентификатор выбора "строка" // текстовое описание выбора ] ], "experience": <целое число>, // количество опыта за задание "power": <целое число>, // количество влияния за задание "actors": [ // список «актёров», участвующих в задании [ "строка", // название актёра <целое число>", // тип актёра (список типов приведён в описании API) <quest_actor_info> // данные, специфичные для конкретного типа актёра ] ] } ] } ] }, "action":{ // текущее действие "percents": <дробное число>, // процент выполнения "description": "строка", // описание "info_link": "url"|null // ссылка на доп. информацию "type": <целое число> // идентификатор типа действия "data": null|<словарь> // дополнительная информация о действиии или null, если такой нет }, "position":{ // позиция героя на клеточной карте "x": <дробное число>, // координата x "y": <дробное число>, // координата y "dx": <дробное число>, // направление взгляда по x "dy": <дробное число>, // направленеи взгляда по y }, "permissions": { // права на выполнение различных операций "can_participate_in_pvp": true|false, // может ли участвовать в pvp "can_repair_building": true|false, // может ли чинить здания }, "might": { // могущество игрока "value": <дробное число>, // величина "crit_chance": <дробное число>, // вероятность критического срабатывания помощи "pvp_effectiveness_bonus": <дробное число>, // бонус к эффективности в pvp от могущества "politics_power": <дробное число> // бонус к политическому влиянию героя }, "id": <целое число>, // идентификатор "actual_on_turn": <целое число>, // данные на какой ход предоставлены "sprite": <целое число> // идентификатор спрайта, которым отображается герой } <quest_actor_info> = <quest_actor_place_info>|<quest_actor_person_info>|<quest_actor_spending_info> <quest_actor_place_info> = { // информация о городе "id": <целое числое>, // идентификатор "name": "строка" // название города } <quest_actor_person_info> = { // информация о жителе города "id": <целое числое> // идентификатор "name": "строка", // имя "race": <целое числое>, // раса "gender": <целое числое>, // пол "profession": <целое числое>, // профессия "mastery_verbose": "строка", // профессия "place": <целое числое> // идентификатор города } <quest_actor_spending_info> = { // информация о целях накопления "goal": "строка" // описание цели накопления } <artifact_info> = { // информация об артефакте "name": "строка", // название "power": [<целое число>, <целое число>], // сила [физическая, магическая] "type": <целое число>, // тип "integrity": [<целое число>, <целое число>], // целостность [текущая, максимальная] "rarity": <целое число>, // редкость "effect": <целое число>, // тип эффекта на артефакте "special_effect": <целое число>, // тип особого свойства артефакта (эффекта, который действует независимо от редкости) "preference_rating": <дробное число>, // «полезность» артефакта с точки зрения героя "equipped": true|false, // может ли быть экипирован "id": <целое число> // уникальный идентификатор рода артефакта } <card_info> = { // информация о карте в колоде игрока "name": "строка", // название "type": <целое число>, // тип "rarity": <целое число>, // редкость карты "uid": <целое число>, // уникальный идентификатор в колоде игрока "auction": true|false // может быть продана на рынке } <companion_info> = { // информация о спутнике героя "type": <целое число>, // тип спутника "name": "строка", // название/имя спутника "health": <целое число>, // текущее здоровье "max_health": <целое число>, // максимальное здоровье "experience": <целое число>, // текущий опыт слаженности "experience_to_level": <целое число>, // опыта до следующего уровня слаженности "coherence": <целое число>, // текущая слаженность "real_coherence": <целое число> // полная слаженность (без учёта ограничений на максимум слаженности) } примечания: - если информация о герое устаревшая (is_old == true), то следует повторить запрос через несколько секунд (но лучше не злоупотреблять) ''' account = context.requested_account if account is None and context.account.is_authenticated: account = context.account data = game_logic.form_game_info(account=account, is_own=False if account is None else (context.account.id == account.id), client_turns=context.client_turns) if context.api_version in ('1.5', '1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_6_to_1_5(data) if context.api_version in ('1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_5_to_1_4(data) if context.api_version in ('1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_4_to_1_3(data) if context.api_version in ('1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_3_to_1_2(data) if context.api_version in ('1.1', '1.0'): data = game_logic.game_info_from_1_2_to_1_1(data) if context.api_version == '1.0': data = game_logic.game_info_from_1_1_to_1_0(data) return dext_views.AjaxOk(content=data)
def test_no_account(self): data = form_game_info() self.assertEqual(data['mode'], 'pve') self.assertEqual(data['account'], None) self.assertEqual(data['enemy'], None)
def test_account__other(self): data = form_game_info(self.account_2, is_own=True) self.assertEqual(data['mode'], 'pve') self.assertFalse(data['account']['in_pvp_queue']) self.assertEqual(data['account']['id'], self.account_2.id) self.assertEqual(data['enemy'], None)
def test_no_account(self): data = form_game_info() self.assertEqual(data['mode'], 'pve') self.assertEqual(data['account'], None) self.assertEqual(data['enemy'], None)
def api_info(context): ''' Информация о текущем ходе и герое - **адрес:** /game/api/info - **http-метод:** GET - **версии:** 1.5 - **параметры:** * GET: account — идентификатор аккаунта * GET: client_turns — номера ходов, по отношению к которым можно вернуть сокращённую информацию о герое (только изменённые с этого времени поля). - **возможные ошибки**: нет Если параметр account не будет указан, то вернётся информация об игре текущего пользователя, а на запрос от неавторизованного пользователя — общая информация об игре (без информации об аккаунте и герое). Часть информации в ответе является личной и доступна только залогиненному игроку, для остальных на её месте будет валидная с точки зрения формата заглушка. Такая информация обозначена следующим образом: **[личная информация]**. Полный ответ имеет большой размер, поэтому реализован следующий механизм его сжатия: - в параметре client_turns можно передать список номеров ходов (через запятую), для которых на клиенте есть полная информация; - если сервер сможет, в ответе он вернёт только изменившуюся информацию о герое; - сокращению подвергается только информация в <hero_info>; - сокращение происходит удалением неизменившихся полей <hero_info> (только на верхнем уровне, без рекурсии); - чтобы получить полную информацию, скопируйте недостаующие поля из закэшированной на стороне клиента информации для хода, указанного в .account.hero.patch_turn; - сервер не гарантирует, что вернёт сокращённую информацию; - сервер может вернуть патч для любого из переданных в client_turns ходов; - имеет смысл в параметре client_turns передавать последние 2-3 хода; - обратите внимание, сжатие ответа применяется и к информации о противнике в PvP! Поэтому первый запрос при PvP всегда должен требовать полную информацию. Формат данных в ответе: { "mode": "pve"|"pvp", // режим героя "turn": { // информация о номере хода "number": <целое число>, // номер хода "verbose_date": "строка", // дата для игроков (в мире Сказки) "verbose_time": "строка" // время для игроков (в мире Сказки) }, "game_state": <целое число>, // состояние игры (остановлена/запущена, см. в описании API) "map_version": "строка", // версия актуальной карты игры "account": <account_info>|null, // информация о запрашиваемом аккаунте и герое "enemy": <account_info>|null // информация о противнике, если идёт pvp сражение } <account_info> = { "new_messages": <целое число>, // количество личных сообщений "id": <целое число>, // идентификатор аккаунта "last_visit": <timestamp>, // примерное время последнего посещения игры "is_own": true|false, // информация о собственном герое или о чужом "is_old": true|false, // информация устаревшая или нет "hero": <hero_info>, // информация о герое "in_pvp_queue": true|false // находится ли герой в очереди на арену } <hero_info> = { "patch_turn": null|<целое число>, // номер хода, для которого возвращается патч или null, если информация полная "energy":{ // энергия игрока [личная информация] "bonus": <целое число>, // дополнительная энергия "max": <целое число>, // максимальное количество "value": <целое число>, // текущее количество "discount": <целое число> // скидка энергии при её трате (например, от использования артефактов) }, "equipment":{ // экипировка героя, словарь <идентификатор типа экипировки, информация об артефакте> "<целое число>": <artifact_info> // идентификатор типа экипировки: информация об артефакте }, "cards":{ // карты судьбы [личная информация] "cards": [ // список карт <card_info> // информация о карте ], "help_count": <целое число>, // сколько помощи накоплено для получения новой карты "help_barrier": <целое число> // сколько всего помощи надо накопить для новой карты }, "companion": <companion_info>|null,// информация о спутнике "bag":{ // содержимое рюкзака, словарь <внутренний идентификатор предмета, описание> () "<целое число>": <artifact_info> // идентификатор слота: информация об артефакте }, "base":{ // базовые параметры героя "experience": <целое число>, // текущий опыт "race": <целое число>, // раса "health": <целое число>, // здоровье "name": "строка", // имя героя "level": <целое число>, // уровень героя "gender": <целое число>, // пол "experience_to_level": <целое число>, // абсолютное количество опыта до следующего уровня "max_health": <целое число>, // максимальное количество здоровья "destiny_points": <целое число> // сколько способностей сейчас может выбрать "money": <целое число>, // количество денег у героя "alive": true|false, // жив герой или мёртв }, "secondary":{ // второстепенные параметры "max_bag_size": <целое число>, // максимальный размер рюкзака "power": [<целое число>, <целое число>], // физическая сила, магическая сила "move_speed": <дробное число>, // скорость движения "loot_items_count": <целое число>, // количество лута в рюкзаке "initiative": <дробное число> // инициатива героя }, "diary": "строка", // версия дневника героя, если она изменилась, необходимо перезапросить дневни "messages":[ // сообщения из журнала [ // запись в задании <timestamp>, // timestamp создания сообщения "строка", // текстовое описание времени в игре "строка", // текст <целое число>|null, // идентификатор типа фразы, найти идентификатор типа фразы можно в адресе страницы лингвистики с фразами этого типа {"строка": "строка"} // словарь соотношения переменных и их значений (ВНИМАНИЕ! перечень переменных может изменяться без изменения версии этого метода) ] ], "habits": { // черты "строка": { // идентификатор черты "verbose": "строка", // текущее текстовое значение черты для игрока (название характера) "raw": <дробное число> // текущее числовое значение черты } }, "quests": { // информация о заданиях "quests": [ // список глобальных заданий { "line": [ // список «базовых» заданий (цепочка последовательных заданий) { "type": "строка", // тип задания "uid": "строка", // уникальный идентификатор задания "name": "строка", // название задания "action": "строка", // описание текущего действия героя в задании "choice": "строка"|null, // текущий выбор героя в задании "choice_alternatives": [ // альтернативные выборы [ "строка", // уникальный идентификатор выбора "строка" // текстовое описание выбора ] ], "experience": <целое число>, // количество опыта за задание "power": <целое число>, // количество влияния за задание "actors": [ // список «актёров», участвующих в задании [ "строка", // название актёра <целое число>", // тип актёра (список типов приведён в описании API) <quest_actor_info> // данные, специфичные для конкретного типа актёра ] ] } ] } ] }, "action":{ // текущее действие "percents": <дробное число>, // процент выполнения "description": "строка", // описание "info_link": "url"|null // ссылка на доп. информацию "type": <целое число> // идентификатор типа действия "data": null|<словарь> // дополнительная информация о действиии или null, если такой нет }, "position":{ // позиция героя на клеточной карте "x": <дробное число>, // координата x "y": <дробное число>, // координата y "dx": <дробное число>, // направление взгляда по x "dy": <дробное число>, // направленеи взгляда по y }, "permissions": { // права на выполнение различных операций "can_participate_in_pvp": true|false, // может ли участвовать в pvp "can_repair_building": true|false, // может ли чинить здания }, "might": { // могущество игрока "value": <дробное число>, // величина "crit_chance": <дробное число>, // вероятность критического срабатывания помощи "pvp_effectiveness_bonus": <дробное число>, // бонус к эффективности в pvp от могущества "politics_power": <дробное число> // бонус к политическому влиянию героя }, "id": <целое число>, // идентификатор "actual_on_turn": <целое число>, // данные на какой ход предоставлены "sprite": <целое число> // идентификатор спрайта, которым отображается герой } <quest_actor_info> = <quest_actor_place_info>|<quest_actor_person_info>|<quest_actor_spending_info> <quest_actor_place_info> = { // информация о городе "id": <целое числое>, // идентификатор "name": "строка" // название города } <quest_actor_person_info> = { // информация о жителе города "id": <целое числое> // идентификатор "name": "строка", // имя "race": <целое числое>, // раса "gender": <целое числое>, // пол "profession": <целое числое>, // профессия "mastery_verbose": "строка", // профессия "place": <целое числое> // идентификатор города } <quest_actor_spending_info> = { // информация о целях накопления "goal": "строка" // описание цели накопления } <artifact_info> = { // информация об артефакте "name": "строка", // название "power": [<целое число>, <целое число>], // сила [физическая, магическая] "type": <целое число>, // тип "integrity": [<целое число>, <целое число>], // целостность [текущая, максимальная] "rarity": <целое число>, // редкость "effect": <целое число>, // тип эффекта на артефакте "special_effect": <целое число>, // тип особого свойства артефакта (эффекта, который действует независимо от редкости) "preference_rating": <дробное число>, // «полезность» артефакта с точки зрения героя "equipped": true|false, // может ли быть экипирован "id": <целое число> // уникальный идентификатор рода артефакта } <card_info> = { // информация о карте в колоде игрока "name": "строка", // название "type": <целое число>, // тип "rarity": <целое число>, // редкость карты "uid": <целое число>, // уникальный идентификатор в колоде игрока "auction": true|false // может быть продана на рынке } <companion_info> = { // информация о спутнике героя "type": <целое число>, // тип спутника "name": "строка", // название/имя спутника "health": <целое число>, // текущее здоровье "max_health": <целое число>, // максимальное здоровье "experience": <целое число>, // текущий опыт слаженности "experience_to_level": <целое число>, // опыта до следующего уровня слаженности "coherence": <целое число>, // текущая слаженность "real_coherence": <целое число> // полная слаженность (без учёта ограничений на максимум слаженности) } примечания: - если информация о герое устаревшая (is_old == true), то следует повторить запрос через несколько секунд (но лучше не злоупотреблять) ''' account = context.requested_account if account is None and context.account.is_authenticated: account = context.account data = game_logic.form_game_info(account=account, is_own=False if account is None else (context.account.id == account.id), client_turns=context.client_turns) if context.api_version in ('1.5', '1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_6_to_1_5(data) if context.api_version in ('1.4', '1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_5_to_1_4(data) if context.api_version in ('1.3', '1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_4_to_1_3(data) if context.api_version in ('1.2', '1.1', '1.0'): data = game_logic.game_info_from_1_3_to_1_2(data) if context.api_version in ('1.1', '1.0'): data = game_logic.game_info_from_1_2_to_1_1(data) if context.api_version == '1.0': data = game_logic.game_info_from_1_1_to_1_0(data) return dext_views.AjaxOk(content=data)