SoprachevAK Posted February 13 Share Posted February 13 Мне нужно записывать сведение которое было перед выстрелом Сейчас я делаю так # @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` одно из сведений разбросалось (увеличилось в результате выстрела). Причём могут разбросаться как оба, так и толко серверное, так и только клиентское Есть ли способ как нибудь это лечить? Как будто бы, сервер может новое сведение отправить до факта подтверждения выстрела. А почему у меня клиентское разбрасывается до выстрела я вообще не понимаю 1 @ Quote Link to comment Short link Share on other sites More sharing options...
StranikS_Scan Posted February 13 Share Posted February 13 Клиентское сведение живет своей жизнью и синхронизируется с сервером только, когда прилетает вызов 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, то клиентский разброс отреагирует на выстрел раньше серверного. Классическая проблема синхронизации клиент-сервер. 7 @ Quote Link to comment Short link Share on other sites More sharing options...
SoprachevAK Posted February 15 Author Share Posted February 15 @StranikS_Scan благодарю за столь подробный ответ, он действительно очень полезен для меня. Изучил код, и оказалось, что всё это время, в wotstat я записывал показатели не в момент выстрела, а в момент подтверждения выстрела от сервера (то есть через пинг, ну или через 120 мс таймаута) Само собой я это исправлю, но возможно, с точки зрения сервера, это были наиболее точные показатели, тк выстрел на сервере тоже никак не предиктится, и случается через пинг после нажатия на клиенте, отправляя обратно серверную координату и разброс прицела. Пробовал сейчас на искусственно созданных больших пингах, визуально снаряд летит ровно в серверный прицел в момент вылета трассера. Исправлять буду потому что игроки ожидают, что снаряды будут лететь в то место, где был круг сведения в момент нажатия ЛКМ. @ Quote Link to comment Short link Share on other sites More sharing options...
StranikS_Scan Posted February 16 Share Posted February 16 10 часов назад, SoprachevAK сказал: выстрел на сервере тоже никак не предиктится, и случается через пинг после нажатия на клиенте, отправляя обратно серверную координату и разброс прицела. Все верно, если хукнуть self.cell.vehicle_shoot() и в хуке запоминать time = BigWorld.time(), то в PlayerAvatar.updateGunMarker() можно посчитать реальный тайумаут сервера timeout = BigWorld.time() - time и он будет по факту складываться из времени формирования пакета на ПК, времени отсылки, времени работы сервера, времени отправки ответа и времени расшифровки пакета клиентом игры. А если еще хукнуть __doShot() из vehicle_extras и туда тоже вставить код выше, то можно аналогично вычислять таймаут клиента игры. @ Quote Link to comment Short link Share on other sites More sharing options...
SoprachevAK Posted February 17 Author Share Posted February 17 19 часов назад, StranikS_Scan сказал: хукнуть self.cell.vehicle_shoot() А можно как то хукать нативные методы? Я попробовал примитивно заменить, не вышло. Чат гпт тоже подсказал так, как не работает @ Quote Link to comment Short link Share on other sites More sharing options...
StranikS_Scan Posted February 18 Share Posted February 18 17.02.2024 в 08:28, SoprachevAK сказал: А можно как то хукать нативные методы? Я попробовал примитивно заменить, не вышло. Чат гпт тоже подсказал так, как не работает Не получится, не пайтановский объект. Тут только скопировать код функции PlayerAvatar.shoot(), продублировать как пользовательскую, вписать в неё свой код перед и/или после self.cell.vehicle_shoot() и затем прописать её как хук PlayerAvatar.shoot() 1 @ Quote Link to comment Short link Share on other sites More sharing options...
Recommended Posts
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.