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