Jump to content
Korean Random

Leaderboard


Popular Content

Showing content with the highest reputation on 08/20/2021 in all areas

  1. 1 point
    Мод по сбору внутриигровой статистики Оглавление Версия 0.0.7 (MVP) Уже реализована MVP версия собирающая информацию о появление трассера, и строящая на её основе различные аналитические графики. Посмотреть можно тут https://wotstat.soprachev.com. Исходники мода в опенсорс. Математический кусок сервера не выкладываю, тк там неправильный способ расчёта пересечения трассера и круга разброса. Правильный вот. Реализую его КТТС и тоже залью Сейчас сервер на c#, бд -- PostgreSQL. Версия 1.0 [В разработке] Сервис который собирает события боя и сохраняет их в базу данных. Любой бой будет начинаться с события загрузки боя, заканчиваться его результатом. Микросервисная архитектура полностью в опенсорс. Описание читать тут. Список событий тут. Исходник мода тут. CI/CD настроен так, что на каждое изменение основной ветки, обновляется состояние на dev.wotstat домене На фронте будет рисовать график событий от времени. Раздел аналитики даст возможность рисовать любые графики с выбором по осям. Экспериментально Записывать по аренам, если в одном бою несколько человек с модом, запишется 1 раз. Перемещение танков раз в 10 метров Полное состояние карты Хочу попробовать пройтись машинным обучением, и обучать нейросеть советовать позиции в зависимости от состояния боя. Думаю это будет крайне полезно большинству игроков. В чем преимущество моего решения перед другими Я позволяю строить аналитику по абсолютно произвольным выборкам в любой момент времени, каждый может натыкать любые фильтры и тут же получить по ним график. Всё бесплатно и я не планирую как либо монетизировать этот проект РЕПОЗИТОРИЙ С ИСХОДНИКАМИ Чтоб не потерялось Пересечение трассера и разброса url url url Связь выстрела и попадания url Механика отрисовки сведения url
  2. 1 point
    Добрый день и вам. Ну скажем так, была разработана платка где все компоненты совмещены, сделано было 40 таких платок (2 комплекта). Сильно покопал тему перемещения в пространстве, пока сильно не получается допилить из-за отсутствия большого помещения для расчетов триангуляции. Разработал похожий прототип для обновленного датчика ICM-20948, работает не хуже. Перевел передачу протокола между датчиками и точкой доступа на mqtt-sn. С одной стороны вроде толкаю его, с другой плохо толкается, так что не сильно усердствую. Как то так.
  3. 1 point
    Всё таки удалось ВГ поломать этот мод: Попробовал включить плагин AutoAimExtension - ствол прилип на одном месте и раздуплялся только на 3 минуте боя, после всяких дерганий. Отключил его обратно, но в этом микропатче появились ошибки в питон-логе, указывающие на этот мод.
  4. 1 point
    А другого способа нет. Только та информация, что прислал сервер. from Avatar import PlayerAvatar from Vehicle import Vehicle from VehicleEffects import DamageFromShotDecoder from Math import Matrix, Vector3 @BigWorld.new_overrideLib.registerEvent(Vehicle, 'showDamageFromShot', DEBUG_MAIN) def new_showDamageFromShot(self, attackerID, points, effectsIndex, *a, **k): #Это попадание в модель танка .... #Декодирование точки и вектора попадания выглядит так decodedPoints = DamageFromShotDecoder.decodeHitPoints(points, self.appearance.collisions) if decodedPoints: firstHitPoint = decodedPoints[0] compMatrix = Matrix(self.appearance.compoundModel.node(firstHitPoint.componentName)) firstHitDirLocal = firstHitPoint.matrix.applyToAxis(2) firstHitDir = compMatrix.applyVector(firstHitDirLocal) firstHitDir.normalise() firstHitPos = compMatrix.applyPoint(firstHitPoint.matrix.translation) @BigWorld.new_overrideLib.registerEvent(PlayerAvatar, 'explodeProjectile', DEBUG_MAIN) def new_explodeProjectile(self, shotID, effectsIndex, effectMaterialIndex, endPoint, velocityDir, *a, **k): #Попадание снаряда в землю #Декодирование не требуется, т.к. endPoint, velocityDir и есть точка и вектор попадания Все верно, только если игра запущена и мод смог перехватить onBattleResultsReceived. Ниже древний пример, когда-то давно делал мод, что-то может изменилось, но не принципиально. import BigWorld __version__ = 'V1.1 P2.7 W0.9.10 26.10.2015' __author__ = 'StranikS_Scan' # Protected code ----------------------------------------------------------------- # WPG-mode ---------------------------------------------------------------- from os import path import BattleReplay from gui.shared.utils.HangarSpace import g_hangarSpace from gui import SystemMessages from PlayerEvents import g_playerEvents import constants from gui.battle_control.arena_info import getArenaGuiTypeLabel from helpers import isPlayerAccount, getFullClientVersion from ConnectionManager import connectionManager from datetime import datetime from functools import partial def WPG_PrintMessage(msg, INF): if len(msg)>0: if g_hangarSpace is not None and g_hangarSpace.inited: msg = '<textformat><font color="#E2D2A2" size="15"><b>WPG:</b></font><br><br>' + \ ('<font color="#E2D2A2" size="14">%s</font></textformat>' % msg) SystemMessages.pushMessage(msg, INF) else: BigWorld.callback(1.0, partial(WPG_PrintMessage, msg, INF)) class WPG_ResultDump(object): def __init__(self): g_playerEvents.onAccountBecomePlayer += self.__onAccountBecomePlayer g_playerEvents.onAvatarReady += self.__onAvatarReady g_playerEvents.onBattleResultsReceived += self.__onBattleResultsReceived self.Info = {} self.LogPath = './wpg_log' def __onAccountBecomePlayer(self): if not isPlayerAccount(): return else: player = BigWorld.player() if player.databaseID is None: BigWorld.callback(0.1, self.__onAccountBecomePlayer) else: self.Info['PlayerID'] = player.databaseID return def __onAvatarReady(self): def getBattleCount(accountDBID): import urllib2 import json request = 'http://api.worldoftanks.ru/wot/account/info/?application_id=demo&fields=statistics.all&account_id=%s' % accountDBID data = json.loads(urllib2.urlopen(request).read())['data'] player = data.get(str(accountDBID), None) if player != None: return int(player['statistics']['all']['battles']) return -1 if not BattleReplay.isPlaying() or 'PlayerID' in self.Info: player = BigWorld.player() self.Info['PlayerName'] = player.name self.Info['BattleNo'] = getBattleCount(self.Info['PlayerID']) + 1 self.Info['PlayerVehicleID'] = player.playerVehicleID self.Info['VehicleName'] = BigWorld.entities[player.playerVehicleID].typeDescriptor.name.replace(':', '-') arenaName = player.arena.arenaType.geometry i = arenaName.find('/') if i != -1: arenaName = arenaName[i+1:] self.Info['MapName'] = '%s(%s)' % (player.arena.arenaType.name, arenaName) self.Info['BattleType'] = getArenaGuiTypeLabel() self.Info['BattleLevel'] = player.arena.extraData.get('battleLevel') now = datetime.now() self.Info['Date'] = '%02d.%02d.%04d %02d:%02d:%02d' % (now.day, now.month, now.year, now.hour, now.minute, now.second) self.Info['ServerName'] = connectionManager.serverUserName self.Info['RegionCode'] = constants.AUTH_REALM self.Info['ClientVersion'] = getFullClientVersion() return def __onBattleResultsReceived(self, isPlayerVehicle, results): if not BattleReplay.isPlaying(): import json, copy from random import randint from struct import pack from os import path, mkdir if isPlayerVehicle and 'PlayerID' in self.Info: try: self.Info['BattleStatistics'] = BigWorld.player().arena.statistics vehicles = {} for k, v in BigWorld.player().arena.vehicles.iteritems(): vehicle = copy.copy(v) vehicle['vehicleType'] = v['vehicleType'].name if v['vehicleType'] is not None else '' del vehicle['forbidInBattleInvitations'] del vehicle['prebattleID'] del vehicle['isPrebattleCreator'] del vehicle['isAvatarReady'] del vehicle['potapovQuestIDs'] del vehicle['igrType'] del vehicle['events'] vehicles[k] = vehicle self.Info['ArenaVehiclesInfo'] = vehicles modifiedResults = copy.deepcopy(results) allPlayersVehicles = modifiedResults.get('vehicles', None) if allPlayersVehicles is not None: for playerVehicles in allPlayersVehicles.itervalues(): for vehicle in playerVehicles: vehicle['damageEventList'] = None personals = modifiedResults.get('personal', None) if personals is not None: for personal in personals.itervalues(): for field in ('damageEventList', 'xpReplay', 'creditsReplay', 'tmenXPReplay', 'fortResourceReplay', 'goldReplay', 'freeXPReplay'): personal[field] = None details = personal.pop('details', None) if details is not None: modifiedDetails = dict() for key, value in details.iteritems(): modifiedDetails[str(key)] = value personal['details'] = modifiedDetails self.Info['BattleResults'] = modifiedResults dump = [] dump.append('========================================================') dump.append(' WPG - BATTLE RESULT ') dump.append(" DON'T REWRITE OR MODIFIED THIS FILE ") dump.append('========================================================') dump.append('') dump.append('PlayerName: %s' % self.Info['PlayerName']) dump.append('PlayerID: %s' % self.Info['PlayerID']) dump.append('BattleNo: %s' % self.Info['BattleNo']) dump.append('') dump.append('PlayerVehicleID: %s' % self.Info['PlayerVehicleID']) dump.append('VehicleName: %s' % self.Info['VehicleName']) dump.append('MapName: %s' % self.Info['MapName']) dump.append('BattleType: %s' % self.Info['BattleType']) dump.append('BattleLevel: %s' % self.Info['BattleLevel']) dump.append('') dump.append('Date: %s' % self.Info['Date']) dump.append('ServerName: %s' % self.Info['ServerName']) dump.append('RegionCode: %s' % self.Info['RegionCode']) dump.append('') dump.append('ClientVersion: %s' % self.Info['ClientVersion']) dump.append('WPGVersion: %s' % __version__) dump.append('') dump.append('BattleStatistics:') dump.append(json.dumps(self.Info['BattleStatistics'], sort_keys=True).replace(',',',\n')) dump.append('') dump.append('ArenaVehiclesInfo:') dump.append(json.dumps(self.Info['ArenaVehiclesInfo'], indent=2, sort_keys=True)) dump.append('') dump.append('BattleResults:') dump.append(json.dumps(self.Info['BattleResults'], indent=2, sort_keys=True)) dump.append('') dump.append('') dump = '\n'.join(dump) self.LogFile = '%s_%d_%s.wpgr' % (self.Info['PlayerName'], self.Info['BattleNo'], self.Info['Date'].replace('.','').replace(':','').replace(' ','_')) except Exception as E: WPG_PrintMessage('Work was interrupted by the error!\n\n%s' % E, SystemMessages.SM_TYPE.Error) else: try: if not path.exists(self.LogPath): mkdir(self.LogPath) with open(self.LogPath+'/'+self.LogFile,'w') as f: f.write(dump) except Exception as E: WPG_PrintMessage('Was unable to save the wpgr-file!\n\n%s' % E, SystemMessages.SM_TYPE.Error) else: WPG_PrintMessage('Battle result saved in %s' % self.LogFile, SystemMessages.SM_TYPE.GameGreeting) self.Info = {} return __WPG_ResultDump = WPG_ResultDump() WPG_PrintMessage('- Statistics mode loaded successfully;\n'+\ '- Do not leave the battle before its completion;\n'+\ '- Files with results will be saved as \'%s/*.wpgr\'.' % __WPG_ResultDump.LogPath.replace('.',''),\ SystemMessages.SM_TYPE.GameGreeting)
  5. 1 point
    Генератор случайных чисел как в танках Задача. Написать генератор случайных чисел чтоб получить распределение как в танках, каким либо общеизвестным методом оно не описывается. Шаг 1. Берём много выстрелов, можем от сюда и строим по ним гистограмму распределения, пусть на N = 100 столбцов data = readtable("./server.csv", opts); data = data{:,[1,2,3]}; N = 100; h = hist(data(:,3), N); Шаг 2. Находим аппроксимацию полученной гистограммы, экспериментальным путём лучше всего описывается с помощью функции вида a*exp(b*x) + c*exp(d*x) fitResult = fit(linspace(0, 1, N)', h', 'exp2'); [a, b, c, d] = deal(fitResult.a, fitResult.b, fitResult.c, fitResult.d) vpa([a, b, c, d], 30) F = @(x) (a*exp(b*x) + c*exp(d*x)); Шаг 3. Нормируем получившуюся плотность. Интеграл плотности от 0 до 1 = 1, для этого делим функцию на интеграл PDF = @(x) (a*exp(b*x) + c*exp(d*x)) / integral(F, 0, 1); Шаг 4. Находим функцию распределения как интеграл от плотности. В качестве константы при интегрирование вычитаем значение интеграла в 0, чтоб распределение имело значения от 0 до 1 CDF = matlabFunction(int(sym(PDF))); CDF = matlabFunction(sym(CDF) - CDF(0)); Шаг 5. Находим функцию обратную данной, аналитически не вышло, делаем численно. xx = linspace(0, 1, 100); yy = CDF(xx); revCDF = @(x) interp1(yy, xx, x); Шаг 6. Аппроксимируем обратную функцию. Тут хоть сколько нибудь адекватной аппроксимацией получился полином 9 степени (p1*x^9 + p2*x^8 + p3*x^7 + p4*x^6 + p5*x^5 + p6*x^4 + p7*x^3 + p8*x^2 + p9*x + p10) X = linspace(0, 1, N); revCDF = fit(X', revCDF(X)', 'poly9', 'Robust', 'Lar'); [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10] = deal(revCDF.p1, revCDF.p2, revCDF.p3, revCDF.p4, revCDF.p5, revCDF.p6, revCDF.p7, revCDF.p8, revCDF.p9, revCDF.p10) revCDF = matlabFunction(p1*x^9 + p2*x^8 + p3*x^7 + p4*x^6 + p5*x^5 + p6*x^4 + p7*x^3 + p8*x^2 + p9*x + p10); Шаг 7. Готово. Теперь для получение случайных чисел достаточно вызвать revCDF(Rand()), где Rand() генерирует случайное число равномерно распределённое от 0 до 1. Чтоб убедиться строим гистограмму от 10 миллионов случайных чисел и сравниваем с исходной гистограммой histogram(data(:,3), 50,'Normalization','probability'); hold on histogram(revCDF(rand(10e6, 1)), 50,'Normalization','probability'); Результат: Коэффициенты формул: PDF: a = 170303.75999096324085 b = -3.9596481004599288767 c = -169770.0697609304043 d = -4.0490033841995565211 a*exp(b*x) + c*exp(d*x) invCDF: p1 = 60.643177737472981903 p2 = -268.4866364087093870 p3 = 511.04790264076154926 p4 = -545.3738964542347957 p5 = 358.28863739253199583 p6 = -150.0669928063029487 p7 = 40.683377765003896798 p8 = -7.116679640521884842 p9 = 1.3780439852440364845 p10 = 0.0024099699685065862 p1*x^9 + p2*x^8 + p3*x^7 + p4*x^6 + p5*x^5 + p6*x^4 + p7*x^3 + p8*x^2 + p9*x + p10 PDF = 171.67590541705826929*exp(-3.9596481004599288767*x) - 171.13791521967221624*exp(-4.0490033841995565211*x) CDF = 42.266676261003986781*exp(-4.0490033841995574093*x) - 43.356354166199118083*exp(-3.9596481004599288767*x) + 1.0896779051951310802 revCDF = 60.643177737472981903*x^9 - 268.48663640870938707*x^8 + 511.04790264076149242*x^7 - 545.37389645423479578*x^6 + 358.28863739253199583*x^5 - 150.06699280630289195*x^4 + 40.683377765003896798*x^3 - 7.116679640521884842*x^2 + 1.3780439852440360404*x + 0.0024099699685065862145 Полный код в матлаб: clear clc close all syms x opts = delimitedTextImportOptions("NumVariables", 4); opts.DataLines = [1, Inf]; opts.Delimiter = ","; opts.VariableNames = ["VarName1", "VarName2", "VarName3", "VarName4"]; opts.VariableTypes = ["double", "double", "double", "datetime"]; opts.ExtraColumnsRule = "ignore"; opts.EmptyLineRule = "read"; opts = setvaropts(opts, "VarName4", "InputFormat", "yyyy-MM-dd HH:mm:ss.SSS"); data = readtable("./wot-stat-filtered.csv", opts); data = data{:,[1,2,3]}; N = 100; h = hist(data(:,3), N); fitResult = fit(linspace(0, 1, N)', h', 'exp2'); %Исходная аппроксимация [a, b, c, d] = deal(fitResult.a, fitResult.b, fitResult.c, fitResult.d) vpa([a, b, c, d], 30) F = @(x) (a*exp(b*x) + c*exp(d*x)); %Нормируем PDF = @(x) (a*exp(b*x) + c*exp(d*x)) / integral(F, 0, 1); %CDF = integral(PDF) CDF = matlabFunction(int(sym(PDF))); CDF = matlabFunction(sym(CDF) - CDF(0)); %Находим численно обратную xx = linspace(0, 1, 100); yy = CDF(xx); revCDF = @(x) interp1(yy, xx, x); %Аппроксимируем обратную X = linspace(0, 1, N); revCDF = fit(X', revCDF(X)', 'poly9', 'Robust', 'Lar'); [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10] = deal(revCDF.p1, revCDF.p2, revCDF.p3, revCDF.p4, revCDF.p5, revCDF.p6, revCDF.p7, revCDF.p8, revCDF.p9, revCDF.p10) revCDF = matlabFunction(p1*x^9 + p2*x^8 + p3*x^7 + p4*x^6 + p5*x^5 + p6*x^4 + p7*x^3 + p8*x^2 + p9*x + p10); %Рисуем хистограмму для сравнения histogram(data(:,3), 50,'Normalization','probability'); hold on histogram(revCDF(rand(10e6, 1)), 50,'Normalization','probability'); rng(0) window = 100; smooth = smoothdata(data(:,3), 'movmean', window); smoothCorrect = smoothdata(revCDF(rand(length(data(:,3)), 1)), 'movmean', window); figure plot(data(:,1), smooth) hold on plot(data(:,1), smoothCorrect) PDFCDF.m
  6. 1 point
    19.08.21 Обновлена сборка модов: * Обновлено для версии игрового клиента v1.14.0.1 * Обновлён мод "Комплексный мод XVM" до версии v8.8.2-dev (0020)
  7. 1 point
    очередное переименование папки,ну и мелкие правки заодно.
  8. 1 point
    @Pelm , открой файл: hitLog.xc Замени: "addToEnd": false, на "addToEnd": true, И в двух строках: <textformat tabstops='[30,39,69,78]' leading='-5'> На: <textformat tabstops='[20,50,59,89,98]' leading='-5'>{{number%2d~.}}<tab>
  9. 1 point
  10. 1 point
    Очень жаль, если шкурки умрут. Хорошие и глаза не мозолят во время игры. Очень надеюсь, что передумаете.
  11. 1 point
  12. 1 point
    Немного про механику игры, исходя из клиентского кода Разброс в механике игры задается сервером как тангенс половинного угла разброса орудия dispersionAngle. Эту цифру сервер обсчитывает с тиком 0,1 сек и шлет клиенту игры. Вычисляет её он как функцию от динамических характеристик танка, путем применения дополнительных коэффициентов к базовому тангенсу разброса орудия, прописанному в файлах ТТХ для дистанции 100 метров. Все эти коэффициенты и формулу можно посмотреть в коде игры, они дублируют расчеты на сервере чтобы сглаживать движение объектов в клиенте игры. Расчеты в клиенте вынесены в отдельную функцию getOwnVehicleShotDispersionAngle. Ниже её код, а вот тут его разбор в числах, правда он мог немного устареть. def getOwnVehicleShotDispersionAngle(self, turretRotationSpeed, withShot=0): descr = self.__getDetailedVehicleDescriptor() aimingStartTime, aimingStartFactor, multFactor, gunShotDispersionFactorsTurretRotation, chassisShotDispersionFactorsMovement, chassisShotDispersionFactorsRotation, aimingTime = self.__aimingInfo vehicleSpeed, vehicleRSpeed = self.getOwnVehicleSpeeds(True) vehicleMovementFactor = vehicleSpeed * chassisShotDispersionFactorsMovement vehicleMovementFactor *= vehicleMovementFactor vehicleRotationFactor = vehicleRSpeed * chassisShotDispersionFactorsRotation vehicleRotationFactor *= vehicleRotationFactor turretRotationFactor = turretRotationSpeed * gunShotDispersionFactorsTurretRotation turretRotationFactor *= turretRotationFactor if withShot == 0: shotFactor = 0.0 elif withShot == 1: shotFactor = descr.gun.shotDispersionFactors['afterShot'] else: shotFactor = descr.gun.shotDispersionFactors['afterShotInBurst'] shotFactor *= shotFactor idealFactor = vehicleMovementFactor + vehicleRotationFactor + turretRotationFactor + shotFactor additiveFactor = self.__getAdditiveShotDispersionFactor(descr) idealFactor *= additiveFactor ** 2 idealFactor = multFactor * math.sqrt(1.0 + idealFactor) currTime = BigWorld.time() aimingFactor = aimingStartFactor * math.exp((aimingStartTime - currTime) / aimingTime) isGunReloading = self.guiSessionProvider.shared.ammo.isGunReloading() if aimingFactor < idealFactor: aimingFactor = idealFactor self.__aimingInfo[0] = currTime self.__aimingInfo[1] = aimingFactor if abs(idealFactor - multFactor) < 0.001: if not self.__isAimingEnded and not isGunReloading: self.soundNotifications.play('sight_convergence') self.__isAimingEnded = True elif idealFactor / multFactor > 1.1: self.__isAimingEnded = False elif aimingFactor / multFactor > 1.1: self.__isAimingEnded = False return [descr.gun.shotDispersionAngle * aimingFactor, descr.gun.shotDispersionAngle * idealFactor] В механике игры снаряды летят по параболе, параметры которой зависят от дистанции от танка игрока до точки прицеливания. Сервер в игре вычисляет дистанции по прямой, либо от опорной точки модели танка, либо от точки вылета снаряда. При этом снаряд на всех моделях вылетает из точки крепления пушки, она же опорная точка орудия (трассер кстати рисуется из другой точки, из конца орудия - элемент модели _GunFire). Первое юзается в механике маскировки и в засвета, второе в баллистике и прицеливании. Опорная точка пушки вычисляется, например так: startPoint = player.getOwnVehiclePosition() startPoint += player.vehicleTypeDescriptor.hull.turretPositions[0] + player.vehicleTypeDescriptor.turret.gunPosition Дистанция прицеливания вычисляется в той точке, в которую указывает маркер игрока, т.е. в точке пересечения луча из маркера, ортогонального плоскости монитора, с поверхностью игрового мира. В клиенте есть код на её вычисление, поэтому самому считать не надо, а можно брать готовое значение из: markerPos = player.gunRotator.markerInfo[0] Отсюда получается, что дистанция от танка игрока до точки прицеливания в механике стрельбы это всегда: markerDist = startPoint.distTo(markerPos) Тогда радиус круга разброса в точке прицеливания для любого танка это: dispersionRadius = player.gunRotator.dispersionAngle * markerDist В клиенте игры это все есть в коде, который отвечает за отрисовку сведения. Для этого используются следующие функции: 1. Внеклассовая функция расчета баллистических параметров в точке прицеливания getCappedShotTargetInfos def getCappedShotTargetInfos(shotPos, shotVec, gravity, shotDescr, vehicleID, minBounds, maxBounds, collisionStrategy): endPos, direction, collData, usedMaxDistance = BigWorld.wg_getCappedShotTargetInfos(BigWorld.player().spaceID, shotPos, shotVec, gravity, shotDescr.maxDistance, vehicleID, minBounds, maxBounds, collisionStrategy) if collData != 0: collData = EntityCollisionData(*collData) else: collData = None return (endPos, direction, collData, usedMaxDistance) Она обсчитывает траекторию нативно, но как именно она считает можно глянуть под спойлером. В списке выходных параметров direction это вектор попадания снаряда с учетом баллистической траектории. 2. Классовая функция расчета баллистических параметров и разброса в точке прицеливания __getGunMarkerPosition, использующая функцию из п.1 def __getGunMarkerPosition(self, shotPos, shotVec, dispersionAngles): shotDescr = self._avatar.getVehicleDescriptor().shot gravity = Math.Vector3(0.0, -shotDescr.gravity, 0.0) testVehicleID = self.getAttachedVehicleID() collisionStrategy = AimingSystems.CollisionStrategy.COLLIDE_DYNAMIC_AND_STATIC minBounds, maxBounds = BigWorld.player().arena.getSpaceBB() endPos, direction, collData, usedMaxDistance = AimingSystems.getCappedShotTargetInfos(shotPos, shotVec, gravity, shotDescr, testVehicleID, minBounds, maxBounds, collisionStrategy) distance = shotDescr.maxDistance if usedMaxDistance else (endPos - shotPos).length markerDiameter = 2.0 * distance * dispersionAngles[0] idealMarkerDiameter = 2.0 * distance * dispersionAngles[1] replayCtrl = BattleReplay.g_replayCtrl if replayCtrl.isPlaying and replayCtrl.isClientReady: markerDiameter, endPos, direction = replayCtrl.getGunMarkerParams(endPos, direction) return (endPos, direction, markerDiameter, idealMarkerDiameter, collData) Как видим, диаметр круга разброса в точке прицеливания это удвоенное текущее значение тангенса половинного угла разброса орудия танка 2*dispersionAngles[0] помноженное на дистанцию distance = .... (endPos - shotPos).length между опорной точкой орудия shotPos и точкой прицеливания endPos. 3. Далее находим, где используется __getGunMarkerPosition. В функции обновления сведения прицела __updateGunMarker, которая вызывается пока игрок в бою с периодичностью 0,1 сек, что составляет тик сервера: def __updateGunMarker(self, forceRelaxTime=None): if self._avatar.getVehicleAttached() is None: return else: shotPos, shotVec = self.getCurShotPosition() markerPos, markerDir, markerSize, idealMarkerSize, collData = self.__getGunMarkerPosition(shotPos, shotVec, self.__dispersionAngles) replayCtrl = BattleReplay.g_replayCtrl if replayCtrl.isRecording and not replayCtrl.isServerAim: replayCtrl.setGunMarkerParams(markerSize, markerPos, markerDir) if not self.__targetLastShotPoint: self.__lastShotPoint = markerPos replayCtrl = BattleReplay.g_replayCtrl if replayCtrl.isPlaying and replayCtrl.isUpdateGunOnTimeWarp: self._avatar.inputHandler.updateGunMarker(markerPos, markerDir, (markerSize, idealMarkerSize), 0.001, collData) else: relaxTime = self.__ROTATION_TICK_LENGTH if forceRelaxTime is None else forceRelaxTime self._avatar.inputHandler.updateGunMarker(markerPos, markerDir, (markerSize, idealMarkerSize), relaxTime, collData) self.__markerInfo = (markerPos, markerDir, markerSize) if self._avatar.inCharge: self._updateMultiGunCollisionData() return А еще в дублирующей её setShotPosition, которая обеспечивает автовозвышение орудия танка и обновление, упомянутого выше публичного буфера player.gunRotator.markerInfo. 4. Теперь нужно лезть в дебри модулей, отвечающих за управление прицелом и отрисовку сведения через флэшку, именно туда ведет вызов _avatar.inputHandler.updateGunMarker из кода выше. Если опустить подробности то, передача данных идет через объект DataProvider, который по сути является прокладкой для передачи данных в код флэшки прицела. Здесь же стоит отметить, что версии провайдера разные для прицела арты и для остальных прицелов танков. Для прицела арты создается он так: def _makeSPGProvider(): dataProvider = GUI.WGSPGGunMarkerDataProvider(aih_constants.SPG_GUN_MARKER_ELEMENTS_COUNT, aih_constants.SPG_GUN_MARKER_ELEMENTS_RATE) dataProvider.positionMatrixProvider = Math.MatrixAnimation() dataProvider.maxTime = 5.0 dataProvider.serverTickLength = constants.SERVER_TICK_LENGTH dataProvider.sizeScaleRate = aih_constants.SPG_GUN_MARKER_SCALE_RATE dataProvider.sizeConstraint = (aih_constants.SPG_GUN_MARKER_MIN_SIZE, aih_constants.SPG_GUN_MARKER_MAX_SIZE) dataProvider.setRelaxTime(constants.SERVER_TICK_LENGTH) return dataProvider А для всех остальных вот так def _makeDefaultProvider(): dataProvider = GUI.WGGunMarkerDataProvider() dataProvider.positionMatrixProvider = Math.MatrixAnimation() dataProvider.setStartSize(_setupGunMarkerSizeLimits(dataProvider)[0]) return dataProvider Что означают доп.константы у прицела арты, думаю не сложно догадаться, их значения кстати вот такие: GUN_MARKER_MIN_SIZE = 32.0 SPG_GUN_MARKER_ELEMENTS_COUNT = 37 SPG_GUN_MARKER_ELEMENTS_RATE = 10 SPG_GUN_MARKER_MIN_SIZE = 50.0 SPG_GUN_MARKER_MAX_SIZE = 100.0 SPG_GUN_MARKER_SCALE_RATE = 10.0 Обращаем внимание на последний параметр, у сведения арты есть дополнительный коэф. масштаба SPG_GUN_MARKER_SCALE_RATE. Передача данных в провайдер прицела арты выглядит вот так: def update(self, markerType, position, direction, size, relaxTime, collData): super(_SPGGunMarkerController, self).update(markerType, position, direction, size, relaxTime, collData) positionMatrix = Math.Matrix() positionMatrix.setTranslate(position) self._updateMatrixProvider(positionMatrix, relaxTime) self._size = size[0] ################################################# self._update() def _update(self): pos3d, vel3d, gravity3d = self._getCurrentShotInfo() replayCtrl = BattleReplay.g_replayCtrl if replayCtrl.isPlaying and replayCtrl.isClientReady: self.__updateRelaxTime() self._updateDispersionData() self._dataProvider.update(pos3d, vel3d, gravity3d, self._size) def _getCurrentShotInfo(self): gunMat = AimingSystems.getPlayerGunMat(self._gunRotator.turretYaw, self._gunRotator.gunPitch) position = gunMat.translation velocity = gunMat.applyVector(Math.Vector3(0, 0, self._shotSpeed)) return (position, velocity, Math.Vector3(0, -self._shotGravity, 0)) ######################### def _updateDispersionData(self): dispersionAngle = self._gunRotator.dispersionAngle ######################### isServerAim = self._gunMarkerType == _MARKER_TYPE.SERVER replayCtrl = BattleReplay.g_replayCtrl if replayCtrl.isPlaying and replayCtrl.isClientReady: d, s = replayCtrl.getSPGGunMarkerParams() if d != -1.0 and s != -1.0: dispersionAngle = d elif replayCtrl.isRecording: if replayCtrl.isServerAim and isServerAim: replayCtrl.setSPGGunMarkerParams(dispersionAngle, 0.0) elif not isServerAim: replayCtrl.setSPGGunMarkerParams(dispersionAngle, 0.0) self._dataProvider.setupConicDispersion(dispersionAngle) В то время как для остальных танков вот так: def update(self, markerType, pos, direction, sizeVector, relaxTime, collData): super(_DefaultGunMarkerController, self).update(markerType, pos, direction, sizeVector, relaxTime, collData) positionMatrix = Math.Matrix() positionMatrix.setTranslate(pos) self._updateMatrixProvider(positionMatrix, relaxTime) size = sizeVector[0] idealSize = sizeVector[1] replayCtrl = BattleReplay.g_replayCtrl if replayCtrl.isPlaying and replayCtrl.isClientReady: s = replayCtrl.getArcadeGunMarkerSize() if s != -1.0: size = s elif replayCtrl.isRecording: if replayCtrl.isServerAim and self._gunMarkerType == _MARKER_TYPE.SERVER: replayCtrl.setArcadeGunMarkerSize(size) elif self._gunMarkerType == _MARKER_TYPE.CLIENT: replayCtrl.setArcadeGunMarkerSize(size) positionMatrixForScale = self.__checkAndRecalculateIfPositionInExtremeProjection(positionMatrix) worldMatrix = _makeWorldMatrix(positionMatrixForScale) currentSize = _calcScale(worldMatrix, size) * self.__screenRatio idealSize = _calcScale(worldMatrix, idealSize) * self.__screenRatio self.__sizeFilter.update(currentSize, idealSize) self.__curSize = self.__sizeFilter.getSize() if self.__replSwitchTime > 0.0: self.__replSwitchTime -= relaxTime self._dataProvider.updateSize(self.__curSize, 0.0) else: self._dataProvider.updateSize(self.__curSize, relaxTime) Рассмотрим сначала второй код. Для аркадного и снайперского прицелов размер сведения в пикселях вычисляется непосредственно в функции update() и затем отдается провайдеру. Для этого берется диаметр мгновенного значения круга разброса в текущей точке прицеливания size = sizeVector[0], высчитывается его масштаб для экрана игрока через функцию _calcScale и этот масштаб умножается на половину ширины экрана в пикселях self.__screenRatio. Последняя величина находится вот так self.__screenRatio = GUI.screenResolution()[0] * 0.5. Чтобы сведение в игре не было очень маленьким или очень большим, а оно будет таковыми, имеется фильтр self.__sizeFilter, который ограничивает минимально и максимально возможное значение круга сведения. Лимиты, которые задаются в него, вычисляются вот так: def _setupGunMarkerSizeLimits(dataProvider, scale=None): if scale is None: settingsCore = dependency.instance(ISettingsCore) scale = settingsCore.interfaceScale.get() limits = (aih_constants.GUN_MARKER_MIN_SIZE * scale, min(GUI.screenResolution())) dataProvider.sizeConstraint = limits return limits Переходим к арт-прицелу. У арты расчеты в пикселях в функции update() не выполняются, вместо этого данные о точке выстрела, векторах начальной скорости и гравитации и текущем диаметре круга разброса в точке прицеливания - pos3d, vel3d, gravity3d и self._size - передаются прямо в провайдер. А также в него передается текущее значение тангенса угла разброса орудия dispersionAngle. Что со всем этим делает провайдер WGSPGGunMarkerDataProvider сказать трудно, т.к. он нативный. Если глянуть список доступных атрибутов и функций у WGSPGGunMarkerDataProvider, то там можно увидеть: *** enableSmoothFiltering *** getPointsInside *** maxTime *** positionMatrixProvider *** relaxTime *** reset *** serverTickLength *** setRelaxTime *** setStartSize *** setupConicDispersion *** setupFlatRadialDispersion *** sizeConstraint *** sizeScaleRate *** update *** updateSize Функция setupFlatRadialDispersion тоже встречается в коде клиента игры. Это код, который отвечает за отрисовку новой игровой фичи - подсветки на игровом поле места, куда стреляет или будет стрелять арта. Код класса можно глянуть тут. В качестве аргумента в функцию передается игровая константа areaRadius.
  13. 1 point
    Потратил несколько вечеров чтобы переписать математику для платформы более простым и понятным языком, после просчета длин отрезков между точками соединения платформы и базы стало слишком сложно стараться описывать расчеты просто да и немного надоело. Если кому интересно будет - выложу эти расчеты, а может и доведу до конца. В итоге у меня есть полное понимание как именно работает платформа. И я перешел к следующему шагу, т.е. подключения её к ардуинке и заливке кода в неё. В качестве тестового кода пробовал этот код - практически слету все завелось. Осталось покопаться в "тонких настройках".
  14. 1 point
    Начал разбирать сваленную в кучу у меня документацию, и понял что я не то, что глубоко изучал, а даже поверхностно не понимаю что там и к чему. А было так вроде много понятно месяц назад. Ну да ладно, пока выкладываю то что есть на ноутбуке. Самым интересным и понятным файлом является екселевский с расчетами и графической моделью. На второй странице "DATA" есть весь пошаговый расчет нужных углов для сервоприводов. Дополнительно выявил несколько проблем у своей "платформы": Так как сервоприводы очень недорогие, имеется некоторая погрешность в каждом. Не корректно отрабатываются положения 0-180 - на глаз где-то 2-200/210 и есть упор физический - двигатель упирается, методом тыка выявил ориентировочно программно 0-165, соответствует визуальным 0-180. В общем каждый придется в ручную калибровать. Каждая пара сервоприводов красный/зеленый, желтый/синий, розовый/белый установлены зеркально соответственно то что у одного максимум 180 градусов, у второго 0, что в совокупности с предыдущей проблемой вообще обещает веселье. При подаче питания на сервоприводы, есть небольшое, порядка 10 градусов, движение. В какую сторону пока не определял, потому как был обрадован. Думал что при подаче напряжения уходят принудительно в 0 :) Кнопки сверху заменил на саморезики, кнопки на рычагах серв вроде сидят довольно уверенно. Нашелся проект который чем-то мне напомнил то что я хочу сделать. Управление из системного python 6-ю сервоприводами на Arduino, то что нужно - простая и понятная реализация, как раз для начала. P.S. Добавил ещё 4 документа. Кинематика параллельных манипуляторов квазиортогональной структуры.pdf Detecting Singularities of Stewart Platforms.pdf StewartPlatformSimulator.xlsm.tar.gz Stewart platform with fixed rotary actuators: a low cost design study.pdf The Mathematics of the Stewart Platform.pdf Строение, кинематика и испытания шестикоординатного манипулятора для модернизации технологических машин.pdf Stewart-Gough Platform Kinematics.pdf Стабилизация положений равновесия нагруженных модификаций платформы Стюарта.pdf Dynamic Modeling and Simulation of Stewart Platform.pdf Анализ и синтез манипуляционных роботов с механизмами параллельной структуры.pdf Использование кватернионов при математическом моделирвоании механизмов с параллельными кинематическими цепями.pdf Применение кватернионов в задачах ориентации твердого тела.pdf
×
×
  • Create New...