Jump to content
Korean Random
SoprachevAK

Сведение в момент выстрела

Recommended Posts

Мне нужно записывать сведение которое было перед выстрелом
Сейчас я делаю так

# @hook VehicleGunRotator.setShotPosition
def update_gun_marker_server(self, obj, vehicleID, shotPos, shotVec, dispersionAngle, *a, **k):
  self.marker_server_disp = dispersionAngle

# @hook VehicleGunRotator.updateGunMarker
def update_gun_marker_client(self, obj, *a, **k):
  self.marker_client_disp = obj._VehicleGunRotator__dispersionAngles[0]

# @hook PlayerAvatar.shoot
def shoot(self, obj, isRepeat=False, *a, **k):
  can_shoot, error = obj.guiSessionProvider.shared.ammo.canShoot(isRepeat)
  if not can_shoot:
    return

  # store self.marker_server_disp and self.marker_client_disp	

И оно почти всегда работает, но иногда случаются ситуации, когда в момент выстрела `shoot and can_shoot` одно из сведений разбросалось (увеличилось в результате выстрела). Причём могут разбросаться как оба, так и толко серверное, так и только клиентское

Есть ли способ как нибудь это лечить? Как будто бы, сервер может новое сведение отправить до факта подтверждения выстрела. А почему у меня клиентское разбрасывается до выстрела я вообще не понимаю

  • Upvote 1

Share this post


Link to post

Short link
Share on other sites

Клиентское сведение живет своей жизнью и синхронизируется с сервером только, когда прилетает вызов PlayerAvatar.updateGunMarker(). Потому лучше его хукать, так надежнее, а не VehicleGunRotator.setShotPosition(). Сервер присылает в updateGunMarker() реальную величину тангенса угла разброса dispersionAngle, которая вписывается в первую ячейку переменной-массива VehicleGunRotator.__dispersionAngles[0] и тем самым заменяет клиентский тангенс угла разброса на серверный с последующим вызовом перерисовки сведения через PlayerAvatar.inputHandler.updateClientGunMarker().

 

Все остальное время клиент перерисовывает сведение путем периодического вызова функций VehicleGunRotator.__rotate() и за ней VehicleGunRotator.__updateGunMarker() в цикле VehicleGunRotator.__onTick c общим тиком 0.1 сек. При этом первая функция нужна для пересчета значения переменной VehicleGunRotator.__dispersionAngles с учетом динамики танка, а вторая - для непосредственной отрисовки сведения.

 

Пересчет VehicleGunRotator.__dispersionAngles выполняется путем вызова функции PlayerAvatar.getOwnVehicleShotDispersionAngle(). В ней с помощью коэффициентов-модификаторов разброса и клиентских значения скоростей вращения башни и движения и вращения корпуса танка высчитывается текущее значение клиентского тангенса угла разброса. У этой функции есть входящий аргумент withShot, принимающий два значения либо "0", либо "1". Если вызов с единицей, то разброс будет пересчитан, как для выстрелившего орудия, т.е. будет применен соответствующий модификатор. Еще есть значение "2", что соответствует модификатору разброса при серии выстрелов.

 

Вызов PlayerAvatar.getOwnVehicleShotDispersionAngle() с ненулевым withShot прописан только в одном месте клиента игры, это функция __doShot() в модуле vehicle_extras. Срабатывает эта функция при вызове функции showShooting() в модуле Vehicle. А его делает функция PlayerAvatar.__showTimedOutShooting() таймера-колбэка, который запускается через функцию PlayerAvatar.__startWaitingForShot(), которая вызывается внутри функции PlayerAvatar.shoot(), срабатывающей, когда юзвер жмет на кнопку выстрела.

 

Задержка у таймера-колбэка задается вот таким образом:

            timeout = BigWorld.LatencyInfo().value[3] * 0.5
            timeout = min(_SHOT_WAITING_MAX_TIMEOUT, timeout)
            timeout = max(_SHOT_WAITING_MIN_TIMEOUT, timeout)

берется половина значения сглаженного пинга Ping/2 и накладывается ограничение в виде диапазона [0,12...0,2] сек (границы прописаны как константы прямо в модуле). Так как у большинства игроков пинг явно меньше 240 мс, то получаем таймер на 120 мс.

 

Что же касается сервера, то команда выстрела на сервер уходит внутри функции PlayerAvatar.shoot() через нативный вызов self.cell.vehicle_shoot(), расположенный перед вызовом функции __startWaitingForShot()

 

----------------------------

 

Таким образом, при нажатии кнопки выстрела на сервер улетает команда выстрела, сервер обрабатывает её, делает выстрел и присылает новый тангенс угла разброса уже с учетом выстрела и на всё это у него уходит T1 = 2*Ping+ServerTime времени, а клиент с момента нажатия кнопки выстрела ждет T2 = 120 мс и затем сам пересчитывает тангенс угла разброса  и перерисовывает сведение с учетом выстрела. Как результат, если окажется, что T1<<T2, то серверный разброс отреагирует раньше на выстрел чем клиентский, а если Т1>>T2, то клиентский разброс отреагирует на выстрел раньше серверного. Классическая проблема синхронизации клиент-сервер.

  • Upvote 7

Share this post


Link to post

Short link
Share on other sites

@StranikS_Scan благодарю за столь подробный ответ, он действительно очень полезен для меня. 

Изучил код, и оказалось, что всё это время, в wotstat я записывал показатели не в момент выстрела, а в момент подтверждения выстрела от сервера (то есть через пинг, ну или через 120 мс таймаута)
Само собой я это исправлю, но возможно, с точки зрения сервера, это были наиболее точные показатели, тк выстрел на сервере тоже никак не предиктится, и случается через пинг после нажатия на клиенте, отправляя обратно серверную координату и разброс прицела. 

Пробовал сейчас на искусственно созданных больших пингах, визуально снаряд летит ровно в серверный прицел в момент вылета трассера.


Исправлять буду потому что игроки ожидают, что снаряды будут лететь в то место, где был круг сведения в момент нажатия ЛКМ. 

Share this post


Link to post

Short link
Share on other sites
10 часов назад, SoprachevAK сказал:

выстрел на сервере тоже никак не предиктится, и случается через пинг после нажатия на клиенте, отправляя обратно серверную координату и разброс прицела.

 

Все верно, если хукнуть self.cell.vehicle_shoot() и в хуке запоминать time = BigWorld.time(), то в PlayerAvatar.updateGunMarker() можно посчитать реальный тайумаут сервера timeout = BigWorld.time() - time и он будет по факту складываться из времени формирования пакета на ПК, времени отсылки, времени работы сервера, времени отправки ответа и времени расшифровки пакета клиентом игры. А если еще хукнуть __doShot() из vehicle_extras и туда тоже вставить код выше, то можно аналогично вычислять таймаут клиента игры.

Share this post


Link to post

Short link
Share on other sites
19 часов назад, StranikS_Scan сказал:

хукнуть self.cell.vehicle_shoot()

А можно как то хукать нативные методы? 
Я попробовал примитивно заменить, не вышло. Чат гпт тоже подсказал так, как не работает

Share this post


Link to post

Short link
Share on other sites
17.02.2024 в 08:28, SoprachevAK сказал:

А можно как то хукать нативные методы? 
Я попробовал примитивно заменить, не вышло. Чат гпт тоже подсказал так, как не работает

 

Не получится, не пайтановский объект. Тут только скопировать код функции PlayerAvatar.shoot(), продублировать как пользовательскую, вписать в неё свой код перед и/или после self.cell.vehicle_shoot() и затем прописать её как хук PlayerAvatar.shoot()

  • Upvote 1

Share this post


Link to post

Short link
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...