├── .circleci └── config.yml ├── .gitattributes ├── .github ├── release.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── docs ├── building.md └── database.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── kotlin │ └── jp │ │ └── assasans │ │ └── protanki │ │ └── server │ │ ├── EncryptionTransformer.kt │ │ ├── Hibernate.kt │ │ ├── Main.kt │ │ ├── PacketProcessor.kt │ │ ├── ResourceConverter.kt │ │ ├── ResourceManager.kt │ │ ├── Server.kt │ │ ├── api │ │ ├── ApiServer.kt │ │ ├── Entities.kt │ │ ├── InviteEntities.kt │ │ └── MoshiConverter.kt │ │ ├── battles │ │ ├── Battle.kt │ │ ├── BattleMine.kt │ │ ├── BattlePlayer.kt │ │ ├── BattleProperty.kt │ │ ├── BattleTank.kt │ │ ├── DamageCalculator.kt │ │ ├── DamageProcessor.kt │ │ ├── FundProcessor.kt │ │ ├── MineProcessor.kt │ │ ├── TankKillType.kt │ │ ├── bonus │ │ │ ├── BattleBonus.kt │ │ │ ├── BattleDoubleArmorBonus.kt │ │ │ ├── BattleDoubleDamageBonus.kt │ │ │ ├── BattleGoldBonus.kt │ │ │ ├── BattleNitroBonus.kt │ │ │ ├── BattleRepairKitBonus.kt │ │ │ ├── BonusProcessor.kt │ │ │ └── Entities.kt │ │ ├── effect │ │ │ ├── DoubleArmorEffect.kt │ │ │ ├── DoubleDamageEffect.kt │ │ │ ├── MineEffect.kt │ │ │ ├── NitroEffect.kt │ │ │ ├── RepairKitEffect.kt │ │ │ └── TankEffect.kt │ │ ├── map │ │ │ └── MapRegistry.kt │ │ ├── mode │ │ │ ├── BattleModeHandler.kt │ │ │ ├── CaptureTheFlagModeHandler.kt │ │ │ ├── ControlPointsModeHandler.kt │ │ │ ├── DeathmatchModeHandler.kt │ │ │ ├── TeamDeathmatchModeHandler.kt │ │ │ └── TeamModeHandler.kt │ │ └── weapons │ │ │ ├── FlamethrowerWeaponHandler.kt │ │ │ ├── FreezeWeaponHandler.kt │ │ │ ├── IsidaWeaponHandler.kt │ │ │ ├── NullWeaponHandler.kt │ │ │ ├── RailgunWeaponHandler.kt │ │ │ ├── RicochetWeaponHandler.kt │ │ │ ├── ShaftWeaponHandler.kt │ │ │ ├── SmokyWeaponHandler.kt │ │ │ ├── ThunderWeaponHandler.kt │ │ │ ├── TwinsWeaponHandler.kt │ │ │ └── WeaponHandler.kt │ │ ├── chat │ │ └── ChatCommandRegistry.kt │ │ ├── client │ │ ├── BattleEntities.kt │ │ ├── CaptchaLocation.kt │ │ ├── ClientDependency.kt │ │ ├── Entities.kt │ │ ├── Screen.kt │ │ ├── ShotEntities.kt │ │ ├── SocketLocale.kt │ │ ├── User.kt │ │ ├── UserRank.kt │ │ ├── UserSocket.kt │ │ ├── UserSubscriptionManager.kt │ │ ├── WeaponVisuals.kt │ │ └── weapons │ │ │ ├── flamethrower │ │ │ └── Flamethrower.kt │ │ │ ├── freeze │ │ │ └── Freeze.kt │ │ │ ├── isida │ │ │ └── Isida.kt │ │ │ ├── railgun │ │ │ └── Railgun.kt │ │ │ ├── ricochet │ │ │ └── Ricochet.kt │ │ │ ├── shaft │ │ │ └── Shaft.kt │ │ │ ├── smoky │ │ │ └── Smoky.kt │ │ │ ├── thunder │ │ │ └── Thunder.kt │ │ │ └── twins │ │ │ └── Twins.kt │ │ ├── commands │ │ ├── ArgsBehaviour.kt │ │ ├── ArgsBehaviourType.kt │ │ ├── Command.kt │ │ ├── CommandCategory.kt │ │ ├── CommandHandler.kt │ │ ├── CommandHandlerDescription.kt │ │ ├── CommandName.kt │ │ ├── CommandRegistry.kt │ │ ├── CommandSide.kt │ │ ├── ICommandHandler.kt │ │ └── handlers │ │ │ ├── AuthHandler.kt │ │ │ ├── BattleChatHandler.kt │ │ │ ├── BattleHandler.kt │ │ │ ├── BattleSupplyHandler.kt │ │ │ ├── CtfBattleHandler.kt │ │ │ ├── FriendsHandler.kt │ │ │ ├── GarageHandler.kt │ │ │ ├── LobbyChatHandler.kt │ │ │ ├── LobbyHandler.kt │ │ │ ├── QuestsHandler.kt │ │ │ ├── SettingsHandler.kt │ │ │ ├── ShotHandler.kt │ │ │ ├── StoreHandler.kt │ │ │ └── SystemHandler.kt │ │ ├── exceptions │ │ ├── NoSuchProplibException.kt │ │ ├── UnknownCommandCategoryException.kt │ │ └── UnknownCommandException.kt │ │ ├── extensions │ │ ├── BuildConfig.kt │ │ ├── ByteArray.kt │ │ ├── Cast.kt │ │ ├── CoroutineContext.kt │ │ ├── Iterable.kt │ │ ├── Map.kt │ │ ├── MutableList.kt │ │ ├── Random.kt │ │ └── ThreadLocal.kt │ │ ├── garage │ │ ├── ClientEntities.kt │ │ ├── GarageItemConverter.kt │ │ ├── GarageItemType.kt │ │ ├── GarageMarketRegistry.kt │ │ └── ServerEntities.kt │ │ ├── invite │ │ ├── Invite.kt │ │ ├── InviteRepository.kt │ │ └── InviteService.kt │ │ ├── ipc │ │ ├── IProcessNetworking.kt │ │ ├── Messages.kt │ │ ├── NullNetworking.kt │ │ └── WebSocketNetworking.kt │ │ ├── lobby │ │ └── chat │ │ │ └── LobbyChatManager.kt │ │ ├── math │ │ ├── Quaternion.kt │ │ └── Vector3.kt │ │ ├── quests │ │ ├── CapturePointQuest.kt │ │ ├── ClientEntities.kt │ │ ├── DeliverFlagQuest.kt │ │ ├── EarnScoreInModeQuest.kt │ │ ├── EarnScoreOnMapQuest.kt │ │ ├── EarnScoreQuest.kt │ │ ├── JoinBattleMapQuest.kt │ │ ├── KillEnemyQuest.kt │ │ ├── QuestConverter.kt │ │ ├── ServerDailyQuest.kt │ │ ├── ServerDailyQuestReward.kt │ │ ├── ServerDailyRewardType.kt │ │ └── TakeBonusQuest.kt │ │ ├── resources │ │ └── ResourceServer.kt │ │ ├── serialization │ │ ├── BattleDataJsonAdapterFactory.kt │ │ ├── BattleModeAdapter.kt │ │ ├── BattleTeamAdapter.kt │ │ ├── BonusTypeMapAdapter.kt │ │ ├── CaptchaLocationAdapter.kt │ │ ├── ChatModeratorLevelAdapter.kt │ │ ├── ClientLocalizedStringAdapterFactory.kt │ │ ├── EquipmentConstraintsModeAdapter.kt │ │ ├── GarageItemTypeAdapter.kt │ │ ├── IsidaFireModeAdapter.kt │ │ ├── LocalizedStringAdapterFactory.kt │ │ ├── NullIfNullJsonAdapter.kt │ │ ├── ResourceTypeAdapter.kt │ │ ├── ScreenAdapter.kt │ │ ├── SerializeNull.kt │ │ ├── ServerMapThemeAdapter.kt │ │ ├── SkyboxSideAdapter.kt │ │ ├── SocketLocaleAdapter.kt │ │ ├── StoreCurrencyAdapter.kt │ │ └── database │ │ │ ├── BattleModeConverter.kt │ │ │ └── BonusTypeConverter.kt │ │ ├── store │ │ ├── ClientEntities.kt │ │ ├── Entities.kt │ │ ├── StoreCurrency.kt │ │ ├── StoreItemConverter.kt │ │ ├── StorePaymentMethod.kt │ │ └── StoreRegistry.kt │ │ └── utils │ │ ├── BlobUtils.kt │ │ ├── ClientLocalizedString.kt │ │ ├── LocalizedString.kt │ │ └── ResourceUtils.kt └── resources │ ├── META-INF │ └── persistence.xml │ ├── data │ ├── garage │ │ └── items │ │ │ ├── hulls │ │ │ ├── dictator.json │ │ │ ├── hornet.json │ │ │ ├── hunter.json │ │ │ ├── mammoth.json │ │ │ ├── titan.json │ │ │ ├── viking.json │ │ │ └── wasp.json │ │ │ ├── kits │ │ │ ├── aborigine.json │ │ │ ├── annihilator.json │ │ │ ├── ant.json │ │ │ ├── atlas.json │ │ │ ├── avalanche.json │ │ │ ├── blizzard.json │ │ │ ├── boar.json │ │ │ ├── boreas.json │ │ │ ├── bulldozer.json │ │ │ ├── cardinal.json │ │ │ ├── centaur.json │ │ │ ├── chiropractor.json │ │ │ ├── commando.json │ │ │ ├── corsair.json │ │ │ ├── cupid.json │ │ │ ├── cyclops.json │ │ │ ├── destroyer.json │ │ │ ├── fighter.json │ │ │ ├── firebolt.json │ │ │ ├── firefly.json │ │ │ ├── firewall.json │ │ │ ├── fortress.json │ │ │ ├── ghost.json │ │ │ ├── giant.json │ │ │ ├── guard.json │ │ │ ├── guardian.json │ │ │ ├── gunslinger.json │ │ │ ├── hammer_of_thor.json │ │ │ ├── healer.json │ │ │ ├── hero.json │ │ │ ├── hooligan.json │ │ │ ├── huntsman.json │ │ │ ├── inventory_100.json │ │ │ ├── inventory_1000.json │ │ │ ├── inventory_1100.json │ │ │ ├── inventory_1200.json │ │ │ ├── inventory_1300.json │ │ │ ├── inventory_1500.json │ │ │ ├── inventory_200.json │ │ │ ├── inventory_300.json │ │ │ ├── inventory_400.json │ │ │ ├── inventory_500.json │ │ │ ├── inventory_600.json │ │ │ ├── inventory_700.json │ │ │ ├── inventory_800.json │ │ │ ├── inventory_900.json │ │ │ ├── jam.json │ │ │ ├── juggler.json │ │ │ ├── keeper.json │ │ │ ├── legend.json │ │ │ ├── maestro.json │ │ │ ├── man_o_war.json │ │ │ ├── master_of_the_taiga.json │ │ │ ├── medic.json │ │ │ ├── minotaur.json │ │ │ ├── mosquito.json │ │ │ ├── nomad.json │ │ │ ├── northerner.json │ │ │ ├── osaka.json │ │ │ ├── paladin.json │ │ │ ├── piranha.json │ │ │ ├── predator.json │ │ │ ├── prometheus.json │ │ │ ├── raiden.json │ │ │ ├── ram.json │ │ │ ├── redneck.json │ │ │ ├── regrigerator.json │ │ │ ├── rock_climber.json │ │ │ ├── savage.json │ │ │ ├── siberian.json │ │ │ ├── simoom.json │ │ │ ├── sniper.json │ │ │ ├── stinger.json │ │ │ ├── striker.json │ │ │ ├── tornado.json │ │ │ ├── touche.json │ │ │ ├── turtle.json │ │ │ ├── vampire.json │ │ │ ├── vandal.json │ │ │ └── voltage.json │ │ │ ├── paints │ │ │ ├── alien.json │ │ │ ├── black.json │ │ │ ├── blue.json │ │ │ ├── carbon.json │ │ │ ├── cedar.json │ │ │ ├── clay.json │ │ │ ├── corrosion.json │ │ │ ├── desert.json │ │ │ ├── digital.json │ │ │ ├── dirty.json │ │ │ ├── dragon.json │ │ │ ├── electra.json │ │ │ ├── emerald.json │ │ │ ├── flora.json │ │ │ ├── forester.json │ │ │ ├── green.json │ │ │ ├── hohloma.json │ │ │ ├── holiday.json │ │ │ ├── in_love.json │ │ │ ├── inferno.json │ │ │ ├── irbis.json │ │ │ ├── jade.json │ │ │ ├── jaguar.json │ │ │ ├── lava.json │ │ │ ├── lead.json │ │ │ ├── loam.json │ │ │ ├── marine.json │ │ │ ├── mary.json │ │ │ ├── metallic.json │ │ │ ├── moonwalker.json │ │ │ ├── needle.json │ │ │ ├── orange.json │ │ │ ├── picasso.json │ │ │ ├── premium.json │ │ │ ├── prodigi.json │ │ │ ├── python.json │ │ │ ├── red.json │ │ │ ├── rock.json │ │ │ ├── roger.json │ │ │ ├── rustle.json │ │ │ ├── safari.json │ │ │ ├── sandstone.json │ │ │ ├── savanna.json │ │ │ ├── spark.json │ │ │ ├── storm.json │ │ │ ├── swamp.json │ │ │ ├── swash.json │ │ │ ├── taiga.json │ │ │ ├── tundra.json │ │ │ ├── urban.json │ │ │ ├── white.json │ │ │ ├── winter.json │ │ │ └── zeus.json │ │ │ ├── presents │ │ │ ├── badge.json │ │ │ ├── blue_sphere.json │ │ │ ├── brofist.json │ │ │ ├── claws.json │ │ │ ├── dislike.json │ │ │ ├── helmet.json │ │ │ ├── like.json │ │ │ ├── tangled_turret.json │ │ │ ├── teddybear.json │ │ │ └── wrench.json │ │ │ ├── subscriptions │ │ │ ├── premium_effect.json │ │ │ ├── pro_battle.json │ │ │ ├── up_score.json │ │ │ └── up_score_start.json │ │ │ ├── supplies │ │ │ ├── 1000_scores.json │ │ │ ├── armor.json │ │ │ ├── double_damage.json │ │ │ ├── health.json │ │ │ ├── mine.json │ │ │ └── n2o.json │ │ │ └── weapons │ │ │ ├── flamethrower.json │ │ │ ├── freeze.json │ │ │ ├── isida.json │ │ │ ├── railgun.json │ │ │ ├── ricochet.json │ │ │ ├── shaft.json │ │ │ ├── smoky.json │ │ │ ├── thunder.json │ │ │ └── twins.json │ ├── lang │ │ ├── EN.json │ │ └── RU.json │ ├── maps.json │ ├── maps │ │ ├── 2042 │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── abyss │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── aleksandrovsk │ │ │ ├── summer-day.json │ │ │ ├── summer-night.json │ │ │ └── winter-day.json │ │ ├── arena │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── atra │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── barda │ │ │ ├── summer-day.json │ │ │ ├── summer-night.json │ │ │ └── winter-day.json │ │ ├── berlin │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── bobruisk │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── boombox │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── brest │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── bridges │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── camp │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── canyon │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── chornobyl │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── combe │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── courage │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── cross │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── deathtrack │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── deck9 │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── desert │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── duality │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── duel │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── dusseldorf │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── edinburgh │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── esplanade │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── factory │ │ │ └── summer-day.json │ │ ├── farm │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── forest │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── fortknox │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── future │ │ │ ├── summer-day.json │ │ │ └── summer-night.json │ │ ├── garder │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── gravity │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── gubakha │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── highland │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── highways │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── hill │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── industrial_zone │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── iran │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── island │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── kolhoz │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── kungur │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── losttemple │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── madness │ │ │ └── space.json │ │ ├── madness_old │ │ │ └── space.json │ │ ├── magadan │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── magistral │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── massacre │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── molotov │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── montecarlo │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── noise │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── novel │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── opposition │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── osa │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── parma │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── pass │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── pingpong │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── platform │ │ │ └── space.json │ │ ├── polygon │ │ │ ├── summer-day.json │ │ │ ├── summer-night.json │ │ │ └── winter-day.json │ │ ├── redalert │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── rift │ │ │ ├── summer_day.json │ │ │ └── winter_day.json │ │ ├── rio │ │ │ ├── summer-day.json │ │ │ └── summer-night.json │ │ ├── sandal │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── sandbox │ │ │ ├── summer-day.json │ │ │ ├── summer-night.json │ │ │ └── winter-day.json │ │ ├── scope │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── serpuhov │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── shortbridge │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── siege │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── silence │ │ │ ├── summer-day.json │ │ │ ├── summer-night.json │ │ │ └── winter-day.json │ │ ├── silence_moon │ │ │ └── space.json │ │ ├── skylark │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── skyscrapers │ │ │ ├── space.json │ │ │ └── summer-day.json │ │ ├── solikamsk │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── stadium │ │ │ ├── summer-day.json │ │ │ ├── summer-night.json │ │ │ └── winter-day.json │ │ ├── station │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── subway │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── trains │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── tribute │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── valley │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── wave │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ │ ├── wolfenstein │ │ │ ├── summer_day.json │ │ │ └── winter_day.json │ │ └── zone │ │ │ ├── summer-day.json │ │ │ └── winter-day.json │ ├── proplibs.json │ ├── resources │ │ ├── auth-untrusted.json │ │ ├── auth.json │ │ ├── garage.json │ │ └── lobby.json │ ├── shots-data.json │ ├── skyboxes.json │ └── store │ │ └── items │ │ ├── crystals │ │ ├── category.json │ │ ├── crystals_pack_1.json │ │ ├── crystals_pack_2.json │ │ └── crystals_pack_3.json │ │ └── premium │ │ ├── category.json │ │ ├── premuim_pack_1.json │ │ ├── premuim_pack_2.json │ │ └── premuim_pack_3.json │ └── logback.xml └── test └── kotlin └── jp └── assasans └── protanki └── server ├── PacketProcessorTest.kt ├── commands └── CommandTest.kt ├── extensions └── ByteArrayTest.kt └── utils ├── BlobUtilsTest.kt ├── LocalizedStringTest.kt └── ResourceUtilsTest.kt /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - octocat 7 | categories: 8 | - title: Breaking Changes 9 | labels: 10 | - Semver-Major 11 | - breaking-change 12 | - title: New Features 13 | labels: 14 | - Semver-Minor 15 | - enhancement 16 | - title: Bug Fixes 17 | labels: 18 | - Semver-Patch 19 | - bug-fix 20 | - title: Other Changes 21 | labels: 22 | - "*" 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '*.md' 8 | pull_request: 9 | branches: 10 | - main 11 | paths-ignore: 12 | - '*.md' 13 | 14 | jobs: 15 | build: 16 | name: Build and test 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v3 22 | with: 23 | distribution: 'temurin' 24 | java-version: '17' 25 | cache: 'gradle' 26 | - name: Run tests 27 | run: ./gradlew test --no-daemon 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniil Pryima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | This project targets JDK 17. 4 | 5 | Gradle artifacts are located at `build/libs/`. 6 | 7 | ## Using IntelliJ IDEA 8 | 9 | * Open repository in IntelliJ IDEA. 10 | * Run / Debug project. 11 | 12 | To build artifacts, execute Gradle `shadowJar` task. 13 | 14 | ## Using command line 15 | 16 | ### Windows (PowerShell) 17 | 18 | ```powershell 19 | # If you need to use custom JDK: 20 | # $env:JAVA_HOME="C:/path/to/jdk" 21 | 22 | ./gradlew.bat shadowJar 23 | ``` 24 | 25 | ### Unix: 26 | ```bash 27 | # If you need to use custom JDK: 28 | # export JAVA_HOME="/path/to/jdk" 29 | 30 | chmod +x gradlew 31 | ./gradlew shadowJar 32 | ``` 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Assasans/protanki-server/5cfb1e2682c029e47a69cb2910e80d14015c5896/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ProTankiServer" 2 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/EncryptionTransformer.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server 2 | 3 | class EncryptionTransformer { 4 | private val keys = Array(9) { index -> index + 1 } 5 | private var lastKey = 1 6 | 7 | fun decrypt(encrypted: String): String { 8 | val key = encrypted[0].toString().toInt() 9 | 10 | // println("Decrypting $request") 11 | 12 | return encrypted 13 | .drop(1) 14 | .map { value -> Char(value.code - (key + 1)) } 15 | .joinToString("") 16 | } 17 | 18 | fun encrypt(plain: String): String { 19 | var key = (lastKey + 1) % keys.size 20 | if(key <= 0) key = 1 21 | lastKey = key 22 | 23 | return key.toString() + plain 24 | .map { value -> Char(value.code + (key + 1)) } 25 | .joinToString("") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/Hibernate.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server 2 | 3 | import jakarta.persistence.EntityManager 4 | import jakarta.persistence.Persistence 5 | 6 | object HibernateUtils { 7 | private val entityManagerFactory = Persistence.createEntityManagerFactory("jp.assasans.protanki.server") 8 | 9 | fun createEntityManager(): EntityManager = entityManagerFactory.createEntityManager() 10 | fun close() = entityManagerFactory.close() 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/PacketProcessor.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server 2 | 3 | import mu.KotlinLogging 4 | import java.io.ByteArrayOutputStream 5 | import jp.assasans.protanki.server.commands.Command 6 | import jp.assasans.protanki.server.extensions.indexOfSequence 7 | 8 | class PacketProcessor { 9 | private val logger = KotlinLogging.logger {} 10 | 11 | private val output = ByteArrayOutputStream() 12 | 13 | fun write(data: ByteArray) = synchronized(output) { 14 | output.write(data) 15 | 16 | // logger.trace { "Written: ${String(data)}" } 17 | } 18 | 19 | fun tryGetPacket(): String? = synchronized(output) { 20 | val buffer = output.toByteArray() 21 | 22 | val position = buffer.indexOfSequence(Command.Delimiter) 23 | if(position == -1) return null 24 | 25 | val packet = buffer.decodeToString(0, position) 26 | 27 | val offset = position + Command.Delimiter.size 28 | output.reset() 29 | output.write(buffer, offset, buffer.size - offset) 30 | 31 | // logger.trace { "End of packet: $packet" } 32 | 33 | return packet 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/ResourceManager.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server 2 | 3 | import java.nio.file.Path 4 | import java.nio.file.Paths 5 | import kotlin.io.path.exists 6 | 7 | interface IResourceManager { 8 | fun get(path: String): Path 9 | } 10 | 11 | class ResourceManager : IResourceManager { 12 | private val resourceDirectory: Path 13 | 14 | init { 15 | var directory = Paths.get("data") 16 | if(!directory.exists()) directory = Paths.get("../data") // Gradle distribution / jar 17 | if(!directory.exists()) directory = Paths.get("src/main/resources/data") // Started from IntelliJ IDEA, default working directory 18 | if(!directory.exists()) directory = Paths.get("../src/main/resources/data") // Started from IntelliJ IDEA, 'out' working directory 19 | if(!directory.exists()) throw Exception("Cannot find runtime resources directory") 20 | 21 | resourceDirectory = directory 22 | } 23 | 24 | override fun get(path: String): Path { 25 | return resourceDirectory.resolve(path) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/api/Entities.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.api 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Screen 5 | 6 | data class PlayerStats( 7 | @Json val registered: Long, 8 | @Json val online: Int, 9 | @Json val screens: Map 10 | ) 11 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/api/InviteEntities.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.api 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.invite.Invite 5 | 6 | class EmptyResponse 7 | 8 | data class ErrorResponse( 9 | @Json val error: Boolean = true, 10 | @Json val message: String 11 | ) 12 | 13 | data class ToggleInviteServiceRequest(@Json val enabled: Boolean) 14 | data class CreateInviteRequest(@Json val code: String) 15 | data class DeleteInviteRequest(@Json val code: String) 16 | 17 | data class GetInvitesResponse( 18 | @Json val enabled: Boolean, 19 | @Json val invites: List 20 | ) 21 | 22 | data class InviteResponse( 23 | @Json val id: Int, 24 | @Json val code: String 25 | ) 26 | 27 | fun Invite.toResponse(): InviteResponse = InviteResponse( 28 | id = id, 29 | code = code 30 | ) 31 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/FundProcessor.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles 2 | 3 | import org.koin.core.component.KoinComponent 4 | import jp.assasans.protanki.server.commands.Command 5 | import jp.assasans.protanki.server.commands.CommandName 6 | 7 | interface IFundProcessor { 8 | val battle: Battle 9 | var fund: Int 10 | 11 | suspend fun updateFund() 12 | } 13 | 14 | class FundProcessor( 15 | override val battle: Battle 16 | ) : IFundProcessor, KoinComponent { 17 | override var fund: Int = 0 18 | 19 | override suspend fun updateFund() { 20 | Command(CommandName.ChangeFund, fund.toString()).sendTo(battle) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/TankKillType.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles 2 | 3 | enum class TankKillType(val key: String) { 4 | ByPlayer("killed"), 5 | SelfDestruct("suicide"); 6 | 7 | companion object { 8 | private val map = BattleTeam.values().associateBy(BattleTeam::key) 9 | 10 | fun get(key: String) = map[key] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/bonus/BattleDoubleArmorBonus.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.bonus 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.BonusType 5 | import jp.assasans.protanki.server.battles.Battle 6 | import jp.assasans.protanki.server.battles.BattleTank 7 | import jp.assasans.protanki.server.battles.effect.DoubleArmorEffect 8 | import jp.assasans.protanki.server.math.Quaternion 9 | import jp.assasans.protanki.server.math.Vector3 10 | 11 | class BattleDoubleArmorBonus(battle: Battle, id: Int, position: Vector3, rotation: Quaternion) : 12 | BattleBonus(battle, id, position, rotation, 20.seconds) { 13 | override val type: BonusType = BonusType.DoubleArmor 14 | 15 | override suspend fun activate(tank: BattleTank) { 16 | val effect = DoubleArmorEffect(tank) 17 | tank.effects.add(effect) 18 | effect.run() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/bonus/BattleDoubleDamageBonus.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.bonus 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.BonusType 5 | import jp.assasans.protanki.server.battles.Battle 6 | import jp.assasans.protanki.server.battles.BattleTank 7 | import jp.assasans.protanki.server.battles.effect.DoubleDamageEffect 8 | import jp.assasans.protanki.server.math.Quaternion 9 | import jp.assasans.protanki.server.math.Vector3 10 | 11 | class BattleDoubleDamageBonus(battle: Battle, id: Int, position: Vector3, rotation: Quaternion) : 12 | BattleBonus(battle, id, position, rotation, 20.seconds) { 13 | override val type: BonusType = BonusType.DoubleDamage 14 | 15 | override suspend fun activate(tank: BattleTank) { 16 | val effect = DoubleDamageEffect(tank) 17 | tank.effects.add(effect) 18 | effect.run() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/bonus/BattleGoldBonus.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.bonus 2 | 3 | import kotlin.time.Duration.Companion.minutes 4 | import kotlin.time.Duration.Companion.seconds 5 | import kotlinx.coroutines.delay 6 | import jp.assasans.protanki.server.BonusType 7 | import jp.assasans.protanki.server.battles.Battle 8 | import jp.assasans.protanki.server.battles.BattleTank 9 | import jp.assasans.protanki.server.battles.sendTo 10 | import jp.assasans.protanki.server.commands.Command 11 | import jp.assasans.protanki.server.commands.CommandName 12 | import jp.assasans.protanki.server.math.Quaternion 13 | import jp.assasans.protanki.server.math.Vector3 14 | 15 | class BattleGoldBonus(battle: Battle, id: Int, position: Vector3, rotation: Quaternion) : 16 | BattleBonus(battle, id, position, rotation, 10.minutes) { 17 | override val type: BonusType = BonusType.Gold 18 | 19 | override suspend fun spawn() { 20 | Command(CommandName.SpawnGold, "Скоро будет сброшен золотой ящик", 490113.toString()).sendTo(battle) 21 | delay(20.seconds.inWholeMilliseconds) 22 | super.spawn() 23 | } 24 | 25 | override suspend fun activate(tank: BattleTank) { 26 | tank.player.user.crystals += 1000 27 | tank.socket.updateCrystals() 28 | 29 | Command(CommandName.TakeGold, tank.id).sendTo(battle) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/bonus/BattleNitroBonus.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.bonus 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.BonusType 5 | import jp.assasans.protanki.server.battles.Battle 6 | import jp.assasans.protanki.server.battles.BattleTank 7 | import jp.assasans.protanki.server.battles.effect.NitroEffect 8 | import jp.assasans.protanki.server.math.Quaternion 9 | import jp.assasans.protanki.server.math.Vector3 10 | 11 | class BattleNitroBonus(battle: Battle, id: Int, position: Vector3, rotation: Quaternion) : 12 | BattleBonus(battle, id, position, rotation, 20.seconds) { 13 | override val type: BonusType = BonusType.Nitro 14 | 15 | override suspend fun activate(tank: BattleTank) { 16 | val effect = NitroEffect(tank) 17 | tank.effects.add(effect) 18 | effect.run() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/bonus/BattleRepairKitBonus.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.bonus 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.BonusType 5 | import jp.assasans.protanki.server.battles.Battle 6 | import jp.assasans.protanki.server.battles.BattleTank 7 | import jp.assasans.protanki.server.battles.effect.RepairKitEffect 8 | import jp.assasans.protanki.server.math.Quaternion 9 | import jp.assasans.protanki.server.math.Vector3 10 | 11 | class BattleRepairKitBonus(battle: Battle, id: Int, position: Vector3, rotation: Quaternion) : 12 | BattleBonus(battle, id, position, rotation, 20.seconds) { 13 | override val type: BonusType = BonusType.Health 14 | 15 | override suspend fun activate(tank: BattleTank) { 16 | val effect = RepairKitEffect(tank) 17 | tank.effects.add(effect) 18 | effect.run() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/bonus/Entities.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.bonus 2 | 3 | import com.squareup.moshi.Json 4 | 5 | data class SpawnBonusDatta( 6 | @Json val id: String, 7 | @Json val x: Double, 8 | @Json val y: Double, 9 | @Json val z: Double, 10 | @Json val disappearing_time: Int 11 | ) 12 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/effect/DoubleArmorEffect.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.effect 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.BonusType 5 | import jp.assasans.protanki.server.battles.BattleTank 6 | 7 | class DoubleArmorEffect( 8 | tank: BattleTank, 9 | val multiplier: Double = 2.0 10 | ) : TankEffect( 11 | tank, 12 | duration = 55.seconds, 13 | cooldown = 20.seconds 14 | ) { 15 | override val info: EffectInfo 16 | get() = EffectInfo( 17 | id = 2, 18 | name = "armor", 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/effect/DoubleDamageEffect.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.effect 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.battles.BattleTank 5 | 6 | class DoubleDamageEffect( 7 | tank: BattleTank, 8 | val multiplier: Double = 2.0 9 | ) : TankEffect( 10 | tank, 11 | duration = 55.seconds, 12 | cooldown = 20.seconds 13 | ) { 14 | override val info: EffectInfo 15 | get() = EffectInfo( 16 | id = 3, 17 | name = "double_damage", 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/effect/MineEffect.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.effect 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import jp.assasans.protanki.server.battles.BattleMine 5 | import jp.assasans.protanki.server.battles.BattleTank 6 | 7 | class MineEffect( 8 | tank: BattleTank 9 | ) : TankEffect( 10 | tank, 11 | duration = null, 12 | cooldown = 20.seconds 13 | ) { 14 | override val info: EffectInfo 15 | get() = EffectInfo( 16 | id = 5, 17 | name = "mine" 18 | ) 19 | 20 | override suspend fun activate() { 21 | val battle = tank.battle 22 | 23 | val mine = BattleMine(battle.mineProcessor.nextId, tank.player, tank.position) 24 | 25 | battle.mineProcessor.incrementId() 26 | battle.mineProcessor.spawn(mine) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/effect/RepairKitEffect.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.effect 2 | 3 | import kotlin.time.Duration.Companion.seconds 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.datetime.Clock 7 | import jp.assasans.protanki.server.battles.BattleTank 8 | 9 | class RepairKitEffect( 10 | tank: BattleTank 11 | ) : TankEffect( 12 | tank, 13 | duration = 3.seconds, 14 | cooldown = 20.seconds 15 | ) { 16 | override val info: EffectInfo 17 | get() = EffectInfo( 18 | id = 1, 19 | name = "health" 20 | ) 21 | 22 | override suspend fun activate() { 23 | val battle = tank.battle 24 | val damageProcessor = battle.damageProcessor 25 | 26 | // TODO(Assasans): More complicated logic 27 | damageProcessor.heal(tank, 1500.0) 28 | 29 | if(duration == null) return 30 | tank.coroutineScope.launch { 31 | val startTime = Clock.System.now() 32 | val endTime = startTime + duration 33 | while(Clock.System.now() < endTime) { 34 | delay(500) 35 | if(tank.health < tank.maxHealth) { 36 | damageProcessor.heal(tank, 300.0) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/mode/BattleModeHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.mode 2 | 3 | import jp.assasans.protanki.server.battles.Battle 4 | import jp.assasans.protanki.server.battles.BattleMode 5 | import jp.assasans.protanki.server.battles.BattlePlayer 6 | 7 | typealias BattleModeHandlerBuilder = (battle: Battle) -> BattleModeHandler 8 | 9 | abstract class BattleModeHandler( 10 | val battle: Battle 11 | ) { 12 | abstract val mode: BattleMode 13 | 14 | abstract suspend fun playerJoin(player: BattlePlayer) 15 | abstract suspend fun playerLeave(player: BattlePlayer) 16 | abstract suspend fun initModeModel(player: BattlePlayer) 17 | open suspend fun initPostGui(player: BattlePlayer) {} 18 | 19 | open suspend fun dump(builder: StringBuilder) {} 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/mode/TeamDeathmatchModeHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.mode 2 | 3 | import jp.assasans.protanki.server.battles.* 4 | import jp.assasans.protanki.server.client.* 5 | import jp.assasans.protanki.server.commands.Command 6 | import jp.assasans.protanki.server.commands.CommandName 7 | 8 | class TeamDeathmatchModeHandler(battle: Battle) : TeamModeHandler(battle) { 9 | companion object { 10 | fun builder(): BattleModeHandlerBuilder = { battle -> TeamDeathmatchModeHandler(battle) } 11 | } 12 | 13 | override val mode: BattleMode get() = BattleMode.TeamDeathmatch 14 | 15 | override suspend fun initModeModel(player: BattlePlayer) { 16 | Command(CommandName.InitTdmModel).send(player) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/weapons/NullWeaponHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.weapons 2 | 3 | import jp.assasans.protanki.server.battles.BattlePlayer 4 | import jp.assasans.protanki.server.garage.ServerGarageUserItemWeapon 5 | 6 | class NullWeaponHandler( 7 | player: BattlePlayer, 8 | weapon: ServerGarageUserItemWeapon 9 | ) : WeaponHandler(player, weapon) { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/battles/weapons/WeaponHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.battles.weapons 2 | 3 | import org.koin.core.component.KoinComponent 4 | import org.koin.core.component.inject 5 | import jp.assasans.protanki.server.battles.BattlePlayer 6 | import jp.assasans.protanki.server.battles.IDamageCalculator 7 | import jp.assasans.protanki.server.garage.ServerGarageUserItemWeapon 8 | 9 | abstract class WeaponHandler( 10 | val player: BattlePlayer, 11 | val item: ServerGarageUserItemWeapon 12 | ) : KoinComponent { 13 | protected val damageCalculator: IDamageCalculator by inject() 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/CaptchaLocation.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client 2 | 3 | enum class CaptchaLocation(val key: String) { 4 | Login("AUTH"), 5 | Registration("registration"), 6 | 7 | // Not implemented in client 8 | ClientStartup("CLIENT_STARTUP"), 9 | PasswordRestore("RESTORE_PASSWORD_FORM"), 10 | EmailChangeHash("EMAIL_CHANGE_HASH"), 11 | AccountSettings("ACCOUNT_SETTINGS_FORM"); 12 | 13 | companion object { 14 | private val map = values().associateBy(CaptchaLocation::key) 15 | 16 | fun get(key: String) = map[key] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/ClientDependency.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client 2 | 3 | import kotlinx.coroutines.CompletableDeferred 4 | import mu.KotlinLogging 5 | 6 | class ClientDependency( 7 | val id: Int, 8 | private val deferred: CompletableDeferred 9 | ) { 10 | private val logger = KotlinLogging.logger { } 11 | 12 | suspend fun await() { 13 | logger.debug { "Waiting for dependency $id to load..." } 14 | deferred.await() 15 | } 16 | 17 | fun loaded() { 18 | deferred.complete(Unit) 19 | logger.debug { "Marked dependency $id as loaded" } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/Screen.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client 2 | 3 | enum class Screen(val key: String) { 4 | BattleSelect("BATTLE_SELECT"), 5 | Battle("BATTLE"), 6 | Garage("GARAGE"); 7 | 8 | companion object { 9 | private val map = values().associateBy(Screen::key) 10 | 11 | fun get(key: String) = map[key] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/SocketLocale.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client 2 | 3 | enum class SocketLocale(val key: String, val localizationKey: String) { 4 | Russian("RU", "ru"), 5 | English("EN", "en"), 6 | Portuguese("pt_BR", "pt"); 7 | 8 | companion object { 9 | private val map = values().associateBy(SocketLocale::key) 10 | private val mapByLocalization = values().associateBy(SocketLocale::localizationKey) 11 | 12 | fun get(key: String) = map[key] 13 | fun getByLocalization(key: String) = mapByLocalization[key] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/UserSubscriptionManager.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | 5 | class UserSubscription(user: User) { 6 | val rank = MutableStateFlow(user.rank) 7 | } 8 | 9 | interface IUserSubscriptionManager { 10 | fun add(user: User): UserSubscription 11 | 12 | fun getOrNull(id: Int): UserSubscription? 13 | fun get(id: Int): UserSubscription 14 | fun getOrAdd(user: User): UserSubscription 15 | } 16 | 17 | class UserSubscriptionManager : IUserSubscriptionManager { 18 | private val subscriptions = mutableMapOf() 19 | 20 | override fun add(user: User): UserSubscription { 21 | subscriptions[user.id]?.let { return it } 22 | return UserSubscription(user).also { subscriptions[user.id] = it } 23 | } 24 | 25 | override fun getOrNull(id: Int): UserSubscription? = subscriptions[id] 26 | override fun get(id: Int): UserSubscription = getOrNull(id) ?: throw IllegalStateException("User $id not found") 27 | override fun getOrAdd(user: User): UserSubscription = getOrNull(user.id) ?: add(user) 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/flamethrower/Flamethrower.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.flamethrower 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | data class StartFire( 7 | @Json val physTime: Int 8 | ) 9 | 10 | data class FireTarget( 11 | @Json val physTime: Int, 12 | 13 | @Json val targets: List, 14 | @Json val targetIncarnations: List, 15 | 16 | @Json val targetPositions: List, 17 | @Json val hitPositions: List 18 | ) 19 | 20 | data class StopFire( 21 | @Json val physTime: Int 22 | ) 23 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/freeze/Freeze.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.freeze 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | data class StartFire( 7 | @Json val physTime: Int 8 | ) 9 | 10 | data class FireTarget( 11 | @Json val physTime: Int, 12 | 13 | @Json val targets: List, 14 | @Json val targetIncarnations: List, 15 | 16 | @Json val targetPositions: List, 17 | @Json val hitPositions: List 18 | ) 19 | 20 | data class StopFire( 21 | @Json val physTime: Int 22 | ) 23 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/isida/Isida.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.isida 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | enum class IsidaFireMode(val key: String) { 7 | Damage("damage"), 8 | Heal("heal"); 9 | 10 | companion object { 11 | private val map = values().associateBy(IsidaFireMode::key) 12 | 13 | fun get(key: String) = map[key] 14 | } 15 | } 16 | 17 | data class StartFire( 18 | @Json val physTime: Int, 19 | 20 | @Json val target: String, 21 | @Json val incarnation: Int, 22 | 23 | @Json val localHitPoint: Vector3Data 24 | ) 25 | 26 | data class SetTarget( 27 | @Json val physTime: Int, 28 | 29 | @Json val target: String, 30 | @Json val incarnation: Int, 31 | @Json val actionType: IsidaFireMode, 32 | 33 | @Json val localHitPoint: Vector3Data 34 | ) 35 | 36 | data class ResetTarget( 37 | @Json val physTime: Int 38 | ) 39 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/railgun/Railgun.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.railgun 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | // Not used 7 | open class FireDummy( 8 | @Json val physTime: Int 9 | ) 10 | 11 | open class FireStart( 12 | @Json val physTime: Int 13 | ) 14 | 15 | open class FireTarget( 16 | @Json val physTime: Int, 17 | 18 | @Json val targets: List, 19 | @Json val incarnations: List?, 20 | 21 | @Json val staticHitPosition: Vector3Data?, 22 | 23 | @Json val targetPositions: List?, 24 | @Json val hitPositions: List 25 | ) 26 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/ricochet/Ricochet.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.ricochet 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | open class Fire( 7 | @Json val physTime: Int, 8 | 9 | @Json val shotId: Int, 10 | @Json val shotDirectionX: Double, 11 | @Json val shotDirectionY: Double, 12 | @Json val shotDirectionZ: Double 13 | ) 14 | 15 | // Not used 16 | open class FireDummy( 17 | @Json val physTime: Int 18 | ) 19 | 20 | // Not used 21 | open class FireStatic( 22 | @Json val physTime: Int, 23 | 24 | @Json val shotId: Int, 25 | 26 | @Json val impactPoints: List 27 | ) 28 | 29 | open class FireTarget( 30 | @Json val physTime: Int, 31 | 32 | @Json val target: String, 33 | 34 | @Json val shotId: Int, 35 | 36 | @Json val impactPoints: List, 37 | @Json val hitPosition: Vector3Data 38 | ) 39 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/shaft/Shaft.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.shaft 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | data class FireTarget( 7 | @Json val physTime: Int, 8 | 9 | @Json val target: String?, 10 | @Json val incarnation: Int, 11 | 12 | @Json val targetPosition: Vector3Data?, 13 | @Json val targetPositionGlobal: Vector3Data?, 14 | @Json val hitPoint: Vector3Data?, 15 | 16 | @Json val staticHitPosition: Vector3Data? 17 | ) 18 | 19 | data class ShotTarget( 20 | @Json val physTime: Int, 21 | 22 | @Json val target: String?, 23 | @Json val incarnation: Int, 24 | 25 | @Json val targetPosition: Vector3Data?, 26 | @Json val targetPositionGlobal: Vector3Data?, 27 | @Json val hitPoint: Vector3Data?, 28 | 29 | @Json val staticHitPosition: Vector3Data?, 30 | 31 | @Json val impactForce: Double 32 | ) { 33 | constructor( 34 | fireTarget: FireTarget, 35 | impactForce: Double 36 | ) : this( 37 | fireTarget.physTime, 38 | fireTarget.target, 39 | fireTarget.incarnation, 40 | fireTarget.targetPosition, 41 | fireTarget.targetPositionGlobal, 42 | fireTarget.hitPoint, 43 | fireTarget.staticHitPosition, 44 | impactForce 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/smoky/Smoky.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.smoky 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | open class Fire( 7 | @Json val physTime: Int 8 | ) 9 | 10 | open class FireStatic( 11 | @Json val physTime: Int, 12 | @Json val hitPosition: Vector3Data 13 | ) 14 | 15 | open class FireTarget( 16 | @Json val physTime: Int, 17 | 18 | @Json val target: String, 19 | @Json val incration: Int, 20 | 21 | @Json val targetPosition: Vector3Data, 22 | @Json val hitPosition: Vector3Data 23 | ) 24 | 25 | open class ShotTarget( 26 | @Json val physTime: Int, 27 | 28 | @Json val target: String, 29 | @Json val incration: Int, 30 | 31 | @Json val targetPosition: Vector3Data, 32 | @Json val hitPosition: Vector3Data, 33 | 34 | @Json val weakening: Double, 35 | @Json val critical: Boolean 36 | ) { 37 | constructor( 38 | fireTarget: FireTarget, 39 | weakening: Double, 40 | critical: Boolean 41 | ) : this( 42 | fireTarget.physTime, 43 | fireTarget.target, 44 | fireTarget.incration, 45 | fireTarget.targetPosition, 46 | fireTarget.hitPosition, 47 | weakening, 48 | critical 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/thunder/Thunder.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.thunder 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | open class Fire( 7 | @Json val physTime: Int 8 | ) 9 | 10 | open class FireStatic( 11 | @Json val physTime: Int, 12 | 13 | @Json val hitPoint: Vector3Data, 14 | 15 | @Json val splashTargetIds: List, 16 | @Json val splashTargetDistances: List 17 | ) 18 | 19 | data class FireTarget( 20 | @Json val physTime: Int, 21 | 22 | @Json val target: String, 23 | @Json val targetIncarnation: Int, 24 | @Json val targetPosition: Vector3Data, 25 | 26 | @Json val relativeHitPoint: Vector3Data, 27 | @Json val hitPointWorld: Vector3Data, 28 | 29 | @Json val splashTargetIds: List, 30 | @Json val splashTargetDistances: List 31 | ) 32 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/client/weapons/twins/Twins.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.client.weapons.twins 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.client.Vector3Data 5 | 6 | open class Fire( 7 | @Json val physTime: Int, 8 | @Json val shotDirection: Vector3Data, 9 | @Json val currentBarrel: Int, 10 | @Json val shotId: Int 11 | ) 12 | 13 | open class FireStatic( 14 | @Json val physTime: Int, 15 | @Json val hitPoint: Vector3Data, 16 | @Json(name = "currentBarrel") val shotId: Int 17 | ) 18 | 19 | open class FireTarget( 20 | @Json val physTime: Int, 21 | @Json val target: String, 22 | 23 | @Json val targetPosition: Vector3Data, 24 | @Json val hitPoint: Vector3Data, 25 | 26 | @Json val shotId: Int 27 | ) 28 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/ArgsBehaviour.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands 2 | 3 | annotation class ArgsBehaviour(val type: ArgsBehaviourType) 4 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/ArgsBehaviourType.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands 2 | 3 | enum class ArgsBehaviourType { 4 | Arguments, 5 | Raw 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/CommandCategory.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands 2 | 3 | enum class CommandCategory(val key: String) { 4 | System("system"), 5 | Ping("ping"), 6 | 7 | Auth("auth"), 8 | Registration("registration"), 9 | PasswordRestore("restore"), 10 | 11 | Lobby("lobby"), 12 | LobbyChat("lobby_chat"), 13 | 14 | Garage("garage"), 15 | 16 | Battle("battle"), 17 | BattleChat("chat"), 18 | BattleSelect("battle_select"), 19 | BattleCreate("battle_create"); 20 | 21 | companion object { 22 | private val map = values().associateBy(CommandCategory::key) 23 | 24 | fun get(key: String) = map[key] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/CommandHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands 2 | 3 | annotation class CommandHandler(val name: CommandName) 4 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/CommandSide.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands 2 | 3 | enum class CommandSide { 4 | Server, 5 | Client 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/ICommandHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands 2 | 3 | interface ICommandHandler {} 4 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/handlers/LobbyChatHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands.handlers 2 | 3 | import mu.KotlinLogging 4 | import org.koin.core.component.KoinComponent 5 | import org.koin.core.component.inject 6 | import jp.assasans.protanki.server.client.* 7 | import jp.assasans.protanki.server.commands.CommandHandler 8 | import jp.assasans.protanki.server.commands.CommandName 9 | import jp.assasans.protanki.server.commands.ICommandHandler 10 | import jp.assasans.protanki.server.lobby.chat.ILobbyChatManager 11 | 12 | class LobbyChatHandler : ICommandHandler, KoinComponent { 13 | private val logger = KotlinLogging.logger { } 14 | 15 | private val lobbyChatManager by inject() 16 | 17 | @CommandHandler(CommandName.SendChatMessageServer) 18 | suspend fun sendChatMessageServer(socket: UserSocket, nameTo: String, content: String) { 19 | val user = socket.user ?: throw Exception("No User") 20 | 21 | val message = ChatMessage( 22 | name = user.username, 23 | rang = user.rank.value, 24 | message = content, 25 | nameTo = nameTo, 26 | addressed = nameTo.isNotEmpty() 27 | ) 28 | 29 | lobbyChatManager.send(socket, message) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/commands/handlers/SettingsHandler.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.commands.handlers 2 | 3 | import mu.KotlinLogging 4 | import jp.assasans.protanki.server.client.ShowSettingsData 5 | import jp.assasans.protanki.server.client.UserSocket 6 | import jp.assasans.protanki.server.client.send 7 | import jp.assasans.protanki.server.client.toJson 8 | import jp.assasans.protanki.server.commands.Command 9 | import jp.assasans.protanki.server.commands.CommandHandler 10 | import jp.assasans.protanki.server.commands.CommandName 11 | import jp.assasans.protanki.server.commands.ICommandHandler 12 | 13 | class SettingsHandler : ICommandHandler { 14 | private val logger = KotlinLogging.logger { } 15 | 16 | @CommandHandler(CommandName.ShowSettings) 17 | suspend fun showSettings(socket: UserSocket) { 18 | Command(CommandName.ClientShowSettings, ShowSettingsData().toJson()).send(socket) 19 | } 20 | 21 | @CommandHandler(CommandName.CheckPasswordIsSet) 22 | suspend fun checkPasswordIsSet(socket: UserSocket) { 23 | Command(CommandName.PasswordIsSet).send(socket) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/exceptions/NoSuchProplibException.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.exceptions 2 | 3 | class NoSuchProplibException( 4 | val proplib: String, 5 | val map: String?, 6 | cause: Throwable? = null 7 | ) : Exception(getMessage(proplib, map), cause) { 8 | private companion object { 9 | fun getMessage(proplib: String, map: String?): String = buildString { 10 | append("No such proplib: $proplib") 11 | if(map != null) append(", map: $map") 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/exceptions/UnknownCommandCategoryException.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.exceptions 2 | 3 | class UnknownCommandCategoryException : Exception { 4 | val category: String 5 | 6 | constructor(category: String) : super() { 7 | this.category = category 8 | } 9 | 10 | constructor(category: String, message: String) : super(message) { 11 | this.category = category 12 | } 13 | 14 | constructor(category: String, message: String, cause: Throwable) : super(message, cause) { 15 | this.category = category 16 | } 17 | 18 | constructor(category: String, cause: Throwable) : super(cause) { 19 | this.category = category 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/exceptions/UnknownCommandException.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.exceptions 2 | 3 | class UnknownCommandException : Exception { 4 | val category: String 5 | val command: String 6 | 7 | constructor(category: String, command: String) : super() { 8 | this.category = category 9 | this.command = command 10 | } 11 | 12 | constructor(category: String, command: String, message: String) : super(message) { 13 | this.category = category 14 | this.command = command 15 | } 16 | 17 | constructor(category: String, command: String, message: String, cause: Throwable) : super(message, cause) { 18 | this.category = category 19 | this.command = command 20 | } 21 | 22 | constructor(category: String, command: String, cause: Throwable) : super(cause) { 23 | this.category = category 24 | this.command = command 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/BuildConfig.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | import jp.assasans.protanki.server.BuildConfig 4 | 5 | val BuildConfig.gitVersion: String 6 | get() { 7 | val gitSuffix = if(GIT_BRANCH.isNotEmpty() && GIT_COMMIT_HASH.isNotEmpty()) "/$GIT_BRANCH+${GIT_COMMIT_HASH.take(8)}" else "" 8 | return "$VERSION$gitSuffix" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/ByteArray.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | fun ByteArray.indexOfSequence(sequence: ByteArray, startFrom: Int = 0): Int { 4 | if(startFrom < 0) throw IllegalArgumentException("startFrom must be >= 0") 5 | if(this.isEmpty()) return -1 6 | if(sequence.isEmpty()) return 0 7 | if(startFrom + sequence.size >= this.size) return -1 8 | 9 | var index = startFrom 10 | var matchIndex = 0 11 | while(index < size) { 12 | val value = this[index++] 13 | 14 | if(value != sequence[matchIndex]) matchIndex = 0 15 | if(value == sequence[matchIndex]) matchIndex++ 16 | 17 | if(matchIndex == sequence.size) return index - matchIndex 18 | } 19 | 20 | return -1 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/Cast.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | inline fun Any.cast(): T { 4 | if(this !is T) throw TypeCastException("Cannot cast ${this::class} to ${T::class}") 5 | return this 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/CoroutineContext.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | import kotlin.coroutines.CoroutineContext 4 | import kotlin.coroutines.EmptyCoroutineContext 5 | import kotlin.time.Duration 6 | import kotlinx.coroutines.* 7 | 8 | fun CoroutineScope.launchDelayed( 9 | delay: Duration, 10 | context: CoroutineContext = EmptyCoroutineContext, 11 | start: CoroutineStart = CoroutineStart.DEFAULT, 12 | block: suspend CoroutineScope.() -> Unit 13 | ): Job { 14 | return launch(context, start) { 15 | delay(delay.inWholeMilliseconds) 16 | block() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/Iterable.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | // TODO(Assasans): Find a way to not specify the type of the Iterable in calls 4 | inline fun Iterable.singleOf(predicate: (T) -> Boolean = { true }): I = 5 | single { it is I && predicate(it) }.cast() 6 | 7 | inline fun Iterable.singleOrNullOf(predicate: (T) -> Boolean = { true }): I? = 8 | singleOrNull { it is I && predicate(it) }?.cast() 9 | 10 | /** 11 | * Returns the single element matching the given [predicate], 12 | * returns null if there is no matching elements, 13 | * or throws exception if there is more than one matching element. 14 | */ 15 | inline fun Iterable.singleOrNullOrThrow(predicate: (T) -> Boolean = { true }): T? { 16 | val items = filter(predicate) 17 | if(items.isEmpty()) return null 18 | return items.single() 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/Map.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | fun Map.keyOfOrNull(value: V): K? { 4 | for(item in this) { 5 | if(item.value == value) return item.key 6 | } 7 | return null 8 | } 9 | 10 | fun Map.keyOf(value: V): K? { 11 | return keyOfOrNull(value) ?: throw IllegalArgumentException("No such value") 12 | } 13 | 14 | fun Map.filterKeysNotNull(): Map { 15 | @Suppress("UNCHECKED_CAST") 16 | return filter { it.key != null } as Map 17 | } 18 | 19 | fun Map.filterValuesNotNull(): Map { 20 | @Suppress("UNCHECKED_CAST") 21 | return filter { it.value != null } as Map 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/MutableList.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | fun MutableList.truncateLastTo(finalSize: Int) { 4 | if(size <= finalSize) return 5 | removeAll(filterIndexed { index, _ -> (size - finalSize) > index }) 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/Random.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | import kotlin.random.Random 4 | import kotlin.random.asJavaRandom 5 | 6 | fun Random.nextGaussianRange(from: Double, to: Double): Double { 7 | val mean = (from + to) / 2.0 8 | val deviation = (to - from) / 6.0 9 | return (mean + deviation * asJavaRandom().nextGaussian()).coerceIn(from, to) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/extensions/ThreadLocal.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.extensions 2 | 3 | inline fun ThreadLocal.putIfAbsent(put: () -> T): T { 4 | var value = get() 5 | if(value == null) { 6 | value = put() 7 | set(value) 8 | } 9 | 10 | return value 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/garage/GarageItemType.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.garage 2 | 3 | enum class GarageItemType(val key: Int, val categoryKey: String) { 4 | Weapon(1, "weapon"), 5 | Hull(2, "armor"), 6 | Paint(3, "paint"), 7 | Supply(4, "inventory"), 8 | Subscription(5, "special"), 9 | Kit(6, "kit"), 10 | Present(7, "special"); 11 | 12 | companion object { 13 | private val map = values().associateBy(GarageItemType::key) 14 | 15 | fun get(key: Int) = map[key] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/invite/Invite.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.invite 2 | 3 | import jakarta.persistence.* 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import jp.assasans.protanki.server.HibernateUtils 7 | 8 | @Entity 9 | @Table( 10 | name = "invites", 11 | indexes = [ 12 | Index(name = "idx_invites_code", columnList = "code") 13 | ] 14 | ) 15 | data class Invite( 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | val id: Int = 0, 19 | 20 | @Column(nullable = false, unique = true, length = 64) var code: String, 21 | @Column(nullable = true, length = 64) var username: String? 22 | ) { 23 | suspend fun updateUsername() { 24 | HibernateUtils.createEntityManager().let { entityManager -> 25 | entityManager.transaction.begin() 26 | 27 | withContext(Dispatchers.IO) { 28 | entityManager 29 | .createQuery("UPDATE Invite SET username = :username WHERE id = :id") 30 | .setParameter("id", id) 31 | .setParameter("username", username) 32 | .executeUpdate() 33 | } 34 | 35 | entityManager.transaction.commit() 36 | entityManager.close() 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/invite/InviteService.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.invite 2 | 3 | import org.koin.core.component.KoinComponent 4 | import org.koin.core.component.inject 5 | 6 | interface IInviteService { 7 | var enabled: Boolean 8 | 9 | suspend fun getInvite(code: String): Invite? 10 | } 11 | 12 | class InviteService( 13 | override var enabled: Boolean 14 | ) : IInviteService, KoinComponent { 15 | private val inviteRepository: IInviteRepository by inject() 16 | 17 | override suspend fun getInvite(code: String): Invite? { 18 | return inviteRepository.getInvite(code) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/ipc/IProcessNetworking.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.ipc 2 | 3 | import org.koin.java.KoinJavaComponent 4 | import kotlinx.coroutines.flow.SharedFlow 5 | 6 | interface IProcessNetworking { 7 | val events: SharedFlow 8 | 9 | suspend fun run() 10 | suspend fun send(message: ProcessMessage) 11 | suspend fun close() 12 | } 13 | 14 | suspend fun ProcessMessage.send(networking: IProcessNetworking) { 15 | networking.send(this) 16 | } 17 | 18 | suspend fun ProcessMessage.send() { 19 | val networking by KoinJavaComponent.getKoin().inject() 20 | send(networking) 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/ipc/Messages.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.ipc 2 | 3 | import com.squareup.moshi.Json 4 | 5 | abstract class ProcessMessage { 6 | override fun toString() = "${this::class.simpleName}" 7 | } 8 | 9 | // TODO(Assasans): Automatic Response messages 10 | 11 | class ServerStartingMessage : ProcessMessage() 12 | class ServerStartedMessage : ProcessMessage() 13 | 14 | class ServerStopRequest : ProcessMessage() 15 | class ServerStopResponse : ProcessMessage() 16 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/ipc/NullNetworking.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.ipc 2 | 3 | import kotlinx.coroutines.flow.MutableSharedFlow 4 | import kotlinx.coroutines.flow.SharedFlow 5 | import kotlinx.coroutines.flow.asSharedFlow 6 | 7 | class NullNetworking : IProcessNetworking { 8 | override val events: SharedFlow = MutableSharedFlow().asSharedFlow() 9 | 10 | override suspend fun run() {} 11 | override suspend fun send(message: ProcessMessage) {} 12 | override suspend fun close() {} 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/CapturePointQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import jp.assasans.protanki.server.client.SocketLocale 6 | import jp.assasans.protanki.server.client.User 7 | import jp.assasans.protanki.server.utils.LocalizedString 8 | import jp.assasans.protanki.server.utils.toLocalizedString 9 | 10 | @Entity 11 | @DiscriminatorValue("capture_point") 12 | class CapturePointQuest( 13 | id: Int, 14 | user: User, 15 | questIndex: Int, 16 | 17 | current: Int, 18 | required: Int, 19 | 20 | new: Boolean, 21 | completed: Boolean, 22 | 23 | rewards: MutableList 24 | ) : ServerDailyQuest( 25 | id, user, questIndex, 26 | current, required, 27 | new, completed, 28 | rewards 29 | ) { 30 | override val description: LocalizedString 31 | get() = mapOf( 32 | SocketLocale.English to "Capture points", 33 | SocketLocale.Russian to "Захвати точки" 34 | ).toLocalizedString() 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/ClientEntities.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import com.squareup.moshi.Json 4 | 5 | data class OpenQuestsData( 6 | @Json val weeklyQuestDescription: WeeklyQuestDescriptionData, 7 | @Json val quests: List 8 | ) 9 | 10 | data class WeeklyQuestDescriptionData( 11 | @Json val currentQuestLevel: Int = 0, 12 | @Json val currentQuestStreak: Int = 0, 13 | @Json val doneForToday: Boolean = false, 14 | @Json val questImage: Int = 123341, 15 | @Json val rewardImage: Int = 123345 16 | ) 17 | 18 | data class DailyQuest( 19 | @Json val canSkipForFree: Boolean = true, 20 | @Json val description: String, 21 | @Json val finishCriteria: Int, 22 | @Json val image: Int = 412322, 23 | @Json val questId: Int = 58366, 24 | @Json val progress: Int, 25 | @Json val skipCost: Int = 100, 26 | @Json val prizes: List 27 | ) 28 | 29 | data class DailyQuestPrize( 30 | @Json val name: String, 31 | @Json val count: Int 32 | ) 33 | 34 | data class SkipDailyQuestResponseData( 35 | @Json val questId: Int, 36 | @Json val quest: DailyQuest 37 | ) 38 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/DeliverFlagQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import jp.assasans.protanki.server.client.SocketLocale 6 | import jp.assasans.protanki.server.client.User 7 | import jp.assasans.protanki.server.utils.LocalizedString 8 | import jp.assasans.protanki.server.utils.toLocalizedString 9 | 10 | @Entity 11 | @DiscriminatorValue("deliver_flag") 12 | class DeliverFlagQuest( 13 | id: Int, 14 | user: User, 15 | questIndex: Int, 16 | 17 | current: Int, 18 | required: Int, 19 | 20 | new: Boolean, 21 | completed: Boolean, 22 | 23 | rewards: MutableList 24 | ) : ServerDailyQuest( 25 | id, user, questIndex, 26 | current, required, 27 | new, completed, 28 | rewards 29 | ) { 30 | override val description: LocalizedString 31 | get() = mapOf( 32 | SocketLocale.English to "Deliver the flags", 33 | SocketLocale.Russian to "Доставь флаги" 34 | ).toLocalizedString() 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/EarnScoreInModeQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.Convert 4 | import jakarta.persistence.DiscriminatorValue 5 | import jakarta.persistence.Entity 6 | import jp.assasans.protanki.server.battles.BattleMode 7 | import jp.assasans.protanki.server.client.SocketLocale 8 | import jp.assasans.protanki.server.client.User 9 | import jp.assasans.protanki.server.serialization.database.BattleModeConverter 10 | import jp.assasans.protanki.server.utils.LocalizedString 11 | import jp.assasans.protanki.server.utils.toLocalizedString 12 | 13 | @Entity 14 | @DiscriminatorValue("earn_score_mode") 15 | class EarnScoreInModeQuest( 16 | id: Int, 17 | user: User, 18 | questIndex: Int, 19 | 20 | current: Int, 21 | required: Int, 22 | 23 | new: Boolean, 24 | completed: Boolean, 25 | 26 | rewards: MutableList, 27 | 28 | @Convert(converter = BattleModeConverter::class) 29 | val mode: BattleMode 30 | ) : ServerDailyQuest( 31 | id, user, questIndex, 32 | current, required, 33 | new, completed, 34 | rewards 35 | ) { 36 | override val description: LocalizedString 37 | get() = mapOf( 38 | SocketLocale.English to "Earn experience in $mode", 39 | SocketLocale.Russian to "Набери опыт в режиме $mode" 40 | ).toLocalizedString() 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/EarnScoreOnMapQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import jp.assasans.protanki.server.client.SocketLocale 6 | import jp.assasans.protanki.server.client.User 7 | import jp.assasans.protanki.server.utils.LocalizedString 8 | import jp.assasans.protanki.server.utils.toLocalizedString 9 | 10 | @Entity 11 | @DiscriminatorValue("earn_score_map") 12 | class EarnScoreOnMapQuest( 13 | id: Int, 14 | user: User, 15 | questIndex: Int, 16 | 17 | current: Int, 18 | required: Int, 19 | 20 | new: Boolean, 21 | completed: Boolean, 22 | 23 | rewards: MutableList, 24 | 25 | val map: String 26 | ) : ServerDailyQuest( 27 | id, user, questIndex, 28 | current, required, 29 | new, completed, 30 | rewards 31 | ) { 32 | override val description: LocalizedString 33 | get() = mapOf( 34 | SocketLocale.English to "Earn experience on $map map", 35 | SocketLocale.Russian to "Набери опыт на карте $map" 36 | ).toLocalizedString() 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/EarnScoreQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import jp.assasans.protanki.server.client.SocketLocale 6 | import jp.assasans.protanki.server.client.User 7 | import jp.assasans.protanki.server.utils.LocalizedString 8 | import jp.assasans.protanki.server.utils.toLocalizedString 9 | 10 | @Entity 11 | @DiscriminatorValue("earn_score") 12 | class EarnScoreQuest( 13 | id: Int, 14 | user: User, 15 | questIndex: Int, 16 | 17 | current: Int, 18 | required: Int, 19 | 20 | new: Boolean, 21 | completed: Boolean, 22 | 23 | rewards: MutableList 24 | ) : ServerDailyQuest( 25 | id, user, questIndex, 26 | current, required, 27 | new, completed, 28 | rewards 29 | ) { 30 | override val description: LocalizedString 31 | get() = mapOf( 32 | SocketLocale.English to "Earn experience in battles", 33 | SocketLocale.Russian to "Набери опыт в битвах" 34 | ).toLocalizedString() 35 | } 36 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/JoinBattleMapQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.DiscriminatorValue 4 | import jakarta.persistence.Entity 5 | import jp.assasans.protanki.server.client.SocketLocale 6 | import jp.assasans.protanki.server.client.User 7 | import jp.assasans.protanki.server.utils.LocalizedString 8 | import jp.assasans.protanki.server.utils.toLocalizedString 9 | 10 | @Entity 11 | @DiscriminatorValue("join_battle_map") 12 | class JoinBattleMapQuest( 13 | id: Int, 14 | user: User, 15 | questIndex: Int, 16 | 17 | current: Int, 18 | required: Int, 19 | 20 | new: Boolean, 21 | completed: Boolean, 22 | 23 | rewards: MutableList, 24 | 25 | val map: String 26 | ) : ServerDailyQuest( 27 | id, user, questIndex, 28 | current, required, 29 | new, completed, 30 | rewards 31 | ) { 32 | override val description: LocalizedString 33 | get() = mapOf( 34 | SocketLocale.English to "Join battles on $map map", 35 | SocketLocale.Russian to "Зайди в битвы на карте $map" 36 | ).toLocalizedString() 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/QuestConverter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import org.koin.core.component.KoinComponent 4 | import jp.assasans.protanki.server.client.SocketLocale 5 | 6 | interface IQuestConverter { 7 | fun toClientDailyQuest(quest: ServerDailyQuest, locale: SocketLocale): DailyQuest 8 | } 9 | 10 | class QuestConverter : IQuestConverter, KoinComponent { 11 | override fun toClientDailyQuest(quest: ServerDailyQuest, locale: SocketLocale): DailyQuest { 12 | // TODO(Assasans): Quest information 13 | return DailyQuest( 14 | canSkipForFree = false, 15 | description = quest.description.get(locale), 16 | finishCriteria = quest.required, 17 | image = 412322, 18 | questId = quest.id, 19 | progress = quest.current, 20 | skipCost = 1000, 21 | prizes = quest.rewards 22 | .sortedBy { reward -> reward.index } 23 | .map { reward -> 24 | DailyQuestPrize( 25 | name = reward.type.name, 26 | count = reward.count 27 | ) 28 | } 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/ServerDailyQuestReward.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.* 4 | import java.io.Serializable 5 | 6 | @Embeddable 7 | class ServerDailyQuestRewardId( 8 | @ManyToOne 9 | val quest: ServerDailyQuest, 10 | @Column(name = "rewardIndex") val index: Int // INDEX is a reserved word in MariaDB 11 | ) : Serializable { 12 | override fun equals(other: Any?): Boolean { 13 | if(this === other) return true 14 | if(other !is ServerDailyQuestRewardId) return false 15 | 16 | if(quest != other.quest) return false 17 | if(index != other.index) return false 18 | 19 | return true 20 | } 21 | 22 | override fun hashCode(): Int { 23 | var result = quest.hashCode() 24 | result = 31 * result + index.hashCode() 25 | return result 26 | } 27 | } 28 | 29 | @Entity 30 | @Table(name = "daily_quest_rewards") 31 | class ServerDailyQuestReward( 32 | quest: ServerDailyQuest, 33 | index: Int, 34 | 35 | @Convert(converter = ServerDailyRewardTypeConverter::class) 36 | val type: ServerDailyRewardType, 37 | val count: Int 38 | ) { 39 | @EmbeddedId 40 | val id: ServerDailyQuestRewardId = ServerDailyQuestRewardId(quest = quest, index = index) 41 | 42 | val quest 43 | get() = id.quest 44 | 45 | val index 46 | get() = id.index 47 | } 48 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/ServerDailyRewardType.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.AttributeConverter 4 | import jakarta.persistence.Converter 5 | 6 | enum class ServerDailyRewardType(val key: Int) { 7 | Crystals(1), 8 | Premium(2), 9 | ; 10 | 11 | companion object { 12 | private val map = values().associateBy(ServerDailyRewardType::key) 13 | 14 | fun get(key: Int) = map[key] 15 | } 16 | } 17 | 18 | @Converter 19 | class ServerDailyRewardTypeConverter : AttributeConverter { 20 | override fun convertToDatabaseColumn(type: ServerDailyRewardType?): Int? = type?.key 21 | override fun convertToEntityAttribute(key: Int?): ServerDailyRewardType? = key?.let(ServerDailyRewardType::get) 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/quests/TakeBonusQuest.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.quests 2 | 3 | import jakarta.persistence.Convert 4 | import jakarta.persistence.DiscriminatorValue 5 | import jakarta.persistence.Entity 6 | import jp.assasans.protanki.server.BonusType 7 | import jp.assasans.protanki.server.client.SocketLocale 8 | import jp.assasans.protanki.server.client.User 9 | import jp.assasans.protanki.server.serialization.database.BonusTypeConverter 10 | import jp.assasans.protanki.server.utils.LocalizedString 11 | import jp.assasans.protanki.server.utils.toLocalizedString 12 | 13 | @Entity 14 | @DiscriminatorValue("take_bonus") 15 | class TakeBonusQuest( 16 | id: Int, 17 | user: User, 18 | questIndex: Int, 19 | 20 | current: Int, 21 | required: Int, 22 | 23 | new: Boolean, 24 | completed: Boolean, 25 | 26 | rewards: MutableList, 27 | 28 | @Convert(converter = BonusTypeConverter::class) 29 | val bonus: BonusType 30 | ) : ServerDailyQuest( 31 | id, user, questIndex, 32 | current, required, 33 | new, completed, 34 | rewards 35 | ) { 36 | // TODO(Assasans): Localize bonus name 37 | override val description: LocalizedString 38 | get() = mapOf( 39 | SocketLocale.English to "Take ${bonus.name}", 40 | SocketLocale.Russian to "Подбери ${bonus.name}" 41 | ).toLocalizedString() 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/BattleDataJsonAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import java.lang.reflect.Type 4 | import com.squareup.moshi.* 5 | import jp.assasans.protanki.server.client.BattleData 6 | import jp.assasans.protanki.server.client.DmBattleData 7 | import jp.assasans.protanki.server.client.TeamBattleData 8 | 9 | class BattleDataJsonAdapterFactory : JsonAdapter.Factory { 10 | override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { 11 | if(type.rawType == BattleData::class.java) { 12 | return object : JsonAdapter() { 13 | override fun fromJson(reader: JsonReader): BattleData? { 14 | TODO("Not yet implemented") 15 | } 16 | 17 | override fun toJson(writer: JsonWriter, value: BattleData?) { 18 | when(value) { 19 | null -> writer.nullValue() 20 | is DmBattleData -> moshi.adapter(DmBattleData::class.java).toJson(writer, value) 21 | is TeamBattleData -> moshi.adapter(TeamBattleData::class.java).toJson(writer, value) 22 | else -> throw IllegalArgumentException("Unknown battle data type: ${value::class}") 23 | } 24 | } 25 | } 26 | } 27 | return null 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/BattleModeAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.battles.BattleMode 6 | 7 | class BattleModeAdapter { 8 | @ToJson 9 | fun toJson(type: BattleMode): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = BattleMode.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/BattleTeamAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.battles.BattleTeam 6 | 7 | class BattleTeamAdapter { 8 | @ToJson 9 | fun toJson(type: BattleTeam): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = BattleTeam.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/BonusTypeMapAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.BonusType 6 | 7 | class BonusTypeMapAdapter { 8 | @ToJson 9 | fun toJson(type: BonusType): String = type.mapKey 10 | 11 | @FromJson 12 | fun fromJson(value: String) = BonusType.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/CaptchaLocationAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.client.CaptchaLocation 6 | 7 | class CaptchaLocationAdapter { 8 | @ToJson 9 | fun toJson(type: CaptchaLocation): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = CaptchaLocation.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/ChatModeratorLevelAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.client.ChatModeratorLevel 6 | 7 | class ChatModeratorLevelAdapter { 8 | @ToJson 9 | fun toJson(level: ChatModeratorLevel): Int = level.key 10 | 11 | @FromJson 12 | fun fromJson(value: Int) = ChatModeratorLevel.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/EquipmentConstraintsModeAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.client.EquipmentConstraintsMode 6 | 7 | class EquipmentConstraintsModeAdapter { 8 | @ToJson 9 | fun toJson(type: EquipmentConstraintsMode): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = EquipmentConstraintsMode.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/GarageItemTypeAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.garage.GarageItemType 6 | 7 | class GarageItemTypeAdapter { 8 | @ToJson 9 | fun toJson(type: GarageItemType): Int = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: Int) = GarageItemType.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/IsidaFireModeAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.client.weapons.isida.IsidaFireMode 6 | 7 | class IsidaFireModeAdapter { 8 | @ToJson 9 | fun toJson(type: IsidaFireMode): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = IsidaFireMode.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/NullIfNullJsonAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import java.io.IOException 4 | import com.squareup.moshi.JsonAdapter 5 | import com.squareup.moshi.JsonReader 6 | import com.squareup.moshi.JsonWriter 7 | 8 | class NullIfNullJsonAdapter(private val delegate: JsonAdapter) : JsonAdapter() { 9 | @Throws(IOException::class) 10 | override fun fromJson(reader: JsonReader): T? { 11 | return delegate.fromJson(reader) 12 | } 13 | 14 | @Throws(IOException::class) 15 | override fun toJson(writer: JsonWriter, value: T?) { 16 | if(value == null) { 17 | val serializeNulls = writer.serializeNulls 18 | writer.serializeNulls = true 19 | try { 20 | delegate.toJson(writer, value) 21 | } finally { 22 | writer.serializeNulls = serializeNulls 23 | } 24 | } else { 25 | delegate.toJson(writer, value) 26 | } 27 | } 28 | 29 | override fun toString(): String = "$delegate.serializeNulls()" 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/ResourceTypeAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.ResourceType 6 | 7 | class ResourceTypeAdapter { 8 | @ToJson 9 | fun toJson(type: ResourceType): Int = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: Int) = ResourceType.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/ScreenAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.client.Screen 6 | 7 | class ScreenAdapter { 8 | @ToJson 9 | fun toJson(type: Screen): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = Screen.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/SerializeNull.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import java.lang.reflect.Type 4 | import com.squareup.moshi.JsonAdapter 5 | import com.squareup.moshi.JsonQualifier 6 | import com.squareup.moshi.Moshi 7 | import com.squareup.moshi.Types 8 | 9 | @JsonQualifier 10 | annotation class SerializeNull { 11 | companion object { 12 | val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory { 13 | override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { 14 | val nextAnnotations = Types.nextAnnotations(annotations, SerializeNull::class.java) ?: return null 15 | return NullIfNullJsonAdapter(moshi.nextAdapter(this, type, nextAnnotations)) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/ServerMapThemeAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.ServerMapTheme 6 | 7 | class ServerMapThemeAdapter { 8 | @ToJson 9 | fun toJson(type: ServerMapTheme): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = ServerMapTheme.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/SkyboxSideAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.SkyboxSide 6 | 7 | class SkyboxSideAdapter { 8 | @ToJson 9 | fun toJson(type: SkyboxSide): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = SkyboxSide.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/SocketLocaleAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.client.SocketLocale 6 | 7 | class SocketLocaleAdapter { 8 | @ToJson 9 | fun toJson(type: SocketLocale): String = type.localizationKey 10 | 11 | @FromJson 12 | fun fromJson(value: String) = SocketLocale.getByLocalization(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/StoreCurrencyAdapter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization 2 | 3 | import com.squareup.moshi.FromJson 4 | import com.squareup.moshi.ToJson 5 | import jp.assasans.protanki.server.store.StoreCurrency 6 | 7 | class StoreCurrencyAdapter { 8 | @ToJson 9 | fun toJson(type: StoreCurrency): String = type.key 10 | 11 | @FromJson 12 | fun fromJson(value: String) = StoreCurrency.get(value) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/database/BattleModeConverter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization.database 2 | 3 | import jakarta.persistence.AttributeConverter 4 | import jakarta.persistence.Converter 5 | import jp.assasans.protanki.server.battles.BattleMode 6 | 7 | @Converter 8 | class BattleModeConverter : AttributeConverter { 9 | override fun convertToDatabaseColumn(mode: BattleMode?): Int? = mode?.id 10 | override fun convertToEntityAttribute(key: Int?): BattleMode? = key?.let(BattleMode::getById) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/serialization/database/BonusTypeConverter.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.serialization.database 2 | 3 | import jakarta.persistence.AttributeConverter 4 | import jakarta.persistence.Converter 5 | import jp.assasans.protanki.server.BonusType 6 | 7 | @Converter 8 | class BonusTypeConverter : AttributeConverter { 9 | override fun convertToDatabaseColumn(type: BonusType?): Int? = type?.id 10 | override fun convertToEntityAttribute(key: Int?): BonusType? = key?.let(BonusType::getById) 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/store/ClientEntities.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.store 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.utils.ClientLocalizedString 5 | 6 | data class OpenStoreWrapperData( 7 | @Json val have_double_crystals: Boolean = false, 8 | @Json val data: String 9 | ) 10 | 11 | data class OpenStoreData( 12 | @Json val categories: List, 13 | @Json val items: List 14 | ) 15 | 16 | data class StoreCategory( 17 | @Json val category_id: String, 18 | @Json val header_text: ClientLocalizedString, 19 | @Json val description: ClientLocalizedString 20 | ) 21 | 22 | data class StoreItem( 23 | @Json val category_id: String, 24 | @Json val item_id: String, 25 | @Json(name = "additional_data") val properties: StoreItemProperties 26 | ) 27 | 28 | data class StoreItemProperties( 29 | @Json val price: Double, 30 | @Json val currency: String, 31 | 32 | /* CrystalPackageItem */ 33 | @Json(name = "crystalls_count") val crystals: Int?, 34 | @Json(name = "bonus_crystalls") val bonusCrystals: Int?, 35 | 36 | /* PremiumPackageItem */ 37 | /** 38 | * Premium subscription duration in days 39 | */ 40 | @Json(name = "premium_duration") val premiumDuration: Int? 41 | ) 42 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/store/Entities.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.store 2 | 3 | import com.squareup.moshi.Json 4 | import jp.assasans.protanki.server.utils.LocalizedString 5 | 6 | data class ServerStoreCategory( 7 | @Json val title: LocalizedString, 8 | @Json val description: LocalizedString 9 | ) { 10 | lateinit var id: String 11 | lateinit var items: MutableList 12 | } 13 | 14 | data class ServerStoreItem( 15 | val price: Map, 16 | 17 | // One of the following 18 | val crystals: CrystalsPackage? = null, 19 | val premium: PremiumPackage? = null 20 | ) { 21 | lateinit var id: String 22 | lateinit var category: ServerStoreCategory 23 | 24 | data class CrystalsPackage( 25 | @Json val base: Int, 26 | @Json val bonus: Int = 0 27 | ) 28 | 29 | data class PremiumPackage( 30 | @Json val base: Int 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/store/StoreCurrency.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.store 2 | 3 | enum class StoreCurrency(val key: String, val displayName: String) { 4 | JPY("jpy", "JPY"); 5 | 6 | companion object { 7 | private val map = values().associateBy(StoreCurrency::key) 8 | 9 | fun get(key: String) = map[key] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/store/StorePaymentMethod.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.store 2 | 3 | enum class StorePaymentMethod(val key: String) { 4 | Freekassa("freekassa"), 5 | Interkassa("interkassa"), 6 | PayPal("paypal"); 7 | 8 | companion object { 9 | private val map = values().associateBy(StorePaymentMethod::key) 10 | 11 | fun get(key: String) = map[key] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/utils/BlobUtils.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.utils 2 | 3 | class BlobUtils private constructor() { 4 | companion object { 5 | fun decode(encoded: String): ByteArray = encoded 6 | .split(",") 7 | .map { byte -> byte.toByte() } 8 | .toByteArray() 9 | 10 | fun encode(bytes: ByteArray): String = bytes.joinToString(",") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/utils/ClientLocalizedString.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.utils 2 | 3 | import jp.assasans.protanki.server.client.SocketLocale 4 | 5 | class ClientLocalizedString( 6 | val localized: Map 7 | ) { 8 | val default: String 9 | get() = localized[SocketLocale.English] ?: throw IllegalStateException("No default localized string") 10 | 11 | fun get(locale: SocketLocale): String = localized[locale] ?: default 12 | } 13 | 14 | fun Map.toClientLocalizedString() = ClientLocalizedString(this) 15 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/utils/LocalizedString.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.utils 2 | 3 | import jp.assasans.protanki.server.client.SocketLocale 4 | 5 | class LocalizedString( 6 | val localized: Map 7 | ) { 8 | val default: String 9 | get() = localized[SocketLocale.English] ?: throw IllegalStateException("No default localized string") 10 | 11 | fun get(locale: SocketLocale): String = localized[locale] ?: default 12 | } 13 | 14 | fun Map.toLocalizedString() = LocalizedString(this) 15 | -------------------------------------------------------------------------------- /src/main/kotlin/jp/assasans/protanki/server/utils/ResourceUtils.kt: -------------------------------------------------------------------------------- 1 | package jp.assasans.protanki.server.utils 2 | 3 | import jp.assasans.protanki.server.ServerIdResource 4 | 5 | class ResourceUtils private constructor() { 6 | companion object { 7 | // Client-side implementation: alternativa.utils:LoaderUtils/getResourcePath() 8 | fun encodeId(resource: ServerIdResource): List { 9 | return listOf( 10 | (resource.id and 0xff000000 shr 24).toString(8), 11 | (resource.id and 0x00ff0000 shr 16).toString(8), 12 | (resource.id and 0x0000ff00 shr 8).toString(8), 13 | (resource.id and 0x000000ff shr 0).toString(8), 14 | resource.version.toString(8) 15 | ) 16 | } 17 | 18 | fun decodeId(parts: List): ServerIdResource { 19 | return ServerIdResource( 20 | id = (parts[0].toLong(8) shl 24) or 21 | (parts[1].toLong(8) shl 16) or 22 | (parts[2].toLong(8) shl 8) or 23 | (parts[3].toLong(8) shl 0), 24 | version = parts[4].toLong(8) 25 | ) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/aborigine.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "aborigine", 3 | "index": 128, 4 | 5 | "name": "Комплект «Туземец»", 6 | "description": "", 7 | 8 | "baseItemId": 511823, 9 | "previewResourceId": 511823, 10 | 11 | "rank": 13, 12 | "price": 99480, 13 | 14 | "kit": { 15 | "image": 687486, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m2", "count": 1 }, 19 | { "id": "smoky_m2", "count": 1 }, 20 | { "id": "savanna_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/annihilator.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "annihilator", 3 | "index": 146, 4 | 5 | "name": "Комплект «Аннигилятор»", 6 | "description": "", 7 | 8 | "baseItemId": 758173, 9 | "previewResourceId": 758173, 10 | 11 | "rank": 19, 12 | "price": 185820, 13 | 14 | "kit": { 15 | "image": 268558, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m2", "count": 1 }, 19 | { "id": "thunder_m2", "count": 1 }, 20 | { "id": "rustle_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/ant.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ant", 3 | "index": 137, 4 | 5 | "name": "Комплект «Муравей»", 6 | "description": "", 7 | 8 | "baseItemId": 131017, 9 | "previewResourceId": 131017, 10 | 11 | "rank": 16, 12 | "price": 126802, 13 | 14 | "kit": { 15 | "image": 542199, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "wasp_m2", "count": 1 }, 19 | { "id": "isida_m2", "count": 1 }, 20 | { "id": "digital_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/atlas.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "atlas", 3 | "index": 132, 4 | 5 | "name": "Комплект «Атлант»", 6 | "description": "", 7 | 8 | "baseItemId": 428601, 9 | "previewResourceId": 428601, 10 | 11 | "rank": 15, 12 | "price": 109140, 13 | 14 | "kit": { 15 | "image": 416096, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "titan_m2", "count": 1 }, 19 | { "id": "smoky_m2", "count": 1 }, 20 | { "id": "loam_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/avalanche.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "avalanche", 3 | "index": 142, 4 | 5 | "name": "Комплект «Лавина»", 6 | "description": "", 7 | 8 | "baseItemId": 977110, 9 | "previewResourceId": 977110, 10 | 11 | "rank": 17, 12 | "price": 117755, 13 | 14 | "kit": { 15 | "image": 195617, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "hornet_m2", "count": 1 }, 19 | { "id": "freeze_m2", "count": 1 }, 20 | { "id": "cedar_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/blizzard.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blizzard", 3 | "index": 119, 4 | 5 | "name": "Комплект «Буран»", 6 | "description": "", 7 | 8 | "baseItemId": 494235, 9 | "previewResourceId": 494235, 10 | 11 | "rank": 9, 12 | "price": 27800, 13 | 14 | "kit": { 15 | "image": 527428, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "hornet_m1", "count": 1 }, 19 | { "id": "freeze_m1", "count": 1 }, 20 | { "id": "storm_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/boar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "boar", 3 | "index": 143, 4 | 5 | "name": "Комплект «Кабан»", 6 | "description": "", 7 | 8 | "baseItemId": 855481, 9 | "previewResourceId": 855481, 10 | 11 | "rank": 18, 12 | "price": 185670, 13 | 14 | "kit": { 15 | "image": 395966, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "dictator_m2", "count": 1 }, 19 | { "id": "ricochet_m2", "count": 1 }, 20 | { "id": "sandstone_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/boreas.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "boreas", 3 | "index": 163, 4 | 5 | "name": "Комплект «Борей»", 6 | "description": "", 7 | 8 | "baseItemId": 900943, 9 | "previewResourceId": 900943, 10 | 11 | "rank": 25, 12 | "price": 447000, 13 | 14 | "kit": { 15 | "image": 528275, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "wasp_m3", "count": 1 }, 19 | { "id": "freeze_m3", "count": 1 }, 20 | { "id": "taiga_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/bulldozer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bulldozer", 3 | "index": 164, 4 | 5 | "name": "Комплект «Бульдозер»", 6 | "description": "", 7 | 8 | "baseItemId": 658830, 9 | "previewResourceId": 658830, 10 | 11 | "rank": 26, 12 | "price": 400000, 13 | 14 | "kit": { 15 | "image": 891798, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "mammoth_m3", "count": 1 }, 19 | { "id": "thunder_m3", "count": 1 }, 20 | { "id": "emerald_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/cardinal.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cardinal", 3 | "index": 155, 4 | 5 | "name": "Комплект «Кардинал»", 6 | "description": "", 7 | 8 | "baseItemId": 426788, 9 | "previewResourceId": 426788, 10 | 11 | "rank": 23, 12 | "price": 468000, 13 | 14 | "kit": { 15 | "image": 673110, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "titan_m3", "count": 1 }, 19 | { "id": "twins_m3", "count": 1 }, 20 | { "id": "picasso_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/centaur.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "centaur", 3 | "index": 170, 4 | 5 | "name": "Комплект «Кентавр»", 6 | "description": "", 7 | 8 | "baseItemId": 660966, 9 | "previewResourceId": 660966, 10 | 11 | "rank": 27, 12 | "price": 420750, 13 | 14 | "kit": { 15 | "image": 842400, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "hornet_m3", "count": 1 }, 19 | { "id": "shaft_m3", "count": 1 }, 20 | { "id": "prodigi_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/chiropractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "chiropractor", 3 | "index": 148, 4 | 5 | "name": "Комплект «Костоправ»", 6 | "description": "", 7 | 8 | "baseItemId": 521658, 9 | "previewResourceId": 521658, 10 | 11 | "rank": 21, 12 | "price": 230618, 13 | 14 | "kit": { 15 | "image": 20255, 16 | "discount": 35, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "shaft_m2", "count": 1 }, 20 | { "id": "loam_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/commando.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "commando", 3 | "index": 109, 4 | 5 | "name": "Комплект «Коммандо»", 6 | "description": "", 7 | 8 | "baseItemId": 620162, 9 | "previewResourceId": 620162, 10 | 11 | "rank": 3, 12 | "price": 2325, 13 | 14 | "kit": { 15 | "image": 357931, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "viking_m0", "count": 1 }, 19 | { "id": "railgun_m0", "count": 1 }, 20 | { "id": "thunder_m0", "count": 1 }, 21 | { "id": "ricochet_m0", "count": 1 } 22 | ], 23 | "isTimeless": true 24 | }, 25 | 26 | "properties": [ ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/corsair.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "corsair", 3 | "index": 129, 4 | 5 | "name": "Комплект «Корсар»", 6 | "description": "", 7 | 8 | "baseItemId": 195900, 9 | "previewResourceId": 195900, 10 | 11 | "rank": 14, 12 | "price": 90860, 13 | 14 | "kit": { 15 | "image": 475042, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "viking_m1", "count": 1 }, 19 | { "id": "twins_m2", "count": 1 }, 20 | { "id": "urban_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/cupid.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cupid", 3 | "index": 121, 4 | 5 | "name": "Комплект «Купидон»", 6 | "description": "", 7 | 8 | "baseItemId": 254380, 9 | "previewResourceId": 254380, 10 | 11 | "rank": 11, 12 | "price": 53100, 13 | 14 | "kit": { 15 | "image": 852231, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m1", "count": 1 }, 19 | { "id": "freeze_m1", "count": 1 }, 20 | { "id": "in_love_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/cyclops.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cyclops", 3 | "index": 145, 4 | 5 | "name": "Комплект «Циклоп»", 6 | "description": "", 7 | 8 | "baseItemId": 807884, 9 | "previewResourceId": 807884, 10 | 11 | "rank": 19, 12 | "price": 173670, 13 | 14 | "kit": { 15 | "image": 208105, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "mammoth_m2", "count": 1 }, 19 | { "id": "shaft_m2", "count": 1 }, 20 | { "id": "hohloma_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/destroyer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "destroyer", 3 | "index": 169, 4 | 5 | "name": "Комплект «Разрушитель»", 6 | "description": "", 7 | 8 | "baseItemId": 490205, 9 | "previewResourceId": 490205, 10 | 11 | "rank": 26, 12 | "price": 486000, 13 | 14 | "kit": { 15 | "image": 215066, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "mammoth_m3", "count": 1 }, 19 | { "id": "twins_m3", "count": 1 }, 20 | { "id": "jade_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/fighter.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "fighter", 3 | "index": 118, 4 | 5 | "name": "Комплект «Истребитель»", 6 | "description": "", 7 | 8 | "baseItemId": 583003, 9 | "previewResourceId": 583003, 10 | 11 | "rank": 9, 12 | "price": 38417, 13 | 14 | "kit": { 15 | "image": 241164, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "hornet_m1", "count": 1 }, 19 | { "id": "twins_m1", "count": 1 }, 20 | { "id": "desert_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/firebolt.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "firebolt", 3 | "index": 130, 4 | 5 | "name": "Комплект «Всполох»", 6 | "description": "", 7 | 8 | "baseItemId": 765374, 9 | "previewResourceId": 765374, 10 | 11 | "rank": 14, 12 | "price": 140310, 13 | 14 | "kit": { 15 | "image": 360111, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "wasp_m2", "count": 1 }, 19 | { "id": "flamethrower_m2", "count": 1 }, 20 | { "id": "rustle_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/firefly.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "firefly", 3 | "index": 111, 4 | 5 | "name": "Комплект «Светлячок»", 6 | "description": "", 7 | 8 | "baseItemId": 743352, 9 | "previewResourceId": 743352, 10 | 11 | "rank": 6, 12 | "price": 12750, 13 | 14 | "kit": { 15 | "image": 890775, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "wasp_m1", "count": 1 }, 19 | { "id": "flamethrower_m1", "count": 1 }, 20 | { "id": "carbon_m0", "count": 1 } 21 | ], 22 | "isTimeless": false, 23 | "timeLeft": 501010 24 | }, 25 | 26 | "properties": [ ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/firewall.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "firewall", 3 | "index": 127, 4 | 5 | "name": "«Огненная стена»", 6 | "description": "", 7 | 8 | "baseItemId": 525234, 9 | "previewResourceId": 525234, 10 | 11 | "rank": 13, 12 | "price": 117480, 13 | 14 | "kit": { 15 | "image": 643659, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m2", "count": 1 }, 19 | { "id": "flamethrower_m2", "count": 1 }, 20 | { "id": "hohloma_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/fortress.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "fortress", 3 | "index": 144, 4 | 5 | "name": "Комплект «Крепость»", 6 | "description": "", 7 | 8 | "baseItemId": 19784, 9 | "previewResourceId": 19784, 10 | 11 | "rank": 18, 12 | "price": 180990, 13 | 14 | "kit": { 15 | "image": 811482, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "mammoth_m2", "count": 1 }, 19 | { "id": "thunder_m2", "count": 1 }, 20 | { "id": "rustle_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/ghost.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ghost", 3 | "index": 153, 4 | 5 | "name": "Комплект «Призрак»", 6 | "description": "", 7 | 8 | "baseItemId": 390649, 9 | "previewResourceId": 390649, 10 | 11 | "rank": 22, 12 | "price": 360000, 13 | 14 | "kit": { 15 | "image": 126433, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "wasp_m3", "count": 1 }, 19 | { "id": "smoky_m3", "count": 1 }, 20 | { "id": "needle_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/giant.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "giant", 3 | "index": 167, 4 | 5 | "name": "Комплект «Исполин»", 6 | "description": "", 7 | 8 | "baseItemId": 749551, 9 | "previewResourceId": 749551, 10 | 11 | "rank": 26, 12 | "price": 552500, 13 | 14 | "kit": { 15 | "image": 136056, 16 | "discount": 35, 17 | "items": [ 18 | { "id": "mammoth_m3", "count": 1 }, 19 | { "id": "ricochet_m3", "count": 1 }, 20 | { "id": "zeus_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/guard.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "guard", 3 | "index": 141, 4 | 5 | "name": "Комплект «Часовой»", 6 | "description": "", 7 | 8 | "baseItemId": 175184, 9 | "previewResourceId": 175184, 10 | 11 | "rank": 17, 12 | "price": 139500, 13 | 14 | "kit": { 15 | "image": 353567, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "titan_m2", "count": 1 }, 19 | { "id": "thunder_m2", "count": 1 }, 20 | { "id": "urban_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/guardian.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "guardian", 3 | "index": 156, 4 | 5 | "name": "Комплект «Страж»", 6 | "description": "", 7 | 8 | "baseItemId": 583689, 9 | "previewResourceId": 583689, 10 | 11 | "rank": 23, 12 | "price": 366000, 13 | 14 | "kit": { 15 | "image": 150010, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "titan_m3", "count": 1 }, 19 | { "id": "smoky_m3", "count": 1 }, 20 | { "id": "needle_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/gunslinger.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "gunslinger", 3 | "index": 117, 4 | 5 | "name": "Комплект «Стрелок»", 6 | "description": "", 7 | 8 | "baseItemId": 149964, 9 | "previewResourceId": 149964, 10 | 11 | "rank": 9, 12 | "price": 45060, 13 | 14 | "kit": { 15 | "image": 239833, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hornet_m1", "count": 1 }, 19 | { "id": "railgun_m1", "count": 1 }, 20 | { "id": "desert_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/hammer_of_thor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hammer_of_thor", 3 | "index": 120, 4 | 5 | "name": "Комплект «Молот Тора»", 6 | "description": "", 7 | 8 | "baseItemId": 192377, 9 | "previewResourceId": 192377, 10 | 11 | "rank": 11, 12 | "price": 52650, 13 | 14 | "kit": { 15 | "image": 752766, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m1", "count": 1 }, 19 | { "id": "ricochet_m1", "count": 1 }, 20 | { "id": "alien_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/healer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "healer", 3 | "index": 162, 4 | 5 | "name": "Комплект «Целитель»", 6 | "description": "", 7 | 8 | "baseItemId": 834458, 9 | "previewResourceId": 834458, 10 | 11 | "rank": 25, 12 | "price": 396000, 13 | 14 | "kit": { 15 | "image": 499689, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hornet_m3", "count": 1 }, 19 | { "id": "isida_m3", "count": 1 }, 20 | { "id": "irbis_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/hero.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hero", 3 | "index": 116, 4 | 5 | "name": "Комплект «Герой»", 6 | "description": "", 7 | 8 | "baseItemId": 486103, 9 | "previewResourceId": 486103, 10 | 11 | "rank": 8, 12 | "price": 31590, 13 | 14 | "kit": { 15 | "image": 384066, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "dictator_m1", "count": 1 }, 19 | { "id": "railgun_m1", "count": 1 }, 20 | { "id": "tundra_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/hooligan.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hooligan", 3 | "index": 124, 4 | 5 | "name": "Комплект «Хулиган»", 6 | "description": "", 7 | 8 | "baseItemId": 703411, 9 | "previewResourceId": 703411, 10 | 11 | "rank": 13, 12 | "price": 81200, 13 | 14 | "kit": { 15 | "image": 248829, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "hornet_m1", "count": 1 }, 19 | { "id": "smoky_m2", "count": 1 }, 20 | { "id": "hohloma_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/huntsman.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "huntsman", 3 | "index": 159, 4 | 5 | "name": "Комплект «Утёс»", 6 | "description": "", 7 | 8 | "baseItemId": 884861, 9 | "previewResourceId": 884861, 10 | 11 | "rank": 25, 12 | "price": 352000, 13 | 14 | "kit": { 15 | "image": 408662, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "hornet_m3", "count": 1 }, 19 | { "id": "smoky_m3", "count": 1 }, 20 | { "id": "rock_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_100.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_100", 3 | "index": 92, 4 | 5 | "name": "Комплект «Припасы 100»", 6 | "description": "", 7 | 8 | "baseItemId": 417664, 9 | "previewResourceId": 417664, 10 | 11 | "rank": 6, 12 | "price": 17500, 13 | 14 | "kit": { 15 | "image": 800215, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "health_m0", "count": 100 }, 19 | { "id": "armor_m0", "count": 100 }, 20 | { "id": "double_damage_m0", "count": 100 }, 21 | { "id": "n2o_m0", "count": 100 }, 22 | { "id": "mine_m0", "count": 100 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_1000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_1000", 3 | "index": 101, 4 | 5 | "name": "Комплект «Припасы 1000»", 6 | "description": "", 7 | 8 | "baseItemId": 752370, 9 | "previewResourceId": 752370, 10 | 11 | "rank": 22, 12 | "price": 105000, 13 | 14 | "kit": { 15 | "image": 397117, 16 | "discount": 70, 17 | "items": [ 18 | { "id": "health_m0", "count": 1000 }, 19 | { "id": "armor_m0", "count": 1000 }, 20 | { "id": "double_damage_m0", "count": 1000 }, 21 | { "id": "n2o_m0", "count": 1000 }, 22 | { "id": "mine_m0", "count": 1000 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_1100.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_1100", 3 | "index": 102, 4 | 5 | "name": "Комплект «Припасы 1100»", 6 | "description": "", 7 | 8 | "baseItemId": 188269, 9 | "previewResourceId": 188269, 10 | 11 | "rank": 24, 12 | "price": 115500, 13 | 14 | "kit": { 15 | "image": 910731, 16 | "discount": 70, 17 | "items": [ 18 | { "id": "health_m0", "count": 1100 }, 19 | { "id": "armor_m0", "count": 1100 }, 20 | { "id": "double_damage_m0", "count": 1100 }, 21 | { "id": "n2o_m0", "count": 1100 }, 22 | { "id": "mine_m0", "count": 1100 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_1200.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_1200", 3 | "index": 103, 4 | 5 | "name": "Комплект «Припасы 1200»", 6 | "description": "", 7 | 8 | "baseItemId": 566195, 9 | "previewResourceId": 566195, 10 | 11 | "rank": 26, 12 | "price": 102000, 13 | 14 | "kit": { 15 | "image": 100096, 16 | "discount": 75, 17 | "items": [ 18 | { "id": "health_m0", "count": 1200 }, 19 | { "id": "armor_m0", "count": 1200 }, 20 | { "id": "double_damage_m0", "count": 1200 }, 21 | { "id": "n2o_m0", "count": 1200 }, 22 | { "id": "mine_m0", "count": 1200 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_1300.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_1300", 3 | "index": 104, 4 | 5 | "name": "Комплект «Припасы 1300»", 6 | "description": "", 7 | 8 | "baseItemId": 355510, 9 | "previewResourceId": 355510, 10 | 11 | "rank": 28, 12 | "price": 110500, 13 | 14 | "kit": { 15 | "image": 674687, 16 | "discount": 75, 17 | "items": [ 18 | { "id": "health_m0", "count": 1300 }, 19 | { "id": "armor_m0", "count": 1300 }, 20 | { "id": "double_damage_m0", "count": 1300 }, 21 | { "id": "n2o_m0", "count": 1300 }, 22 | { "id": "mine_m0", "count": 1300 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_1500.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_1500", 3 | "index": 105, 4 | 5 | "name": "Комплект «Припасы 1500»", 6 | "description": "", 7 | 8 | "baseItemId": 30158, 9 | "previewResourceId": 30158, 10 | 11 | "rank": 30, 12 | "price": 97500, 13 | 14 | "kit": { 15 | "image": 727483, 16 | "discount": 80, 17 | "items": [ 18 | { "id": "health_m0", "count": 1500 }, 19 | { "id": "armor_m0", "count": 1500 }, 20 | { "id": "double_damage_m0", "count": 1500 }, 21 | { "id": "n2o_m0", "count": 1500 }, 22 | { "id": "mine_m0", "count": 1500 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_200", 3 | "index": 93, 4 | 5 | "name": "Комплект «Припасы 200»", 6 | "description": "", 7 | 8 | "baseItemId": 390894, 9 | "previewResourceId": 390894, 10 | 11 | "rank": 7, 12 | "price": 35000, 13 | 14 | "kit": { 15 | "image": 617155, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "health_m0", "count": 200 }, 19 | { "id": "armor_m0", "count": 200 }, 20 | { "id": "double_damage_m0", "count": 200 }, 21 | { "id": "n2o_m0", "count": 200 }, 22 | { "id": "mine_m0", "count": 200 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_300.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_300", 3 | "index": 94, 4 | 5 | "name": "Комплект «Припасы 300»", 6 | "description": "", 7 | 8 | "baseItemId": 922189, 9 | "previewResourceId": 922189, 10 | 11 | "rank": 8, 12 | "price": 52500, 13 | 14 | "kit": { 15 | "image": 348039, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "health_m0", "count": 300 }, 19 | { "id": "armor_m0", "count": 300 }, 20 | { "id": "double_damage_m0", "count": 300 }, 21 | { "id": "n2o_m0", "count": 300 }, 22 | { "id": "mine_m0", "count": 300 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_400.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_400", 3 | "index": 95, 4 | 5 | "name": "Комплект «Припасы 400»", 6 | "description": "", 7 | 8 | "baseItemId": 783992, 9 | "previewResourceId": 783992, 10 | 11 | "rank": 10, 12 | "price": 62000, 13 | 14 | "kit": { 15 | "image": 662139, 16 | "discount": 55, 17 | "items": [ 18 | { "id": "health_m0", "count": 400 }, 19 | { "id": "armor_m0", "count": 400 }, 20 | { "id": "double_damage_m0", "count": 400 }, 21 | { "id": "n2o_m0", "count": 400 }, 22 | { "id": "mine_m0", "count": 400 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_500.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_500", 3 | "index": 96, 4 | 5 | "name": "Комплект «Припасы 500»", 6 | "description": "", 7 | 8 | "baseItemId": 922542, 9 | "previewResourceId": 922542, 10 | 11 | "rank": 12, 12 | "price": 77500, 13 | 14 | "kit": { 15 | "image": 227538, 16 | "discount": 55, 17 | "items": [ 18 | { "id": "health_m0", "count": 500 }, 19 | { "id": "armor_m0", "count": 500 }, 20 | { "id": "double_damage_m0", "count": 500 }, 21 | { "id": "n2o_m0", "count": 500 }, 22 | { "id": "mine_m0", "count": 500 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_600.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_600", 3 | "index": 97, 4 | 5 | "name": "Комплект «Припасы 600»", 6 | "description": "", 7 | 8 | "baseItemId": 892482, 9 | "previewResourceId": 892482, 10 | 11 | "rank": 14, 12 | "price": 81000, 13 | 14 | "kit": { 15 | "image": 389934, 16 | "discount": 60, 17 | "items": [ 18 | { "id": "health_m0", "count": 600 }, 19 | { "id": "armor_m0", "count": 600 }, 20 | { "id": "double_damage_m0", "count": 600 }, 21 | { "id": "n2o_m0", "count": 600 }, 22 | { "id": "mine_m0", "count": 600 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_700.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_700", 3 | "index": 98, 4 | 5 | "name": "Комплект «Припасы 700»", 6 | "description": "", 7 | 8 | "baseItemId": 184586, 9 | "previewResourceId": 184586, 10 | 11 | "rank": 16, 12 | "price": 94500, 13 | 14 | "kit": { 15 | "image": 387176, 16 | "discount": 60, 17 | "items": [ 18 | { "id": "health_m0", "count": 700 }, 19 | { "id": "armor_m0", "count": 700 }, 20 | { "id": "double_damage_m0", "count": 700 }, 21 | { "id": "n2o_m0", "count": 700 }, 22 | { "id": "mine_m0", "count": 700 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_800.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_800", 3 | "index": 99, 4 | 5 | "name": "Комплект «Припасы 800»", 6 | "description": "", 7 | 8 | "baseItemId": 419174, 9 | "previewResourceId": 419174, 10 | 11 | "rank": 18, 12 | "price": 96000, 13 | 14 | "kit": { 15 | "image": 239194, 16 | "discount": 65, 17 | "items": [ 18 | { "id": "health_m0", "count": 800 }, 19 | { "id": "armor_m0", "count": 800 }, 20 | { "id": "double_damage_m0", "count": 800 }, 21 | { "id": "n2o_m0", "count": 800 }, 22 | { "id": "mine_m0", "count": 800 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/inventory_900.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inventory_900", 3 | "index": 100, 4 | 5 | "name": "Комплект «Припасы 900»", 6 | "description": "", 7 | 8 | "baseItemId": 252605, 9 | "previewResourceId": 252605, 10 | 11 | "rank": 20, 12 | "price": 108000, 13 | 14 | "kit": { 15 | "image": 864803, 16 | "discount": 65, 17 | "items": [ 18 | { "id": "health_m0", "count": 900 }, 19 | { "id": "armor_m0", "count": 900 }, 20 | { "id": "double_damage_m0", "count": 900 }, 21 | { "id": "n2o_m0", "count": 900 }, 22 | { "id": "mine_m0", "count": 900 } 23 | ], 24 | "isTimeless": true 25 | }, 26 | 27 | "properties": [ ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/jam.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "jam", 3 | "index": 136, 4 | 5 | "name": "Комплект «Джем»", 6 | "description": "", 7 | 8 | "baseItemId": 141147, 9 | "previewResourceId": 141147, 10 | 11 | "rank": 16, 12 | "price": 155310, 13 | 14 | "kit": { 15 | "image": 275793, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "dictator_m2", "count": 1 }, 19 | { "id": "twins_m2", "count": 1 }, 20 | { "id": "python_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/juggler.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "juggler", 3 | "index": 147, 4 | 5 | "name": "Комплект «Жонглер»", 6 | "description": "", 7 | 8 | "baseItemId": 524803, 9 | "previewResourceId": 524803, 10 | 11 | "rank": 21, 12 | "price": 225540, 13 | 14 | "kit": { 15 | "image": 187107, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "ricochet_m2", "count": 1 }, 20 | { "id": "digital_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/keeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "keeper", 3 | "index": 114, 4 | 5 | "name": "Комплект «Хранитель»", 6 | "description": "", 7 | 8 | "baseItemId": 482387, 9 | "previewResourceId": 482387, 10 | 11 | "rank": 7, 12 | "price": 12677, 13 | 14 | "kit": { 15 | "image": 817161, 16 | "discount": 60, 17 | "items": [ 18 | { "id": "titan_m1", "count": 1 }, 19 | { "id": "smoky_m1", "count": 1 }, 20 | { "id": "roger_m0", "count": 1 } 21 | ], 22 | "isTimeless": false, 23 | "timeLeft": 514808 24 | }, 25 | 26 | "properties": [ ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/legend.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "legend", 3 | "index": 161, 4 | 5 | "name": "Комплект «Легенда»", 6 | "description": "", 7 | 8 | "baseItemId": 396547, 9 | "previewResourceId": 396547, 10 | 11 | "rank": 25, 12 | "price": 451750, 13 | 14 | "kit": { 15 | "image": 100527, 16 | "discount": 35, 17 | "items": [ 18 | { "id": "hornet_m3", "count": 1 }, 19 | { "id": "railgun_m3", "count": 1 }, 20 | { "id": "prodigi_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/maestro.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "maestro", 3 | "index": 166, 4 | 5 | "name": "Комплект «Маэстро»", 6 | "description": "", 7 | 8 | "baseItemId": 135239, 9 | "previewResourceId": 135239, 10 | 11 | "rank": 26, 12 | "price": 516000, 13 | 14 | "kit": { 15 | "image": 254796, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "dictator_m3", "count": 1 }, 19 | { "id": "ricochet_m3", "count": 1 }, 20 | { "id": "picasso_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/man_o_war.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "man_o_war", 3 | "index": 160, 4 | 5 | "name": "Комплект «Воин»", 6 | "description": "", 7 | 8 | "baseItemId": 855523, 9 | "previewResourceId": 855523, 10 | 11 | "rank": 25, 12 | "price": 459000, 13 | 14 | "kit": { 15 | "image": 796471, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hornet_m3", "count": 1 }, 19 | { "id": "thunder_m3", "count": 1 }, 20 | { "id": "taiga_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/master_of_the_taiga.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "master_of_the_taiga", 3 | "index": 176, 4 | 5 | "name": "Комплект «Хозяин тайги»", 6 | "description": "", 7 | 8 | "baseItemId": 80128, 9 | "previewResourceId": 80128, 10 | 11 | "rank": 27, 12 | "price": 441000, 13 | 14 | "kit": { 15 | "image": 156904, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m3", "count": 1 }, 19 | { "id": "twins_m3", "count": 1 }, 20 | { "id": "taiga_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/medic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "medic", 3 | "index": 173, 4 | 5 | "name": "Комплект «Санитар»", 6 | "description": "", 7 | 8 | "baseItemId": 84693, 9 | "previewResourceId": 84693, 10 | 11 | "rank": 27, 12 | "price": 510000, 13 | 14 | "kit": { 15 | "image": 138495, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m3", "count": 1 }, 19 | { "id": "isida_m3", "count": 1 }, 20 | { "id": "picasso_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/minotaur.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "minotaur", 3 | "index": 152, 4 | 5 | "name": "Комплект «Минотавр»", 6 | "description": "", 7 | 8 | "baseItemId": 969240, 9 | "previewResourceId": 969240, 10 | 11 | "rank": 22, 12 | "price": 367620, 13 | 14 | "kit": { 15 | "image": 489242, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m2", "count": 1 }, 19 | { "id": "twins_m3", "count": 1 }, 20 | { "id": "emerald_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/mosquito.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "mosquito", 3 | "index": 131, 4 | 5 | "name": "Комплект «Комар»", 6 | "description": "", 7 | 8 | "baseItemId": 333579, 9 | "previewResourceId": 333579, 10 | 11 | "rank": 15, 12 | "price": 114990, 13 | 14 | "kit": { 15 | "image": 652351, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "wasp_m2", "count": 1 }, 19 | { "id": "railgun_m2", "count": 1 }, 20 | { "id": "savanna_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/nomad.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nomad", 3 | "index": 158, 4 | 5 | "name": "Комплект «Кочевник»", 6 | "description": "", 7 | 8 | "baseItemId": 95492, 9 | "previewResourceId": 95492, 10 | 11 | "rank": 23, 12 | "price": 445249, 13 | 14 | "kit": { 15 | "image": 634104, 16 | "discount": 35, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "railgun_m3", "count": 1 }, 20 | { "id": "taiga_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/northerner.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "northerner", 3 | "index": 149, 4 | 5 | "name": "Комплект «Варяг»", 6 | "description": "", 7 | 8 | "baseItemId": 739955, 9 | "previewResourceId": 739955, 10 | 11 | "rank": 21, 12 | "price": 265620, 13 | 14 | "kit": { 15 | "image": 15895, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m2", "count": 1 }, 19 | { "id": "smoky_m3", "count": 1 }, 20 | { "id": "winter_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/osaka.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "osaka", 3 | "index": 134, 4 | 5 | "name": "Комплект «Осака»", 6 | "description": "", 7 | 8 | "baseItemId": 962041, 9 | "previewResourceId": 962041, 10 | 11 | "rank": 16, 12 | "price": 118750, 13 | 14 | "kit": { 15 | "image": 531101, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "hunter_m2", "count": 1 }, 19 | { "id": "isida_m2", "count": 1 }, 20 | { "id": "electra_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/paladin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "paladin", 3 | "index": 175, 4 | 5 | "name": "Комплект «Паладин»", 6 | "description": "", 7 | 8 | "baseItemId": 25966, 9 | "previewResourceId": 25966, 10 | 11 | "rank": 27, 12 | "price": 407000, 13 | 14 | "kit": { 15 | "image": 114450, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "viking_m3", "count": 1 }, 19 | { "id": "smoky_m3", "count": 1 }, 20 | { "id": "inferno_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/piranha.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "piranha", 3 | "index": 122, 4 | 5 | "name": "Комплект «Пиранья»", 6 | "description": "", 7 | 8 | "baseItemId": 533887, 9 | "previewResourceId": 533887, 10 | 11 | "rank": 11, 12 | "price": 42955, 13 | 14 | "kit": { 15 | "image": 965911, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "hornet_m1", "count": 1 }, 19 | { "id": "shaft_m1", "count": 1 }, 20 | { "id": "corrosion_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/predator.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "predator", 3 | "index": 125, 4 | 5 | "name": "Комплект «Хищник»", 6 | "description": "", 7 | 8 | "baseItemId": 841306, 9 | "previewResourceId": 841306, 10 | 11 | "rank": 13, 12 | "price": 68750, 13 | 14 | "kit": { 15 | "image": 480054, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "hunter_m2", "count": 1 }, 19 | { "id": "thunder_m1", "count": 1 }, 20 | { "id": "jaguar_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/prometheus.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "prometheus", 3 | "index": 151, 4 | 5 | "name": "Комплект «Дивий»", 6 | "description": "", 7 | 8 | "baseItemId": 288940, 9 | "previewResourceId": 288940, 10 | 11 | "rank": 21, 12 | "price": 360000, 13 | 14 | "kit": { 15 | "image": 288506, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "flamethrower_m3", "count": 1 }, 20 | { "id": "needle_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/raiden.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "raiden", 3 | "index": 174, 4 | 5 | "name": "Комплект «Райден»", 6 | "description": "", 7 | 8 | "baseItemId": 714942, 9 | "previewResourceId": 714942, 10 | 11 | "rank": 27, 12 | "price": 486000, 13 | 14 | "kit": { 15 | "image": 802465, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m3", "count": 1 }, 19 | { "id": "thunder_m3", "count": 1 }, 20 | { "id": "emerald_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/ram.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ram", 3 | "index": 133, 4 | 5 | "name": "Комплект «Таран»", 6 | "description": "", 7 | 8 | "baseItemId": 671068, 9 | "previewResourceId": 671068, 10 | 11 | "rank": 15, 12 | "price": 121440, 13 | 14 | "kit": { 15 | "image": 846623, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "titan_m2", "count": 1 }, 19 | { "id": "twins_m2", "count": 1 }, 20 | { "id": "hohloma_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/redneck.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "redneck", 3 | "index": 165, 4 | 5 | "name": "Комплект «Реднек»", 6 | "description": "", 7 | 8 | "baseItemId": 234018, 9 | "previewResourceId": 234018, 10 | 11 | "rank": 26, 12 | "price": 480000, 13 | 14 | "kit": { 15 | "image": 322006, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "ricochet_m3", "count": 1 }, 20 | { "id": "clay_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/regrigerator.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "regrigerator", 3 | "index": 172, 4 | 5 | "name": "Комплект «Гроза»", 6 | "description": "", 7 | 8 | "baseItemId": 243929, 9 | "previewResourceId": 243929, 10 | 11 | "rank": 27, 12 | "price": 510000, 13 | 14 | "kit": { 15 | "image": 226368, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "viking_m3", "count": 1 }, 19 | { "id": "freeze_m3", "count": 1 }, 20 | { "id": "zeus_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/rock_climber.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "rock_climber", 3 | "index": 168, 4 | 5 | "name": "Комплект «Скалолаз»", 6 | "description": "", 7 | 8 | "baseItemId": 405746, 9 | "previewResourceId": 405746, 10 | 11 | "rank": 26, 12 | "price": 426000, 13 | 14 | "kit": { 15 | "image": 171873, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "titan_m3", "count": 1 }, 19 | { "id": "ricochet_m3", "count": 1 }, 20 | { "id": "rock_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/savage.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "savage", 3 | "index": 112, 4 | 5 | "name": "Комплект «Дикарь»", 6 | "description": "", 7 | 8 | "baseItemId": 431685, 9 | "previewResourceId": 431685, 10 | 11 | "rank": 6, 12 | "price": 10625, 13 | 14 | "kit": { 15 | "image": 66990, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "wasp_m1", "count": 1 }, 19 | { "id": "smoky_m1", "count": 1 }, 20 | { "id": "carbon_m0", "count": 1 } 21 | ], 22 | "isTimeless": false, 23 | "timeLeft": 499125 24 | }, 25 | 26 | "properties": [ ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/siberian.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "siberian", 3 | "index": 126, 4 | 5 | "name": "Комплект «Сибиряк»", 6 | "description": "", 7 | 8 | "baseItemId": 846169, 9 | "previewResourceId": 846169, 10 | 11 | "rank": 13, 12 | "price": 50500, 13 | 14 | "kit": { 15 | "image": 660670, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "hunter_m2", "count": 1 }, 19 | { "id": "freeze_m1", "count": 1 }, 20 | { "id": "tundra_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/simoom.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "simoom", 3 | "index": 140, 4 | 5 | "name": "Комплект «Самум»", 6 | "description": "", 7 | 8 | "baseItemId": 353894, 9 | "previewResourceId": 353894, 10 | 11 | "rank": 17, 12 | "price": 165302, 13 | 14 | "kit": { 15 | "image": 686233, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "dictator_m2", "count": 1 }, 19 | { "id": "thunder_m2", "count": 1 }, 20 | { "id": "sandstone_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/sniper.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "sniper", 3 | "index": 171, 4 | 5 | "name": "Комплект «Снайпер»", 6 | "description": "", 7 | 8 | "baseItemId": 289461, 9 | "previewResourceId": 289461, 10 | 11 | "rank": 27, 12 | "price": 565500, 13 | 14 | "kit": { 15 | "image": 915892, 16 | "discount": 35, 17 | "items": [ 18 | { "id": "viking_m3", "count": 1 }, 19 | { "id": "shaft_m3", "count": 1 }, 20 | { "id": "clay_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/stinger.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "stinger", 3 | "index": 138, 4 | 5 | "name": "Комплект «Стингер»", 6 | "description": "", 7 | 8 | "baseItemId": 310874, 9 | "previewResourceId": 310874, 10 | 11 | "rank": 17, 12 | "price": 143820, 13 | 14 | "kit": { 15 | "image": 133241, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hornet_m2", "count": 1 }, 19 | { "id": "isida_m2", "count": 1 }, 20 | { "id": "urban_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/striker.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "striker", 3 | "index": 123, 4 | 5 | "name": "Комплект «Страйкер»", 6 | "description": "", 7 | 8 | "baseItemId": 677607, 9 | "previewResourceId": 677607, 10 | 11 | "rank": 13, 12 | "price": 57900, 13 | 14 | "kit": { 15 | "image": 478398, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "viking_m1", "count": 1 }, 19 | { "id": "smoky_m2", "count": 1 }, 20 | { "id": "swash_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/tornado.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tornado", 3 | "index": 139, 4 | 5 | "name": "Комплект «Торнадо»", 6 | "description": "", 7 | 8 | "baseItemId": 33331, 9 | "previewResourceId": 33331, 10 | 11 | "rank": 17, 12 | "price": 174480, 13 | 14 | "kit": { 15 | "image": 34073, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hornet_m2", "count": 1 }, 19 | { "id": "railgun_m2", "count": 1 }, 20 | { "id": "spark_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/touche.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "touche", 3 | "index": 157, 4 | 5 | "name": "Комплект «Туше»", 6 | "description": "", 7 | 8 | "baseItemId": 297237, 9 | "previewResourceId": 297237, 10 | 11 | "rank": 23, 12 | "price": 432249, 13 | 14 | "kit": { 15 | "image": 650392, 16 | "discount": 35, 17 | "items": [ 18 | { "id": "wasp_m3", "count": 1 }, 19 | { "id": "railgun_m3", "count": 1 }, 20 | { "id": "prodigi_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/turtle.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "turtle", 3 | "index": 150, 4 | 5 | "name": "Комплект «Град»", 6 | "description": "", 7 | 8 | "baseItemId": 672648, 9 | "previewResourceId": 672648, 10 | 11 | "rank": 21, 12 | "price": 342000, 13 | 14 | "kit": { 15 | "image": 654396, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "smoky_m3", "count": 1 }, 20 | { "id": "irbis_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/vampire.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vampire", 3 | "index": 115, 4 | 5 | "name": "Комплект «Вампир»", 6 | "description": "", 7 | 8 | "baseItemId": 348673, 9 | "previewResourceId": 348673, 10 | 11 | "rank": 8, 12 | "price": 18500, 13 | 14 | "kit": { 15 | "image": 89108, 16 | "discount": 50, 17 | "items": [ 18 | { "id": "wasp_m1", "count": 1 }, 19 | { "id": "isida_m1", "count": 1 }, 20 | { "id": "mary_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/vandal.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vandal", 3 | "index": 154, 4 | 5 | "name": "Комплект «Вандал»", 6 | "description": "", 7 | 8 | "baseItemId": 393067, 9 | "previewResourceId": 393067, 10 | 11 | "rank": 22, 12 | "price": 372000, 13 | 14 | "kit": { 15 | "image": 997998, 16 | "discount": 40, 17 | "items": [ 18 | { "id": "hunter_m3", "count": 1 }, 19 | { "id": "twins_m3", "count": 1 }, 20 | { "id": "rock_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/kits/voltage.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "voltage", 3 | "index": 135, 4 | 5 | "name": "Комплект «Вольтаж»", 6 | "description": "", 7 | 8 | "baseItemId": 803390, 9 | "previewResourceId": 803390, 10 | 11 | "rank": 16, 12 | "price": 139012, 13 | 14 | "kit": { 15 | "image": 401654, 16 | "discount": 45, 17 | "items": [ 18 | { "id": "dictator_m2", "count": 1 }, 19 | { "id": "railgun_m2", "count": 1 }, 20 | { "id": "electra_m0", "count": 1 } 21 | ], 22 | "isTimeless": true 23 | }, 24 | 25 | "properties": [ ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/alien.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "alien", 3 | "index": 46, 4 | 5 | "name": { 6 | "en": "Alien", 7 | "ru": "Чужой" 8 | }, 9 | "description": { 10 | "en": "Rumor has it that this camouflage is a replica of the cover of an alien ship that was knocked down in the last century by the forces of «WARNING: This information is classified — Further inquiries on the matter will be met with deadly force.»", 11 | "ru": "Существует легенда, что этот камуфляж копирует окраску летающей тарелки, в прошлом веке сбитой ПВО сами-знаете-кого сами-знаете-где." 12 | }, 13 | 14 | "baseItemId": 254675, 15 | "previewResourceId": 254675, 16 | 17 | "rank": 12, 18 | "price": 24500, 19 | 20 | "coloring": 931624, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 23 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 8 }, 25 | { "property": "THUNDER_RESISTANCE", "value": 8 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/black.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "black", 3 | "index": 28, 4 | 5 | "name": { 6 | "en": "Black", 7 | "ru": "Чёрный" 8 | }, 9 | "description": { 10 | "en": "This color has become the unofficial standard for the saboteur tank division, whose main task is to penetrate enemy territory during the night. Within military circles, these elite divisions have been nicknamed «The black spots of death».", 11 | "ru": "Данная расцветка стала негласным стандартом для диверсионных танковых дивизий, основной задачей которых является скрытное проникновение на территорию врага в темное время суток. В армейских кругах танки из таких элитных дивизий за глаза с завистью называют «чёрные пятна смерти»." 12 | }, 13 | 14 | "baseItemId": 468704, 15 | "previewResourceId": 468704, 16 | 17 | "rank": 1, 18 | "price": 100, 19 | 20 | "coloring": 976299, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blue", 3 | "index": 27, 4 | 5 | "name": { 6 | "en": "Blue", 7 | "ru": "Синий" 8 | }, 9 | "description": { 10 | "en": "This paint was added to the official list in memory of a failed project - the development of an amphibious tank codenamed «Catfish». Designers were planning on giving the the ability to move beneath the waters of shallow rivers, allowing it to take position behind enemy lines. The blue color would have been perfect for camouflage.", 11 | "ru": "Эту расцветку оставили в официальном перечне, как память о провалившемся проекте разработки танка-амфибии «Сом». По задумке конструкторов, он должен был передвигаться по руслам неглубоких рек, и, благодаря синей расцветке, беспрепятственно проникать в глубокий вражеский тыл, оставаясь незамеченным." 12 | }, 13 | 14 | "baseItemId": 350240, 15 | "previewResourceId": 350240, 16 | 17 | "rank": 1, 18 | "price": 100, 19 | 20 | "coloring": 98644, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/carbon.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "carbon", 3 | "index": 42, 4 | 5 | "name": { 6 | "en": "Carbon", 7 | "ru": "Карбон" 8 | }, 9 | "description": { 10 | "en": "A combination of high-tech carbon fiber and high-resistance paint. Includes a special substance meant for disrupting enemy sighting systems, even though this is prohibited by the Tank Movement Inspection Directorate.", 11 | "ru": "Сочетание высоких технологий, углеродного волокна и особой краски повышенной стойкости. В комплект поставки входит тонировка на прицельные комплексы, но по закону она запрещена требованиями инспекции танкового движения" 12 | }, 13 | 14 | "baseItemId": 865963, 15 | "previewResourceId": 865963, 16 | 17 | "rank": 9, 18 | "price": 6500, 19 | 20 | "coloring": 739386, 21 | 22 | "properties": [ 23 | { "property": "TWINS_RESISTANCE", "value": 6 }, 24 | { "property": "SMOKY_RESISTANCE", "value": 19 }, 25 | { "property": "SHAFT_RESISTANCE", "value": 6 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/cedar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cedar", 3 | "index": 48, 4 | 5 | "name": { 6 | "en": "Cedar", 7 | "ru": "Кедр" 8 | }, 9 | "description": { 10 | "en": "This paint is composed of pounded pine cone flakes. It is environmentally friendly and comes with two bags of peeled pine nuts with every second canister of paint purchased.", 11 | "ru": "Для создания маскирующего покрытия «Кедр» используется сложный состав на основе толчёных чешуек кедровых шишек. Его неоспоримое достоинство — стопроцентная экологичность производства и два мешка очищенных кедровых орешков в подарок за каждую вторую канистру краски." 12 | }, 13 | 14 | "baseItemId": 5343, 15 | "previewResourceId": 5343, 16 | 17 | "rank": 13, 18 | "price": 30500, 19 | 20 | "coloring": 438964, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 6 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 31 }, 25 | { "property": "SMOKY_RESISTANCE", "value": 6 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/clay.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "clay", 3 | "index": 71, 4 | 5 | "name": { 6 | "en": "Clay", 7 | "ru": "Тина" 8 | }, 9 | "description": { 10 | "en": "This paint was designed for sabotage operations in the marshland. The tanks wearing this paint were camouflaged so well that not even the leeches noticed them.", 11 | "ru": "Краска под кодовым названием «Тина» разрабатывалась для диверсионных танковых бригад, действующих в болотистой местности. Танки маскировались настолько хорошо, что даже пиявки не замечали их и на полном ходу врезались в бронированный корпус." 12 | }, 13 | 14 | "baseItemId": 700822, 15 | "previewResourceId": 700822, 16 | 17 | "rank": 29, 18 | "price": 240000, 19 | 20 | "coloring": 141405, 21 | 22 | "properties": [ 23 | { "property": "RICOCHET_RESISTANCE", "value": 50 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 20 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 30 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/corrosion.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "corrosion", 3 | "index": 44, 4 | 5 | "name": { 6 | "en": "Corrosion", 7 | "ru": "Ржавчина" 8 | }, 9 | "description": { 10 | "en": "For the tanker who wants to go beyond using bullets, and venture into the realm of chemical tetanus-powered warfare, the «Corrosion» paint is the perfect companion.", 11 | "ru": "Антипафос, антигламур и вызов моде! Кровавая ржавчина — выбор суровых танкистов, презирающих лоск и блеск. Полученная путём окисления металлической стружки в ванночках с кровью, эта краска говорит о брутальности и твёрдости характера танкиста." 12 | }, 13 | 14 | "baseItemId": 92554, 15 | "previewResourceId": 92554, 16 | 17 | "rank": 11, 18 | "price": 18500, 19 | 20 | "coloring": 967180, 21 | 22 | "properties": [ 23 | { "property": "RAILGUN_RESISTANCE", "value": 10 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 13 }, 25 | { "property": "SHAFT_RESISTANCE", "value": 13 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/desert.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "desert", 3 | "index": 50, 4 | 5 | "name": { 6 | "en": "Desert", 7 | "ru": "Пустыня" 8 | }, 9 | "description": { 10 | "en": "A special paint ideal for desert operations. It is created by spraying crystalline powder extracted from a sandworm's teeth. To maintain its unique texture, this paint should not be washed.", 11 | "ru": "Специальная краска, идеально подходящая для операций в пустыне. Она создается путем распыления кристаллического порошка, извлеченного из зубов песчаного червя. Чтобы сохранить свою уникальную текстуру, эту краску не следует мыть." 12 | }, 13 | 14 | "baseItemId": 112041, 15 | "previewResourceId": 112041, 16 | 17 | "rank": 14, 18 | "price": 36500, 19 | 20 | "coloring": 82151, 21 | 22 | "properties": [ 23 | { "property": "TWINS_RESISTANCE", "value": 7 }, 24 | { "property": "RICOCHET_RESISTANCE", "value": 24 }, 25 | { "property": "SMOKY_RESISTANCE", "value": 7 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/digital.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "digital", 3 | "index": 56, 4 | 5 | "name": { 6 | "en": "Digital", 7 | "ru": "Диджитал" 8 | }, 9 | "description": { 10 | "en": "Besides having exceptional camouflage properties, this paint is extremely durable and contains special nanobots for automatic cleaning of the paint while in combat.", 11 | "ru": "Это не просто краска, это настоящий защитный комплекс. В его состав входят покраска особой стойкости, имеющая повышенные камуфлирующие свойства, и специальные наноботы для автоматической самоочистки краски в любых условиях боя." 12 | }, 13 | 14 | "baseItemId": 332230, 15 | "previewResourceId": 332230, 16 | 17 | "rank": 18, 18 | "price": 76600, 19 | 20 | "coloring": 444408, 21 | 22 | "properties": [ 23 | { "property": "SMOKY_RESISTANCE", "value": 21 }, 24 | { "property": "RAILGUN_RESISTANCE", "value": 32 }, 25 | { "property": "SHAFT_RESISTANCE", "value": 12 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/dirty.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "dirty", 3 | "index": 51, 4 | 5 | "name": { 6 | "en": "Dirty", 7 | "ru": "Грязь" 8 | }, 9 | "description": { 10 | "en": "This was originally intended as the third paint designed for ground operations in a mid-latitude climate. However, the military later decided not to include it in the standard package, after repeated attempts by staff cleaners to remove the painted mud texture from the cover.", 11 | "ru": "Эта краска должна была быть третьей краской, специально разработанной для наземных операций в средних климатических широтах. По решению военных покрытие не было включено в стандартный военный пакет из-за нескольких несчастных случаев на испытаниях, связанных с попыткой отчистить это покрытие от нарисованной грязи." 12 | }, 13 | 14 | "baseItemId": 201886, 15 | "previewResourceId": 201886, 16 | 17 | "rank": 15, 18 | "price": 42500, 19 | 20 | "coloring": 386784, 21 | 22 | "properties": [ 23 | { "property": "RAILGUN_RESISTANCE", "value": 16 }, 24 | { "property": "SHAFT_RESISTANCE", "value": 36 } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/dragon.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "dragon", 3 | "index": 38, 4 | 5 | "name": { 6 | "en": "Dragon", 7 | "ru": "Дракон" 8 | }, 9 | "description": { 10 | "en": "The inspiration for the prototype of this complex creation, was the dragon scale. Each scale is drawn by hand on a reinforced piece of titanium plate armor.", 11 | "ru": "Прототипом при создании этой сложной композитной окраски послужила чешуя драконов, каждая чешуйка рисуется вручную на усиленной титановыми пластинами броне." 12 | }, 13 | 14 | "baseItemId": 107643, 15 | "previewResourceId": 107643, 16 | 17 | "rank": 7, 18 | "price": 2500, 19 | 20 | "coloring": 192378, 21 | 22 | "properties": [ 23 | { "property": "RICOCHET_RESISTANCE", "value": 20 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/electra.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "electra", 3 | "index": 58, 4 | 5 | "name": { 6 | "en": "Electra", 7 | "ru": "Электра" 8 | }, 9 | "description": { 10 | "en": "A shockingly beautiful paint that captures the elegance of an arc of electricity flying through the air.", 11 | "ru": "Одна из самых красивых красок, разработанная при участии модельных домов Франции. «Электра» имеет хорошие камуфлирующие свойства и эстетичный внешний вид. К сожалению, не все способны по достоинству оценить изящество линий и глубину работы французских мастеров." 12 | }, 13 | 14 | "baseItemId": 964904, 15 | "previewResourceId": 964904, 16 | 17 | "rank": 19, 18 | "price": 90000, 19 | 20 | "coloring": 858789, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 40 }, 24 | { "property": "TWINS_RESISTANCE", "value": 15 }, 25 | { "property": "SMOKY_RESISTANCE", "value": 15 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/emerald.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "emerald", 3 | "index": 69, 4 | 5 | "name": { 6 | "en": "Emerald", 7 | "ru": "Изумруд" 8 | }, 9 | "description": { 10 | "en": "The researchers at the paint laboratory «Style and Grill» have long worked on a paint that would retain the classic green tone of a tank, while at the same time emphasizing the high status of its owner. The result is the «Emerald» paint, which appears to be illuminated from within.", 11 | "ru": "Лаборатория лакокрасочных материалов «Стиль и гриль» долго работала над покрытием, которое сохранит классические зелёные тона танка и в то же время подчеркнёт высокий статус его владельца. Итогом многолетних трудов стала краска «Изумруд», будто светящаяся изнутри. Подчеркните своё положение!" 12 | }, 13 | 14 | "baseItemId": 16227, 15 | "previewResourceId": 16227, 16 | 17 | "rank": 28, 18 | "price": 228000, 19 | 20 | "coloring": 448000, 21 | 22 | "properties": [ 23 | { "property": "TWINS_RESISTANCE", "value": 35 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 50 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 15 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/flora.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "flora", 3 | "index": 31, 4 | 5 | "name": { 6 | "en": "Flora", 7 | "ru": "Флора" 8 | }, 9 | "description": { 10 | "en": "This is one of two paints specifically designed for ground operations in a mid latitude climate. According to reports from the independent organization «True Camouflage», a tank wearing this particular combination of yellow, green and brown, is less likely to be detected in such regions.", 11 | "ru": "Одна из двух расцветок, специально разработанных для наземных операций в средних климатических широтах. Судя по отчетам независимой организации «Верный камуфляж», такая комбинация жёлтых, зелёных и коричневых пятен снижает вероятность обнаружения танка в «родной стихии»." 12 | }, 13 | 14 | "baseItemId": 95646, 15 | "previewResourceId": 95646, 16 | 17 | "rank": 2, 18 | "price": 500, 19 | 20 | "coloring": 817810, 21 | 22 | "properties": [ 23 | { "property": "SMOKY_RESISTANCE", "value": 10 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/forester.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "forester", 3 | "index": 34, 4 | 5 | "name": { 6 | "en": "Forester", 7 | "ru": "Лесник" 8 | }, 9 | "description": { 10 | "en": "This is one of two paints specifically designed for ground operations in a mid latitude climate. According to reports from the independent organization «True Camouflage», a tank wearing this particular combination of light and dark green, is less likely to be detected in such regions.", 11 | "ru": "Одна из двух расцветок, специально разработанных для наземных операций в средних климатических широтах. По результатам исследования независимой организации «Верный камуфляж», такая комбинация светло- и тёмно-зелёных пятен снижает вероятность обнаружения танка в кустах." 12 | }, 13 | 14 | "baseItemId": 769778, 15 | "previewResourceId": 769778, 16 | 17 | "rank": 4, 18 | "price": 1350, 19 | 20 | "coloring": 554337, 21 | 22 | "properties": [ 23 | { "property": "RAILGUN_RESISTANCE", "value": 12 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/green.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "green", 3 | "index": 23, 4 | 5 | "name": { 6 | "en": "Green", 7 | "ru": "Зелёный" 8 | }, 9 | "description": { 10 | "en": "A tribute to the tradition of the classic tank, this simple color, so beloved by designers all over the world, has become a standard paint for factory models. This legendary color has the true connoisseurs returning to it, having tried everything in the world.", 11 | "ru": "Являясь скорее данью традициям классического танкостроения, этот незамысловатый цвет, так полюбившийся конструкторам всего мира, стал своеобразным стандартом для заводских моделей. К этому легендарному цвету часто возвращаются истинные ценители, перепробовав всё на свете." 12 | }, 13 | 14 | "baseItemId": 388967, 15 | "previewResourceId": 388967, 16 | 17 | "rank": 1, 18 | "price": 0, 19 | 20 | "coloring": 966681, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/hohloma.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hohloma", 3 | "index": 57, 4 | 5 | "name": { 6 | "en": "Hohloma", 7 | "ru": "Хохлома" 8 | }, 9 | "description": { 10 | "en": "A creation whose beauty recalls the skill of Russian artisans, working in tiny workshops with patience and dedication.", 11 | "ru": "Изначально краска «Хохлома» создавалась исключительно для экспорта. Ею покрывались танки, продававшиеся за рубеж, дабы подчеркнуть исконно русское происхождение боевой техники. Теперь «Хохлому» можно достать и на внутреннем рынке, а в конструкторских бюро, по слухам, уже создаётся проект танка-матрёшки." 12 | }, 13 | 14 | "baseItemId": 327089, 15 | "previewResourceId": 327089, 16 | 17 | "rank": 18, 18 | "price": 76600, 19 | 20 | "coloring": 140028, 21 | 22 | "properties": [ 23 | { "property": "FREEZE_RESISTANCE", "value": 20 }, 24 | { "property": "RICOCHET_RESISTANCE", "value": 20 }, 25 | { "property": "SMOKY_RESISTANCE", "value": 26 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/holiday.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "holiday", 3 | "index": 25, 4 | 5 | "name": { 6 | "en": "Holiday", 7 | "ru": "Праздник" 8 | }, 9 | "description": { 10 | "en": "A unique and «smart» paint that can change its appearance depending on the occasion. On a regular day, it is simply a gray and white, checkered texture. But when a holiday kicks in, its appearance changes dramatically.", 11 | "ru": "Это уникальное «умное» покрытие умеет изменять свой вид в зависимости от конкретного повода. В обычный день это чёрно-белая клетка, но с наступлением праздника разительно преображается." 12 | }, 13 | 14 | "baseItemId": 971082, 15 | "previewResourceId": 971083, 16 | 17 | "rank": 1, 18 | "price": 0, 19 | 20 | "coloring": 423333, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/in_love.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "in_love", 3 | "index": 49, 4 | 5 | "name": { 6 | "en": "In love", 7 | "ru": "С любовью" 8 | }, 9 | "description": { 10 | "en": "Designed for the sophisticated and romantic ones, this paint will satisfy your desire for aesthetically pleasing military attributes. It will also earn you a few valuable seconds by fascinating your enemy with the extravagant looks of your tank, covered in red hearts.", 11 | "ru": "Разработанная для натур утончённых и романтичных, эта расцветка способна не только утолить жажду к прекрасному «искушённого» армейской атрибутикой эстета, но также поможет выиграть пару секунд у ошарашенного врага, зачарованно смотрящего на феерию из красных сердечек в центре поля боя." 12 | }, 13 | 14 | "baseItemId": 277983, 15 | "previewResourceId": 277983, 16 | 17 | "rank": 13, 18 | "price": 30500, 19 | 20 | "coloring": 493495, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 12 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 22 }, 25 | { "property": "RICOCHET_RESISTANCE", "value": 9 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/inferno.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "inferno", 3 | "index": 70, 4 | 5 | "name": { 6 | "en": "Inferno", 7 | "ru": "Инферно" 8 | }, 9 | "description": { 10 | "en": "The «Inferno» paint is delivered via shady means to the black market of China's glass-blowing workshops. According to the producers themselves, its defiant appearance symbolizes strength, courage, luck and longevity. An excellent paint for a fierce attacker.", 11 | "ru": "Покрытие «Инферно» контрабандой доставляется на рынок из подпольных стеклодувных мастерских Китая. По словам самих производителей, его дерзкий вид символизирует силу, отвагу, удачу и долголетие. Отличная краска для яростной атаки!" 12 | }, 13 | 14 | "baseItemId": 186119, 15 | "previewResourceId": 186119, 16 | 17 | "rank": 28, 18 | "price": 228000, 19 | 20 | "coloring": 200304, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 36 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 50 }, 25 | { "property": "TWINS_RESISTANCE", "value": 14 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/irbis.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "irbis", 3 | "index": 68, 4 | 5 | "name": { 6 | "en": "Irbis", 7 | "ru": "Ирбис" 8 | }, 9 | "description": { 10 | "en": "One of the most mysterious felines, the Irbis is also known as the «snow leopard». After a series of high-risk expeditions and fifteen accidents, our researchers managed to restrain a snow leopard long enough to copy its coat and create the «Irbis» paint.", 11 | "ru": "Одно из самых загадочных животных семейства кошачьих — ирбис, он же снежный барс. Исследователи многих стран безуспешно пытаются поймать живого ирбиса для детального изучения, но пока это удалось лишь нашим инженерам-биологам. После серии рискованных экспериментов и пятнадцати несчастных случаев они представили краску «Ирбис». Покрытие содержит ДНК снежного барса, что делает его абсолютно уникальным и в какой-то степени даже экологичным." 12 | }, 13 | 14 | "baseItemId": 494974, 15 | "previewResourceId": 494974, 16 | 17 | "rank": 27, 18 | "price": 216000, 19 | 20 | "coloring": 931954, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 50 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 27 }, 25 | { "property": "RICOCHET_RESISTANCE", "value": 23 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/jade.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "jade", 3 | "index": 73, 4 | 5 | "name": { 6 | "en": "Jade", 7 | "ru": "Нефрит" 8 | }, 9 | "description": { 10 | "en": "A spray from a rare brown jade. Beautiful, perfect for camouflage, and ecologically friendly.", 11 | "ru": "Напыление из редкого коричневого нефрита обеспечивает этому покрытию прочность хорошей стали. Но если производство металлсодержащих покрытий загрязняет океаны и приводит к вымиранию низколетающих птиц, то краска «Нефрит» с каменной пудрой экологически чиста, как берёзовый сок!" 12 | }, 13 | 14 | "baseItemId": 866855, 15 | "previewResourceId": 866855, 16 | 17 | "rank": 30, 18 | "price": 250000, 19 | 20 | "coloring": 746058, 21 | 22 | "properties": [ 23 | { "property": "THUNDER_RESISTANCE", "value": 12 }, 24 | { "property": "SMOKY_RESISTANCE", "value": 26 }, 25 | { "property": "SHAFT_RESISTANCE", "value": 26 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/jaguar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "jaguar", 3 | "index": 52, 4 | 5 | "name": { 6 | "en": "Jaguar", 7 | "ru": "Ягуар" 8 | }, 9 | "description": { 10 | "en": "This protection system codenamed «Jaguar» has spotty texture of the noble predator as well as protects your tank from all kinds of weapons.", 11 | "ru": "Вручную подогнанная под ваш танк шкура благородного хищника. Окрас истинного охотника." 12 | }, 13 | 14 | "baseItemId": 46357, 15 | "previewResourceId": 46357, 16 | 17 | "rank": 15, 18 | "price": 42500, 19 | 20 | "coloring": 952497, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 13 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 30 }, 25 | { "property": "THUNDER_RESISTANCE", "value": 9 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/lava.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "lava", 3 | "index": 37, 4 | 5 | "name": { 6 | "en": "Lava", 7 | "ru": "Лава" 8 | }, 9 | "description": { 10 | "en": "An experimental color, first tested during the local conflict in East Africa. According to military psychologists, this specific pattern resembling a divine fire will trigger a sense of panic within enemies.", 11 | "ru": "Экспериментальный цвет, впервые опробованный во время локальных конфликтов в восточной Африке. По мнению армейских психологов, такая расцветка способна повергнуть в шок представителей некоторых радикальных религиозных организаций, вызвав у них панический страх перед инферно." 12 | }, 13 | 14 | "baseItemId": 201158, 15 | "previewResourceId": 201158, 16 | 17 | "rank": 6, 18 | "price": 2100, 19 | 20 | "coloring": 498645, 21 | 22 | "properties": [ 23 | { "property": "FREEZE_RESISTANCE", "value": 18 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/lead.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "lead", 3 | "index": 39, 4 | 5 | "name": { 6 | "en": "Lead", 7 | "ru": "Свинец" 8 | }, 9 | "description": { 10 | "en": "Lead has long been known for its protection against radiation. This lead-infused paint takes advantage of these properties.", 11 | "ru": "Свинец — самая надёжная защита от излучения, это знали ещё в двадцатом веке. Покрытая свинцовым напылением броня эффективно защищает репродуктивную систему танкиста от радиации, позволяя сохранять бодрость духа даже в самых «горячих» переделках!" 12 | }, 13 | 14 | "baseItemId": 829177, 15 | "previewResourceId": 829177, 16 | 17 | "rank": 8, 18 | "price": 3000, 19 | 20 | "coloring": 560996, 21 | 22 | "properties": [ 23 | { "property": "SHAFT_RESISTANCE", "value": 22 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/loam.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "loam", 3 | "index": 54, 4 | 5 | "name": { 6 | "en": "Loam", 7 | "ru": "Глина" 8 | }, 9 | "description": { 10 | "en": "Nobody pays much attention to dirty vehicles. The «Loam» paint uses this psychological element to your tank's advantage by disguising it as a dirty piece of machinery. And once your enemy turns his back on you, that's when you strike!", 11 | "ru": "Лучший способ отвести вражеский взгляд от своего танка — заляпать его грязью. Однако естественная грязь весьма неустойчива — легко смывается водой и даже отваливается пластами в жаркую погоду. Чтобы придать танку нужный «грязный» цвет, было разработано покрытие «Глина». В его состав включены отходы керамического производства лучших китайских гончарных мастерских." 12 | }, 13 | 14 | "baseItemId": 600886, 15 | "previewResourceId": 600886, 16 | 17 | "rank": 16, 18 | "price": 50500, 19 | 20 | "coloring": 53082, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 26 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 10 }, 25 | { "property": "RICOCHET_RESISTANCE", "value": 10 }, 26 | { "property": "THUNDER_RESISTANCE", "value": 10 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/marine.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "marine", 3 | "index": 32, 4 | 5 | "name": "Морпех", 6 | "description": "Это покрытие прекрасно держится на танке даже после продолжительного контакта с солёной водой. Хитрые танкисты покрывают танк этой краской изнутри, после чего используют его как большую бочку для засаливания огурцов на зиму.", 7 | 8 | "baseItemId": 682329, 9 | "previewResourceId": 682329, 10 | 11 | "rank": 2, 12 | "price": 500, 13 | 14 | "coloring": 662824, 15 | 16 | "properties": [ 17 | { "property": "FIREBIRD_RESISTANCE", "value": 10 } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/mary.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "mary", 3 | "index": 40, 4 | 5 | "name": { 6 | "en": "Mary", 7 | "ru": "Мэри" 8 | }, 9 | "description": { 10 | "en": "Covered with bloody red stains, this paint is meant to inflict horror upon the enemy. However, highly experienced tankers will probably notice that what looks like blood is actually tomato juice. No wonder the paint is called «Mary».", 11 | "ru": "Покрытый кроваво-красными пятнами танк должен нагонять жути на противника, вселяя в него первобытный ужас. Хотя умудрённые опытом танкисты знают, что на корпусе вовсе не кровь, а брызги томатного сока. Недаром же краска называется «Мэри»." 12 | }, 13 | 14 | "baseItemId": 564917, 15 | "previewResourceId": 564917, 16 | 17 | "rank": 9, 18 | "price": 6500, 19 | 20 | "coloring": 523797, 21 | 22 | "properties": [ 23 | { "property": "FREEZE_RESISTANCE", "value": 15 }, 24 | { "property": "TWINS_RESISTANCE", "value": 8 }, 25 | { "property": "SMOKY_RESISTANCE", "value": 8 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/metallic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "metallic", 3 | "index": 36, 4 | 5 | "name": { 6 | "en": "Metallic", 7 | "ru": "Металлик" 8 | }, 9 | "description": { 10 | "en": "There is the misconception that this paint is intended as a replacement for varnishing surfaces. In reality, the reflective nature of this paint is intended to interfere with tracking and radar devices.", 11 | "ru": "Существует ошибочное мнение, что данной расцветкой пользуются в условиях дефицита лакокрасочных материалов. На самом деле — это специальное покрытие, особым образом отражающее свет. Бликовый шум, создаваемый таким покрытием, вносит помехи в работу некоторых систем наведения." 12 | }, 13 | 14 | "baseItemId": 246572, 15 | "previewResourceId": 246572, 16 | 17 | "rank": 6, 18 | "price": 2100, 19 | 20 | "coloring": 542023, 21 | 22 | "properties": [ 23 | { "property": "THUNDER_RESISTANCE", "value": 18 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/moonwalker.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "moonwalker", 3 | "index": 75, 4 | 5 | "name": { 6 | "en": "Moonwalker", 7 | "ru": "Луноход" 8 | }, 9 | "description": { 10 | "en": "A rare paint that could only be obtained for three days during the «Cosmonautics Day promotion» - from April 11 to April 13, 2022.\\n\\nThose who caught 6 moon boxes and received this paint, it remains in the garage forever.", 11 | "ru": "Редкая краска, которую можно было получить лишь в течении трёх дней во время акции «День космонавтики» – с 11 по 13 апреля 2022 года.\n\nТе, кто поймал 6 лунных ящиков и получил эту краску, она остаётся в гараже навсегда." 12 | }, 13 | 14 | "baseItemId": 342553, 15 | "previewResourceId": 342553, 16 | 17 | "rank": 1, 18 | "price": 0, 19 | 20 | "coloring": 567446, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/needle.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "needle", 3 | "index": 64, 4 | 5 | "name": { 6 | "en": "Needle", 7 | "ru": "Хвоя" 8 | }, 9 | "description": { 10 | "en": "Using spruce from sustainable-only areas, the «Needle» paint is prepared using 25 square meters of spruce, cut down and shredded into miniature wooden needles, and bound together using a special type of resin.", 11 | "ru": "Для производства одной банки краски «Хвоя», изготовленной на основе иголочек хвойных деревьев, вырубается 25 квадратных метров молодого ельника. Но каждый снайпер знает, что хвойная присыпка — лучший способ маскировки!" 12 | }, 13 | 14 | "baseItemId": 648950, 15 | "previewResourceId": 648950, 16 | 17 | "rank": 24, 18 | "price": 180000, 19 | 20 | "coloring": 216783, 21 | 22 | "properties": [ 23 | { "property": "RICOCHET_RESISTANCE", "value": 22 }, 24 | { "property": "SMOKY_RESISTANCE", "value": 50 }, 25 | { "property": "SHAFT_RESISTANCE", "value": 28 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/orange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "orange", 3 | "index": 30, 4 | 5 | "name": { 6 | "en": "Orange", 7 | "ru": "Оранжевый" 8 | }, 9 | "description": { 10 | "en": "Originally, there were no plans to include this color in the official list, as it lacked any practical value, except maybe a decent camouflage in desert areas. However, army psychologists have proved that this particular hue of orange has a positive, therapeutic effect on shell-shocked tanks.", 11 | "ru": "Данную расцветку не планировалось включать в официальный перечень, так как она не представляла особой практической ценности, кроме, быть может, посредственной маскировки в пустынной местности. Но армейские мозгоправы доказали, что оранжевый цвет положительно влияет на психологическое здоровье «пушечного мяса»." 12 | }, 13 | 14 | "baseItemId": 456516, 15 | "previewResourceId": 456516, 16 | 17 | "rank": 1, 18 | "price": 100, 19 | 20 | "coloring": 104412, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/picasso.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "picasso", 3 | "index": 74, 4 | 5 | "name": { 6 | "en": "Picasso", 7 | "ru": "Пикассо" 8 | }, 9 | "description": { 10 | "en": "Picasso was named after Pablo Picasso, the famous cubist artist. Thin polygonal fragments of the armor form a truly unique abstract pattern, which diffuses even the most powerful hits. This paint is the choice of true art critics!", 11 | "ru": "Пикассо был назван в честь Пабло Пикассо, известного художника-кубиста. Тонкие многоугольные фрагменты доспехов образуют поистине уникальный абстрактный узор, отражающий даже самые сильные удары. Эта краска - выбор настоящих искусствоведов!" 12 | }, 13 | 14 | "baseItemId": 337676, 15 | "previewResourceId": 337676, 16 | 17 | "rank": 30, 18 | "price": 250000, 19 | 20 | "coloring": 906593, 21 | 22 | "properties": [ 23 | { "property": "FREEZE_RESISTANCE", "value": 20 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 20 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 27 }, 26 | { "property": "SHAFT_RESISTANCE", "value": 33 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/premium.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "premium", 3 | "index": 24, 4 | 5 | "name": { 6 | "en": "Premium paint", 7 | "ru": "Премиум краска" 8 | }, 9 | "description": { 10 | "en": "A regular, non-animated paint that serves as an aesthetic coating for your tank. Its color scheme might potentially offer some camouflage benefits, but besides that, the paint does not offer any functional gameplay advantages.", 11 | "ru": "Специальная краска с уникальным дизайном — превосходный выбор для тех, кто любой ценой стремится достичь максимума в игре." 12 | }, 13 | 14 | "baseItemId": 910936, 15 | "previewResourceId": 910936, 16 | 17 | "rank": 1, 18 | "price": 0, 19 | 20 | "coloring": 703442, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/prodigi.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "prodigi", 3 | "index": 67, 4 | 5 | "name": { 6 | "en": "Prodigi", 7 | "ru": "Продиджи" 8 | }, 9 | "description": { 10 | "en": "An enhanced digital paint with a pattern that is intended to distract and disorient your enemy, making them more likely to miss when firing at you.", 11 | "ru": "Усовершенствованная версия стандартного «Диджитала» выполнена по секретной технологии «Рябь в глазах». Она не только надёжно маскирует танк, но и раздражает противника, заставляя его чаще ошибаться." 12 | }, 13 | 14 | "baseItemId": 645131, 15 | "previewResourceId": 645131, 16 | 17 | "rank": 26, 18 | "price": 204000, 19 | 20 | "coloring": 78411, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 7 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 23 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 50 }, 26 | { "property": "SHAFT_RESISTANCE", "value": 20 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/python.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "python", 3 | "index": 60, 4 | 5 | "name": { 6 | "en": "Python", 7 | "ru": "Питон" 8 | }, 9 | "description": { 10 | "en": "This paint is an exact replica of a giant python's skin. It also replicates the snake's properties of extreme elasticity, as well as its scaly texture.", 11 | "ru": "Покрытие «Питон» — это не стилизация под змеиную шкуру, а цельная кожа настоящего питона. Покрытие ею танка производится по хитроумной технологии: камбоджийского гигантского питона морят голодом, а затем подсовывают ему обмазанный свиным жиром танк. Змея глотает его целиком, всё лишнее отрезается." 12 | }, 13 | 14 | "baseItemId": 167323, 15 | "previewResourceId": 167323, 16 | 17 | "rank": 20, 18 | "price": 103000, 19 | 20 | "coloring": 704601, 21 | 22 | "properties": [ 23 | { "property": "FREEZE_RESISTANCE", "value": 42 }, 24 | { "property": "SHAFT_RESISTANCE", "value": 33 } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/red.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "red", 3 | "index": 26, 4 | 5 | "name": { 6 | "en": "Red", 7 | "ru": "Красный" 8 | }, 9 | "description": { 10 | "en": "The red color - the embodiment of rage and courage. It also has the practical application of being able to hide any traces of enemy blood.", 11 | "ru": "Красный цвет — воплощение ярости и отваги, обладает также природной практичностью, позволяя деликатно скрывать пятна вражеской крови, неизбежно появляющиеся на корпусе, как бы вы ни аккуратничали." 12 | }, 13 | 14 | "baseItemId": 471061, 15 | "previewResourceId": 471061, 16 | 17 | "rank": 1, 18 | "price": 100, 19 | 20 | "coloring": 529101, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/rock.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "rock", 3 | "index": 66, 4 | 5 | "name": { 6 | "en": "Rock", 7 | "ru": "Скала" 8 | }, 9 | "description": { 10 | "en": "The armored plates called «The Rock» are mined from huge rock formations, using an ancient Egyptian technology. Dry pegs are hammered into the rock, then soaked with water till they swell, breaking the rock into smooth blocks of stone. This camouflage is the definition of «hand-made».", 11 | "ru": "Броневые пластины для покрытия «Скала» добываются в скальных массивах по древнеегипетской технологии. Сухие колышки вбиваются в скалу, смачиваются водой, разбухают и откалывают ровные каменные блоки. Этот камуфляж — настоящая ручная работа!" 12 | }, 13 | 14 | "baseItemId": 669820, 15 | "previewResourceId": 669820, 16 | 17 | "rank": 25, 18 | "price": 192000, 19 | 20 | "coloring": 473605, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 28 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 12 }, 25 | { "property": "TWINS_RESISTANCE", "value": 50 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/roger.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "roger", 3 | "index": 43, 4 | 5 | "name": { 6 | "en": "Roger", 7 | "ru": "Роджер" 8 | }, 9 | "description": { 10 | "en": "The fearsome skull and bones motif popularized by the blood-thirsty pirates of the seven seas.", 11 | "ru": "Главный пиратский символ всех времён, теперь впервые и на твоём танке." 12 | }, 13 | 14 | "baseItemId": 968399, 15 | "previewResourceId": 968399, 16 | 17 | "rank": 10, 18 | "price": 12500, 19 | 20 | "coloring": 929509, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 7 }, 24 | { "property": "TWINS_RESISTANCE", "value": 15 } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/rustle.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "rustle", 3 | "index": 59, 4 | 5 | "name": { 6 | "en": "Rustle", 7 | "ru": "Шелест" 8 | }, 9 | "description": { 10 | "en": "Autumn leaves may rustle beneath your tank's tracks, but your opponents still won't be able to detect you thanks to this paint's unique blend of hues.", 11 | "ru": "Танки не боятся ни жары, ни мороза, поэтому сражения бушуют круглый год. А значит, танкистам требуется маскировка не только для разной местности, но и для всех сезонов. Этот окрас поможет слиться с палой листвой и подосиновиками в осеннем лесу. При условии, что вам удастся заехать на танке в лес..." 12 | }, 13 | 14 | "baseItemId": 49564, 15 | "previewResourceId": 49564, 16 | 17 | "rank": 20, 18 | "price": 103000, 19 | 20 | "coloring": 60284, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 12 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 26 }, 25 | { "property": "THUNDER_RESISTANCE", "value": 37 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/safari.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "safari", 3 | "index": 35, 4 | 5 | "name": { 6 | "en": "Safari", 7 | "ru": "Сафари" 8 | }, 9 | "description": { 10 | "en": "Designed by those who love the thrill of the safari, this paint masks the tank as an innocuous zebra, which makes it perfect for hunting African predators, as well as naive tankers.", 11 | "ru": "Любители пощекотать себе нервы на сафари придумали весьма эффективный способ привлекать к себе хищников. Маскировка танка под невинную зебру отлично подходит для охоты с противотанковым вооружением как на африканских хищников, так и на не очень умных танкистов." 12 | }, 13 | 14 | "baseItemId": 981027, 15 | "previewResourceId": 981027, 16 | 17 | "rank": 5, 18 | "price": 1750, 19 | 20 | "coloring": 258487, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 15 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/sandstone.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "sandstone", 3 | "index": 61, 4 | 5 | "name": { 6 | "en": "Sandstone", 7 | "ru": "Песчаник" 8 | }, 9 | "description": { 10 | "en": "The paint combines the masking properties of sand, with wind-tempered stone. The «Sandstone» set comes bundled with bathing trunks or bikini of choice, sunglasses and a parasol.", 11 | "ru": "Покрытие «Песчаник» сочетает в себе маскирующие свойства песочной раскраски и прочность закалённого ветрами камня. Подарочный комплект «Песчаника» включает в себя купальные плавки или бикини на выбор, солнечные очки и пляжный зонт." 12 | }, 13 | 14 | "baseItemId": 379571, 15 | "previewResourceId": 379571, 16 | 17 | "rank": 21, 18 | "price": 116000, 19 | 20 | "coloring": 519187, 21 | 22 | "properties": [ 23 | { "property": "TWINS_RESISTANCE", "value": 10 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 20 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 30 }, 26 | { "property": "SHAFT_RESISTANCE", "value": 20 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/savanna.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "savanna", 3 | "index": 53, 4 | 5 | "name": { 6 | "en": "Savanna", 7 | "ru": "Саванна" 8 | }, 9 | "description": { 10 | "en": "Perfect for hiding among thick foliage such as jungle environments. Careful though... gorillas are known to use leaves for «cleaning» purposes.", 11 | "ru": "Этот камуфляж скроет вас среди пальм. Пляж тоже подойдёт, но, пожалуйста, не оставляйте гильзы от танковых снарядов на песке, это доставляет неудобства отдыхающим." 12 | }, 13 | 14 | "baseItemId": 388701, 15 | "previewResourceId": 388701, 16 | 17 | "rank": 16, 18 | "price": 50500, 19 | 20 | "coloring": 299478, 21 | 22 | "properties": [ 23 | { "property": "RICOCHET_RESISTANCE", "value": 10 }, 24 | { "property": "SMOKY_RESISTANCE", "value": 30 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 15 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/spark.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "spark", 3 | "index": 62, 4 | 5 | "name": { 6 | "en": "Spark", 7 | "ru": "Искра" 8 | }, 9 | "description": { 10 | "en": "Fairy lights, stage lights, music of the spheres - this paint can be described using many words, but none of them can ever fully describe it. The «Spark» paint is chosen by those tankers for whom the tank is not only a battle machine, but also a means of expression.", 11 | "ru": "Феерия света, каскад огней, музыка сфер — это уникальное покрытие можно описывать разными словами, но ни одно из них не отразит полностью его глубину. «Искру» выбирают танкисты, для которых танк — не просто боевая машина, но средство самовыражения." 12 | }, 13 | 14 | "baseItemId": 184125, 15 | "previewResourceId": 184125, 16 | 17 | "rank": 21, 18 | "price": 116000, 19 | 20 | "coloring": 378667, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 25 }, 24 | { "property": "TWINS_RESISTANCE", "value": 15 }, 25 | { "property": "RICOCHET_RESISTANCE", "value": 40 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/storm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "storm", 3 | "index": 41, 4 | 5 | "name": { 6 | "en": "Storm", 7 | "ru": "Буря" 8 | }, 9 | "description": { 10 | "en": "A perfect disguise for covert night time operations, this paint makes the tank virtually invisible at dusk.", 11 | "ru": "Отличная маскировка для секретных ночных операций. В условиях слабой освещённости эта текстура делает танк совершенно неразличимым в сумраке." 12 | }, 13 | 14 | "baseItemId": 364130, 15 | "previewResourceId": 364130, 16 | 17 | "rank": 9, 18 | "price": 6500, 19 | 20 | "coloring": 265602, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 18 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 7 } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/swamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "swamp", 3 | "index": 33, 4 | 5 | "name": { 6 | "en": "Swamp", 7 | "ru": "Болотный" 8 | }, 9 | "description": { 10 | "en": "This paint was created after a tank battalion leader accidentally fell asleep, and drove his comrades into a swamp, where a secret operation for training saboteur frogs was being conducted.", 11 | "ru": "Случайно заснувший ведущий танкового батальона завёл своих товарищей в болото, где проводилась тайная операция по подготовке жаб-диверсантов, и около десяти тысяч экземпляров превратились в прототип этой окраски." 12 | }, 13 | 14 | "baseItemId": 300293, 15 | "previewResourceId": 300293, 16 | 17 | "rank": 3, 18 | "price": 900, 19 | 20 | "coloring": 172172, 21 | 22 | "properties": [ 23 | { "property": "TWINS_RESISTANCE", "value": 10 } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/swash.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "swash", 3 | "index": 47, 4 | 5 | "name": { 6 | "en": "Swash", 7 | "ru": "Прибой" 8 | }, 9 | "description": { 10 | "en": "The formula of the «Swash» cover was stolen from a secret laboratory codenamed , specializing in genetic experiments on fish. Thanks to its special heat-reflecting properties, this paint gives the air inside the tank a refreshing seaside freshness.", 11 | "ru": "Формула покрытия «Прибой» была похищена из секретной лаборатории под кодовым названием «Аква», специализирующейся на генетических экспериментах над рыбами. Благодаря особым теплоотражающим свойствам краска придаёт воздуху внутри танка морскую свежесть" 12 | }, 13 | 14 | "baseItemId": 344971, 15 | "previewResourceId": 344971, 16 | 17 | "rank": 12, 18 | "price": 24500, 19 | 20 | "coloring": 48688, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 9 }, 24 | { "property": "FIREBIRD_RESISTANCE", "value": 15 }, 25 | { "property": "TWINS_RESISTANCE", "value": 10 }, 26 | { "property": "SMOKY_RESISTANCE", "value": 5 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/taiga.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "taiga", 3 | "index": 72, 4 | 5 | "name": { 6 | "en": "Taiga", 7 | "ru": "Тайга" 8 | }, 9 | "description": { 10 | "en": "The «Taiga» camouflage paint masks the tank so well in thick vegetation, that you could probably sneak up on a moose with your tank.", 11 | "ru": "Камуфляжное покрытие «Тайга» так хорошо маскирует танк в густой растительности, что на нём даже можно охотиться на лосей. Пугливое животное не замечает угрозу и подходит к боевой машине вплотную. Впрочем, на месте лося может оказаться и другой танкист..." 12 | }, 13 | 14 | "baseItemId": 184733, 15 | "previewResourceId": 184733, 16 | 17 | "rank": 29, 18 | "price": 240000, 19 | 20 | "coloring": 32379, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 35 }, 24 | { "property": "FIREBIRD_RESISTANCE", "value": 24 }, 25 | { "property": "TWINS_RESISTANCE", "value": 17 }, 26 | { "property": "RICOCHET_RESISTANCE", "value": 24 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/tundra.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tundra", 3 | "index": 45, 4 | 5 | "name": { 6 | "en": "Tundra", 7 | "ru": "Тундра" 8 | }, 9 | "description": { 10 | "en": "Gives your tank an intimidating, battle-hardened look. Nobody will dare mess with you.", 11 | "ru": "Одна из немногих по-настоящему стильных камуфляжных красок. Текстура сурова и по-мужски проста. Делает танк менее заметным на поле боя." 12 | }, 13 | 14 | "baseItemId": 985440, 15 | "previewResourceId": 985440, 16 | 17 | "rank": 11, 18 | "price": 18500, 19 | 20 | "coloring": 689436, 21 | 22 | "properties": [ 23 | { "property": "SMOKY_RESISTANCE", "value": 10 }, 24 | { "property": "RAILGUN_RESISTANCE", "value": 25 } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/urban.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "urban", 3 | "index": 55, 4 | 5 | "name": { 6 | "en": "Urban", 7 | "ru": "Урбан" 8 | }, 9 | "description": { 10 | "en": "The best paint for battle in urban environments. Works especially well with roadside bushes.", 11 | "ru": "Лучшая краска для сражений в городе. Особенно хорошо работает в придорожных кустах, за что любима инспекцией танкового движения." 12 | }, 13 | 14 | "baseItemId": 739321, 15 | "previewResourceId": 739321, 16 | 17 | "rank": 17, 18 | "price": 63500, 19 | 20 | "coloring": 868766, 21 | 22 | "properties": [ 23 | { "property": "ISIS_RESISTANCE", "value": 15 }, 24 | { "property": "FIREBIRD_RESISTANCE", "value": 5 }, 25 | { "property": "TWINS_RESISTANCE", "value": 36 }, 26 | { "property": "RICOCHET_RESISTANCE", "value": 5 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/white.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "white", 3 | "index": 29, 4 | 5 | "name": { 6 | "en": "White", 7 | "ru": "Белый" 8 | }, 9 | "description": { 10 | "en": "This paint has been developed specifically for ground operations in countries with cold climates. In snowy areas, it can significantly reduce the likelihood of detection. In order to increase the sales, some manufacturers included a warm coat and boots in the package.", 11 | "ru": "Специальная разработка для проведения наземных операций в странах с холодным климатом. В заснеженной местности такая расцветка способна значительно снизить вероятность обнаружения. Ради увеличения объёмов продаж производитель включил в комплект поставки теплый тулуп и валенки." 12 | }, 13 | 14 | "baseItemId": 912977, 15 | "previewResourceId": 912977, 16 | 17 | "rank": 1, 18 | "price": 100, 19 | 20 | "coloring": 996274, 21 | 22 | "properties": [ ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/winter.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "winter", 3 | "index": 63, 4 | 5 | "name": { 6 | "en": "Winter", 7 | "ru": "Метель" 8 | }, 9 | "description": { 10 | "en": "This paint was specially developed for battle on snow-covered land. It was originally intended to include a snow shovel, but the finance department didn't approve the extra cost.", 11 | "ru": "Эта краска разработана специально для боёв на заснеженных картах и в условиях повышенной видимости. Лопата для самооткапывания танка из снега в комплект не входит по экономическим причинам." 12 | }, 13 | 14 | "baseItemId": 791701, 15 | "previewResourceId": 791701, 16 | 17 | "rank": 22, 18 | "price": 129000, 19 | 20 | "coloring": 285466, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 11 }, 24 | { "property": "THUNDER_RESISTANCE", "value": 23 }, 25 | { "property": "RAILGUN_RESISTANCE", "value": 11 }, 26 | { "property": "SHAFT_RESISTANCE", "value": 40 } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/paints/zeus.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "zeus", 3 | "index": 65, 4 | 5 | "name": { 6 | "en": "Zeus", 7 | "ru": "Зевс" 8 | }, 9 | "description": { 10 | "en": "Fashioned after the Greek god known for his love of thunderbolts, this paint will signal to your enemies that you're a force not to be taken lightly.", 11 | "ru": "Краска «Зевс» названа так за свою необычную текстуру. Яркие вспышки молний на вашем танке ослабят бдительность вражеских танков и позволят вам выглядеть круто в любой битве. " 12 | }, 13 | 14 | "baseItemId": 123565, 15 | "previewResourceId": 123565, 16 | 17 | "rank": 24, 18 | "price": 180000, 19 | 20 | "coloring": 412123, 21 | 22 | "properties": [ 23 | { "property": "FIREBIRD_RESISTANCE", "value": 50 }, 24 | { "property": "FREEZE_RESISTANCE", "value": 25 }, 25 | { "property": "SMOKY_RESISTANCE", "value": 25 } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/badge.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "badge", 3 | "index": 192, 4 | 5 | "name": { 6 | "en": "Badge", 7 | "ru": "Значок" 8 | }, 9 | "description": { 10 | "en": "A branded badge is the best way to tell a friend that he's a real tanker.", 11 | "ru": "Фирменный значок — самый простой способ сообщить другу, что он — настоящий танкист." 12 | }, 13 | 14 | "baseItemId": 186997, 15 | "previewResourceId": 186997, 16 | 17 | "rank": 7, 18 | "price": 200, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/blue_sphere.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blue_sphere", 3 | "index": 201, 4 | 5 | "name": { 6 | "en": "Blue Sphere", 7 | "ru": "Синий шар" 8 | }, 9 | "description": { 10 | "en": "The legendary blue ball. Yes, that's him.", 11 | "ru": "Легендарный синий шар. Да, это он." 12 | }, 13 | 14 | "baseItemId": 524809, 15 | "previewResourceId": 524809, 16 | 17 | "rank": 7, 18 | "price": 100500, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/brofist.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "brofist", 3 | "index": 195, 4 | 5 | "name": { 6 | "en": "Bro!", 7 | "ru": "Бро!" 8 | }, 9 | "description": { 10 | "en": "A Tanki statuette granting +1 to coolness.", 11 | "ru": "Танковая статуэтка, дающая обладателю +1 к крутости!" 12 | }, 13 | 14 | "baseItemId": 45309, 15 | "previewResourceId": 45309, 16 | 17 | "rank": 7, 18 | "price": 2000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/claws.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "claws", 3 | "index": 199, 4 | 5 | "name": { 6 | "en": "Claws", 7 | "ru": "Клешни рака" 8 | }, 9 | "description": { 10 | "en": "When someone's hands are simply not meant to be used for playing video games...", 11 | "ru": "Клац-клац!" 12 | }, 13 | 14 | "baseItemId": 749669, 15 | "previewResourceId": 749669, 16 | 17 | "rank": 7, 18 | "price": 2000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/dislike.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "dislike", 3 | "index": 194, 4 | 5 | "name": { 6 | "en": "Dislike", 7 | "ru": "Дизлайк" 8 | }, 9 | "description": { 10 | "en": "A thumbs down is the perfect answer to anything you don't like.", 11 | "ru": "Дизлайк — универсальный ответ на всё, что тебе не нравится." 12 | }, 13 | 14 | "baseItemId": 789404, 15 | "previewResourceId": 789404, 16 | 17 | "rank": 7, 18 | "price": 1000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/helmet.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "helmet", 3 | "index": 196, 4 | 5 | "name": { 6 | "en": "Helmet", 7 | "ru": "Танковый шлем" 8 | }, 9 | "description": { 10 | "en": "A tank helmet is an essential piece of equipment for any seasoned player.", 11 | "ru": "Танковый шлем — неотъемлемый элемент экипировки опытного бойца." 12 | }, 13 | 14 | "baseItemId": 122218, 15 | "previewResourceId": 122218, 16 | 17 | "rank": 7, 18 | "price": 2000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/like.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "like", 3 | "index": 193, 4 | 5 | "name": { 6 | "en": "Like", 7 | "ru": "Лайк" 8 | }, 9 | "description": { 10 | "en": "Send a thumbs up to someone who has impressed you in a battle.", 11 | "ru": "Отправь лайк тому, кто впечатлил тебя в битве." 12 | }, 13 | 14 | "baseItemId": 524516, 15 | "previewResourceId": 524516, 16 | 17 | "rank": 7, 18 | "price": 1000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/tangled_turret.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tangled_turret", 3 | "index": 198, 4 | 5 | "name": { 6 | "en": "Tangled Turret", 7 | "ru": "Гнутый ствол" 8 | }, 9 | "description": { 10 | "en": "Sometimes they just come into your team...", 11 | "ru": "Иногда они просто заходят в твою команду..." 12 | }, 13 | 14 | "baseItemId": 784942, 15 | "previewResourceId": 784942, 16 | 17 | "rank": 7, 18 | "price": 2000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/teddybear.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "teddybear", 3 | "index": 197, 4 | 5 | "name": { 6 | "en": "Teddy Bear", 7 | "ru": "Плюшевый мишка" 8 | }, 9 | "description": { 10 | "en": "The best way to confess your feelings to someone you care about.", 11 | "ru": "Добавь уюта в гараж тому, кто тебе небезразличен." 12 | }, 13 | 14 | "baseItemId": 868053, 15 | "previewResourceId": 868053, 16 | 17 | "rank": 7, 18 | "price": 2000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/presents/wrench.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "wrench", 3 | "index": 200, 4 | 5 | "name": { 6 | "en": "Wrench", 7 | "ru": "Гаечный ключ" 8 | }, 9 | "description": { 10 | "en": "A golden wrench is the perfect gift, even for tankers with the most sophisticated taste.", 11 | "ru": "Золотой гаечный ключ, который не стыдно подарить даже самому искушённому танкисту." 12 | }, 13 | 14 | "baseItemId": 978053, 15 | "previewResourceId": 978053, 16 | 17 | "rank": 7, 18 | "price": 10000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/subscriptions/premium_effect.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "premium_effect", 3 | "index": 87, 4 | 5 | "name": { 6 | "en": "Premium Account", 7 | "ru": "Премиум аккаунт" 8 | }, 9 | "description": { 10 | "en": "Ability to create PRO battles\nOpportunity to participate in PRO-battles\nBattle Reward Bonus: 100%\nBattle Experience Bonus: 50%", 11 | "ru": "Возможность создавать PRO-битвы\nВозможность участвовать в PRO-битвах\nБонус к награде за битву: 100%\nБонус к опыту в битве: 50%" 12 | }, 13 | 14 | "baseItemId": 580104, 15 | "previewResourceId": 580104, 16 | 17 | "rank": 1, 18 | "price": -1, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/subscriptions/pro_battle.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "pro_battle", 3 | "index": 89, 4 | 5 | "name": { 6 | "en": "PRO Battle", 7 | "ru": "PRO Битва" 8 | }, 9 | "description": { 10 | "en": "The duration is 1 month. Allows you to create your own battles and choose their settings, as well as participate in «Professional Battles» an unlimited number of times within 31 days from the date of purchase.\n\nPage of the subscription in N.", 11 | "ru": "Время действия — 1 месяц. Даёт возможность создавать собственные бои и выбирать их настройки, а также участвовать в «Профессиональных битвах» неограниченное количество раз в течение 31 дня с момента покупки.\n\nСтраница абонемента в N." 12 | }, 13 | "baseItemId": 906431, 14 | "previewResourceId": 906431, 15 | 16 | "rank": 1, 17 | "price": 1400, 18 | 19 | "properties": [ ] 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/subscriptions/up_score.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "up_score", 3 | "index": 85, 4 | 5 | "name": { 6 | "en": "Up score", 7 | "ru": "Повышенный опыт" 8 | }, 9 | "description": { 10 | "en": "Increases XP that you receive by 30%.\n\nLasts 1 month.\n\nAttention, the subscription does not give an advantage in team battles!", 11 | "ru": "Увеличивает получаемые очки на 30%.\n\nВремя действия — 1 месяц.\n\nВнимание, абонемент не даёт преимущества в командных битвах!" 12 | }, 13 | 14 | "baseItemId": 272402, 15 | "previewResourceId": 272402, 16 | 17 | "rank": 1, 18 | "price": 12500, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/subscriptions/up_score_start.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "up_score_start", 3 | "index": 88, 4 | 5 | "name": { 6 | "en": "Up score", 7 | "ru": "Повышенный опыт" 8 | }, 9 | "description": { 10 | "en": "Increases XP that you receive by 30%.\n\nLasts 1 week.\n\nAttention, the subscription does not give an advantage in team battles!", 11 | "ru": "Увеличивает получаемые очки на 30%.\n\nВремя действия — 1 неделя.\n\nВнимание, абонемент не даёт преимущества в командных битвах!" 12 | }, 13 | 14 | "baseItemId": 102099, 15 | "previewResourceId": 102099, 16 | 17 | "rank": 1, 18 | "price": 0, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/supplies/1000_scores.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1000_scores", 3 | "index": 0, 4 | 5 | "name": { 6 | "en": "1000 experience", 7 | "ru": "1000 опыта" 8 | }, 9 | "description": { 10 | "en": "Buying 1000 experience points, you get closer to their next rank. Having bought several thousands of experience points, you can make substantial progress on the career ladder, and thus get access to new guns, hulls and paints.\n\nAttention! \"1000 experience\" is activated automatically after it's purchased.\n\nRank table", 11 | "ru": "Приобретая 1000 очков опыта, вы приближаетесь к своему следующему званию. Купив сразу несколько тысяч очков опыта, вы можете значительно продвинуться по карьерной лестнице и получить тем самым доступ к новым пушкам , корпусам и краскам.\n\nВнимание! \"1000 опыта\" используется автоматически сразу после покупки.\n\nТаблица званий" 12 | }, 13 | 14 | "baseItemId": 613847, 15 | "previewResourceId": 613847, 16 | 17 | "rank": 11, 18 | "price": 2000, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/supplies/armor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "armor", 3 | "index": 2, 4 | 5 | "name": { 6 | "en": "Double Armor", 7 | "ru": "Двойная защита" 8 | }, 9 | "description": { 10 | "en": "Doubles protection to one minute.\n\nA set of advanced kevlar plates, mounted in standart slots. Being set in a right way they can take up 50% of kinetic and thermal energy of the impact, provided double power armor.\n\nThe devices function for the period of one minute.", 11 | "ru": "Удваивает защиту на минуту.\n\nНабор усовершенствованных керамо-кавлоровых бронепластин, монтируемых в стандартные слоты 600x800 мм. При весе всего в 10 кг, такая пластина способна вдвое увеличить защитные свойства штатной брони, при правильной установке принимая на себя 50% кинетической и термической энергии взрыва. В комплект также входят ножницы с победитовыми лезвиями, три катушки двухстороннего скотча и белоснежная кружевная тряпочка из некатинового материала, без которой, судя по инструкции, никак не обойтись." 12 | }, 13 | 14 | "baseItemId": 824172, 15 | "previewResourceId": 824172, 16 | 17 | "rank": 4, 18 | "price": 50, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/supplies/mine.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "mine", 3 | "index": 5, 4 | 5 | "name": { 6 | "en": "Mine", 7 | "ru": "Мина" 8 | }, 9 | "description": { 10 | "en": "Undermines enemies.\n\nThe mine it deactivated if its owner was killed, left the battle was finished. In team battles mines don't react to allies.\n\nThe mine is the friend, comrade and brother of an experienced tanker. To gain the rear of the enemy and mine his staff, protect the comrades while retreat, block approaches to the base - all this is prossible due to the inconspicuous deadly device.", 11 | "ru": "Подрывает противников.\n\nМина деактивируется, если владелец погиб, вышел из битвы или закончилась битва. В командной битве мины не реагируют на союзников.\n\nМина — друг, товарищ и брат опытного танкиста. Незаметно прокрасться в тыл врага и заминировать его штаб, прикрыть товарищей при отступлении, заблокировать подходы к базе — всё это стало возможным благодаря применению незаметного смертоносного устройства. Танк, подорвавшийся на мине, теряет от 120 до 240 единиц здоровья (hp). Время активации мины — 2 секунды, повторное использование мины возможно только через 30 секунд." 12 | }, 13 | 14 | "baseItemId": 504645, 15 | "previewResourceId": 504645, 16 | 17 | "rank": 5, 18 | "price": 50, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/garage/items/supplies/n2o.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "n2o", 3 | "index": 4, 4 | 5 | "name": { 6 | "en": "Nitro", 7 | "ru": "Ускорение" 8 | }, 9 | "description": { 10 | "en": "Increases speed for one minute.\n\nNitrogen oxide should be in every tank. First, it is wonderful fuel for acceleration. Second, nitrogen oxide also known as laughing gas can be use for narcosis and even just for fun. If you meet silly giggling tankman you should know - he has snuffed N20.", 11 | "ru": "Повышает скорость на минуту.\n\nБаллон с закисью азота должен быть в каждом танке. Во-первых, это отличное горючее для временного ускорения боевой машины. Во-вторых, оксид азота, также известный как веселящий газ, можно использовать для наркоза и просто для поднятия настроения. Если вы повстречаете глупо хихикающего танкиста, знайте, он просто нюхнул N2O." 12 | }, 13 | 14 | "baseItemId": 153186, 15 | "previewResourceId": 153186, 16 | 17 | "rank": 4, 18 | "price": 50, 19 | 20 | "properties": [ ] 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/data/maps/trains/summer-day.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "map_trains", 3 | "theme": "summer_day", 4 | 5 | "id": 577904, 6 | "preview": 323193, 7 | 8 | "visual": { 9 | "angleX": -1, 10 | "angleZ": -0.5, 11 | 12 | "lightColor": 13090219, 13 | "shadowColor": 5530735, 14 | 15 | "fogAlpha": 0.25, 16 | "fogColor": 14545407, 17 | 18 | "farLimit": 10000, 19 | "nearLimit": 5000, 20 | 21 | "gravity": 1000, 22 | "skyboxRevolutionSpeed": 0, 23 | "ssaoColor": 3025184, 24 | 25 | "dustAlpha": 0.75, 26 | "dustDensity": 0.15000000596046448, 27 | "dustFarDistance": 7000, 28 | "dustNearDistance": 5000, 29 | "dustParticle": "summer", 30 | "dustSize": 200 31 | }, 32 | "skybox": "Hills", 33 | "resources": { 34 | "proplibs": [ 35 | "Bush", 36 | "City Builds", 37 | "Cliffs", 38 | "Concrete Tiles", 39 | "Concrete Walls", 40 | "Grass Tiles", 41 | "NuBu 3", 42 | "NuBu 5", 43 | "NuBu 9", 44 | "Promotion", 45 | "Slopes Rises", 46 | "Small Bridge", 47 | "Village Builds" 48 | ], 49 | "map": { "id": 577904, "version": 1 } 50 | }, 51 | "spawnPoints": [ 52 | { 53 | "position": { "x": 0, "y": 0, "z": 0 }, 54 | "rotation": { "x": 0, "y": 0, "z": 0 } 55 | } 56 | ], 57 | "bonuses": [ 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /src/main/resources/data/resources/auth-untrusted.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": [ 3 | { "idhigh": "0", "idlow": 122842, "versionhigh": "0", "versionlow": 1, "lazy": false, "alpha": false, "type": 10 } 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/crystals/category.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "en": "Crystals", 4 | "ru": "Кристаллы" 5 | }, 6 | "description": { 7 | "en": "This is where you can buy crystals to buy equipment in your garage.", 8 | "ru": "Здесь вы можете купить кристаллы, чтобы использовать их для приобретения нужных вам вещей в гараже." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/crystals/crystals_pack_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": { 3 | "jpy": 2019 4 | }, 5 | "crystals": { 6 | "base": 2112 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/crystals/crystals_pack_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": { 3 | "jpy": 10000 4 | }, 5 | "crystals": { 6 | "base": 30000, 7 | "bonus": 2000 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/crystals/crystals_pack_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": { 3 | "jpy": 20000 4 | }, 5 | "crystals": { 6 | "base": 80000, 7 | "bonus": 5000 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/premium/category.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": { 3 | "en": "Premium Account", 4 | "ru": "Премиум аккаунт" 5 | }, 6 | "description": { 7 | "en": "Purchase a Premium Account to unlock a whole range of advantages. With Premium you will receive 100% more crystals and 50% experience after every battle. You will also receive a unique Premium paint! Note: The Unique paint does not provide additional protection to the tank.", 8 | "ru": "Приобретая премиум аккаунт, вы получаете целый ряд приемуществ - на 100% больше кристаллов и на 50% больше опыта за каждый бой, уникальную премиум краску, возможность создавать PRO битвы и принимать в них участие. Уникальная краска не даёт дополнительную защиту танку." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/premium/premuim_pack_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": { 3 | "jpy": 1500 4 | }, 5 | "premium": { 6 | "base": 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/premium/premuim_pack_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": { 3 | "jpy": 7000 4 | }, 5 | "premium": { 6 | "base": 7 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/data/store/items/premium/premuim_pack_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": { 3 | "jpy": 12000 4 | }, 5 | "premium": { 6 | "base": 14 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [%date{HH:mm:ss.SSS}] [%logger/%level]: %message%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------