Full Code of Sorapointa/Sorapointa for AI

master 31578cb9e460 cached
479 files
1.2 MB
394.1k tokens
11 symbols
1 requests
Download .txt
Showing preview only (1,346K chars total). Download the full file or copy to clipboard to get everything.
Repository: Sorapointa/Sorapointa
Branch: master
Commit: 31578cb9e460
Files: 479
Total size: 1.2 MB

Directory structure:
gitextract_y0piscv0/

├── .editorconfig
├── .git-hooks/
│   ├── commit-msg
│   └── pre-commit
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── api_check.yml
│       └── test.yml
├── .gitignore
├── .idea/
│   └── encodings.xml
├── LICENSE
├── build.gradle.kts
├── buildSrc/
│   ├── build.gradle.kts
│   ├── settings.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               ├── BuildConfigExtension.kt
│               ├── GitHook.kt
│               ├── JniHeader.kt
│               ├── OptInAnnotations.kt
│               ├── Properties.kt
│               ├── ResourcesCopy.kt
│               ├── Test.kt
│               ├── sorapointa-conventions.gradle.kts
│               └── sorapointa-publish.gradle.kts
├── docs/
│   ├── CONTRIBUTING.md
│   ├── CONTRIBUTING.zh-CN.md
│   ├── README.md
│   ├── README.zh-CN.md
│   └── guides/
│       ├── concurrency.md
│       ├── concurrency.zh-CN.md
│       ├── database.md
│       ├── database.zh-CN.md
│       ├── kotlin-atomicfu.md
│       ├── kotlin-atomicfu.zh-CN.md
│       ├── unit-test.md
│       └── unit-test.zh-CN.md
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── renovate.json
├── settings.gradle.kts
├── sorapointa-core/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── kotlin/
│       │   │   └── org/
│       │   │       └── sorapointa/
│       │   │           ├── CoreBundle.kt
│       │   │           ├── Main.kt
│       │   │           ├── Sorapointa.kt
│       │   │           ├── SorapointaConfig.kt
│       │   │           ├── command/
│       │   │           │   ├── Command.kt
│       │   │           │   ├── CommandLocalization.kt
│       │   │           │   ├── CommandManager.kt
│       │   │           │   ├── CommandSender.kt
│       │   │           │   ├── ConsoleCommandSender.kt
│       │   │           │   ├── defaults/
│       │   │           │   │   ├── Defaults.kt
│       │   │           │   │   ├── console/
│       │   │           │   │   │   ├── ConsoleUser.kt
│       │   │           │   │   │   └── Quit.kt
│       │   │           │   │   └── general/
│       │   │           │   │       ├── Help.kt
│       │   │           │   │       ├── ListPlayer.kt
│       │   │           │   │       ├── LocaleCommand.kt
│       │   │           │   │       └── Version.kt
│       │   │           │   └── utils/
│       │   │           │       └── Options.kt
│       │   │           ├── console/
│       │   │           │   ├── Completer.kt
│       │   │           │   ├── Console.kt
│       │   │           │   ├── JLineRedirector.kt
│       │   │           │   ├── SoraHighlighter.kt
│       │   │           │   └── WebSocketConsole.kt
│       │   │           ├── events/
│       │   │           │   └── PlayerEvent.kt
│       │   │           ├── game/
│       │   │           │   ├── AvatarEntity.kt
│       │   │           │   ├── Player.kt
│       │   │           │   ├── PlayerAvatarComp.kt
│       │   │           │   ├── PlayerComp.kt
│       │   │           │   ├── PlayerItemComp.kt
│       │   │           │   ├── Scene.kt
│       │   │           │   ├── SceneEntity.kt
│       │   │           │   ├── World.kt
│       │   │           │   └── data/
│       │   │           │       ├── GameConstants.kt
│       │   │           │       ├── PlayerData.kt
│       │   │           │       ├── Position.kt
│       │   │           │       └── SorapointaStoreEntry.kt
│       │   │           ├── server/
│       │   │           │   ├── ServerNetwork.kt
│       │   │           │   └── network/
│       │   │           │       ├── NetworkHandler.kt
│       │   │           │       ├── OutgoingPacket.kt
│       │   │           │       ├── PacketHandler.kt
│       │   │           │       ├── PacketHandlerImpl.kt
│       │   │           │       └── SoraPacket.kt
│       │   │           └── utils/
│       │   │               ├── Console.kt
│       │   │               ├── GameUtils.kt
│       │   │               ├── NetworkUtils.kt
│       │   │               ├── OptionalContainer.kt
│       │   │               ├── PropDelegate.kt
│       │   │               └── TypoSuggestor.kt
│       │   └── resources/
│       │       ├── logback.xml
│       │       └── messages/
│       │           ├── CoreBundle.properties
│       │           └── CoreBundle_zh_CN.properties
│       └── test/
│           ├── kotlin/
│           │   └── org/
│           │       └── sorapointa/
│           │           ├── command/
│           │           │   └── defaults/
│           │           │       └── HelpTest.kt
│           │           └── logger/
│           │               └── LogTest.kt
│           └── resources/
│               └── logback-test.xml
├── sorapointa-crypto/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── crypto/
│                           └── Crypto.kt
├── sorapointa-dataloader/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── dataloader/
│       │                   ├── DataLoader.kt
│       │                   ├── common/
│       │                   │   ├── AddProp.kt
│       │                   │   ├── CurveInfo.kt
│       │                   │   ├── Enum.kt
│       │                   │   ├── ItemParamData.kt
│       │                   │   ├── ItemParamStringData.kt
│       │                   │   ├── OpenCondData.kt
│       │                   │   ├── PointData.kt
│       │                   │   ├── PropGrowCurve.kt
│       │                   │   ├── RewardItemData.kt
│       │                   │   └── ScenePointConfig.kt
│       │                   └── def/
│       │                       ├── AvatarExcelData.kt
│       │                       ├── AvatarSkillData.kt
│       │                       ├── AvatarSkillDepotData.kt
│       │                       ├── MaterialData.kt
│       │                       ├── ReliquaryAffixData.kt
│       │                       ├── ReliquaryData.kt
│       │                       ├── ReliquaryLevelData.kt
│       │                       ├── ReliquaryMainPropData.kt
│       │                       ├── ReliquarySetData.kt
│       │                       ├── SceneData.kt
│       │                       └── WeaponData.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── dataloader/
│                           └── DataLoaderTest.kt
├── sorapointa-dataprovider/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── data/
│       │                   └── provider/
│       │                       ├── AutoLoadFilePersist.kt
│       │                       ├── AutoSaveFilePersist.kt
│       │                       ├── DataFilePersist.kt
│       │                       ├── DatabaseConfig.kt
│       │                       ├── DatabaseManager.kt
│       │                       ├── FilePersist.kt
│       │                       └── sql/
│       │                           ├── SQLJson.kt
│       │                           ├── SQLMap.kt
│       │                           └── SQLSet.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── data/
│                           └── provider/
│                               ├── DatabaseProviderTest.kt
│                               ├── FileProviderTest.kt
│                               └── Init.kt
├── sorapointa-dispatch/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── kotlin/
│       │   │   └── org/
│       │   │       └── sorapointa/
│       │   │           └── dispatch/
│       │   │               ├── DispatchBundle.kt
│       │   │               ├── DispatchServer.kt
│       │   │               ├── data/
│       │   │               │   ├── AccountData.kt
│       │   │               │   ├── DispatchData.kt
│       │   │               │   ├── DispatchKeyData.kt
│       │   │               │   └── SwitchData.kt
│       │   │               ├── events/
│       │   │               │   └── DispatchEvent.kt
│       │   │               ├── plugins/
│       │   │               │   ├── HTTP.kt
│       │   │               │   ├── Monitoring.kt
│       │   │               │   ├── RouteHandler.kt
│       │   │               │   ├── Routing.kt
│       │   │               │   ├── Serialization.kt
│       │   │               │   └── StatusPage.kt
│       │   │               └── utils/
│       │   │                   ├── CertBuilder.kt
│       │   │                   ├── Certificates.kt
│       │   │                   ├── KeyProvider.kt
│       │   │                   └── Route.kt
│       │   └── resources/
│       │       └── messages/
│       │           ├── DispatchBundle.properties
│       │           └── DispatchBundle_zh_CN.properties
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── dispatch/
│                           ├── AccountTest.kt
│                           ├── CertTest.kt
│                           └── DispatchServerTest.kt
├── sorapointa-event/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── event/
│       │                   ├── Event.kt
│       │                   ├── EventManager.kt
│       │                   └── StateController.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── event/
│                           ├── EventPipelineTest.kt
│                           └── StateControllerTest.kt
├── sorapointa-i18n/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── utils/
│       │                   ├── I18n.kt
│       │                   └── MessageBundle.kt
│       └── test/
│           ├── kotlin/
│           │   └── org/
│           │       └── sorapointa/
│           │           └── utils/
│           │               ├── I18nTest.kt
│           │               ├── LocalSerializerTest.kt
│           │               └── TestBundle.kt
│           └── resources/
│               └── messages/
│                   ├── TestBundle.properties
│                   └── TestBundle_nl.properties
├── sorapointa-native/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── jnienv.rs
│       ├── lib.rs
│       └── logger.rs
├── sorapointa-native-wrapper/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── rust/
│       │                   ├── Setup.kt
│       │                   └── logging/
│       │                       └── RustLogger.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── rust/
│                           └── logging/
│                               └── LoggerTest.kt
├── sorapointa-proto/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── proto/
│       │                   ├── PacketUtils.kt
│       │                   └── ProtoInfo.kt
│       ├── proto/
│       │   ├── AbilityAppliedAbility.proto
│       │   ├── AbilityAppliedModifier.proto
│       │   ├── AbilityAttachedModifier.proto
│       │   ├── AbilityControlBlock.proto
│       │   ├── AbilityEmbryo.proto
│       │   ├── AbilityGadgetInfo.proto
│       │   ├── AbilityMixinRecoverInfo.proto
│       │   ├── AbilityScalarType.proto
│       │   ├── AbilityScalarValueEntry.proto
│       │   ├── AbilityString.proto
│       │   ├── AbilitySyncStateInfo.proto
│       │   ├── AdjustTrackingInfo.proto
│       │   ├── AnimatorParameterValueInfo.proto
│       │   ├── AnimatorParameterValueInfoPair.proto
│       │   ├── AvatarDataNotify.proto
│       │   ├── AvatarEnterSceneInfo.proto
│       │   ├── AvatarEquipAffixInfo.proto
│       │   ├── AvatarExcelInfo.proto
│       │   ├── AvatarExpeditionState.proto
│       │   ├── AvatarFetterInfo.proto
│       │   ├── AvatarFightPropNotify.proto
│       │   ├── AvatarFightPropUpdateNotify.proto
│       │   ├── AvatarInfo.proto
│       │   ├── AvatarLifeStateChangeNotify.proto
│       │   ├── AvatarPropChangeReasonNotify.proto
│       │   ├── AvatarPropNotify.proto
│       │   ├── AvatarRenameInfo.proto
│       │   ├── AvatarSkillInfo.proto
│       │   ├── AvatarTeam.proto
│       │   ├── AvatarTeamUpdateNotify.proto
│       │   ├── AvatarUpgradeRsp.proto
│       │   ├── Birthday.proto
│       │   ├── BlockInfo.proto
│       │   ├── BlossomChestInfo.proto
│       │   ├── BossChestInfo.proto
│       │   ├── BreakoutAction.proto
│       │   ├── BreakoutBrickInfo.proto
│       │   ├── BreakoutElementReactionCounter.proto
│       │   ├── BreakoutPhysicalObject.proto
│       │   ├── BreakoutPhysicalObjectModifier.proto
│       │   ├── BreakoutSnapShot.proto
│       │   ├── BreakoutSpawnPoint.proto
│       │   ├── BreakoutSyncAction.proto
│       │   ├── BreakoutSyncConnectUidInfo.proto
│       │   ├── BreakoutSyncCreateConnect.proto
│       │   ├── BreakoutSyncFinishGame.proto
│       │   ├── BreakoutSyncPing.proto
│       │   ├── BreakoutSyncSnapShot.proto
│       │   ├── BreakoutVector2.proto
│       │   ├── ChangeGameTimeReq.proto
│       │   ├── ChangeGameTimeRsp.proto
│       │   ├── ClientGadgetInfo.proto
│       │   ├── CoinCollectOperatorInfo.proto
│       │   ├── CurVehicleInfo.proto
│       │   ├── CustomCommonNodeInfo.proto
│       │   ├── CustomGadgetTreeInfo.proto
│       │   ├── DeshretObeliskGadgetInfo.proto
│       │   ├── DoSetPlayerBornDataNotify.proto
│       │   ├── EchoShellInfo.proto
│       │   ├── EnterSceneDoneReq.proto
│       │   ├── EnterSceneDoneRsp.proto
│       │   ├── EnterScenePeerNotify.proto
│       │   ├── EnterSceneReadyReq.proto
│       │   ├── EnterSceneReadyRsp.proto
│       │   ├── EnterType.proto
│       │   ├── EntityAuthorityInfo.proto
│       │   ├── EntityClientData.proto
│       │   ├── EntityClientExtraInfo.proto
│       │   ├── EntityEnvironmentInfo.proto
│       │   ├── EntityRendererChangedInfo.proto
│       │   ├── Equip.proto
│       │   ├── FeatureBlockInfo.proto
│       │   ├── FetterData.proto
│       │   ├── FightPropPair.proto
│       │   ├── FishPoolInfo.proto
│       │   ├── FishtankFishInfo.proto
│       │   ├── ForceUpdateInfo.proto
│       │   ├── FoundationInfo.proto
│       │   ├── FoundationStatus.proto
│       │   ├── FriendEnterHomeOption.proto
│       │   ├── FriendOnlineState.proto
│       │   ├── Furniture.proto
│       │   ├── GadgetBornType.proto
│       │   ├── GadgetCrucibleInfo.proto
│       │   ├── GadgetGeneralRewardInfo.proto
│       │   ├── GadgetPlayInfo.proto
│       │   ├── GatherGadgetInfo.proto
│       │   ├── GetPlayerSocialDetailReq.proto
│       │   ├── GetPlayerSocialDetailRsp.proto
│       │   ├── GetPlayerTokenReq.proto
│       │   ├── GetPlayerTokenRsp.proto
│       │   ├── HostPlayerNotify.proto
│       │   ├── Item.proto
│       │   ├── ItemParam.proto
│       │   ├── LifeStateChangeNotify.proto
│       │   ├── MPLevelEntityInfo.proto
│       │   ├── MassivePropParam.proto
│       │   ├── MassivePropSyncInfo.proto
│       │   ├── Material.proto
│       │   ├── MaterialDeleteInfo.proto
│       │   ├── MathQuaternion.proto
│       │   ├── ModifierDurability.proto
│       │   ├── MonsterBornType.proto
│       │   ├── MonsterRoute.proto
│       │   ├── MotionInfo.proto
│       │   ├── MotionState.proto
│       │   ├── MovingPlatformType.proto
│       │   ├── MpPlayRewardInfo.proto
│       │   ├── MpSettingType.proto
│       │   ├── NightCrowGadgetInfo.proto
│       │   ├── OfferingInfo.proto
│       │   ├── OnlinePlayerInfo.proto
│       │   ├── OpenStateUpdateNotify.proto
│       │   ├── PacketHead.proto
│       │   ├── PingReq.proto
│       │   ├── PingRsp.proto
│       │   ├── PlatformInfo.proto
│       │   ├── PlayTeamEntityInfo.proto
│       │   ├── PlayerDataNotify.proto
│       │   ├── PlayerDieOption.proto
│       │   ├── PlayerDieType.proto
│       │   ├── PlayerEnterSceneInfoNotify.proto
│       │   ├── PlayerEnterSceneNotify.proto
│       │   ├── PlayerGameTimeNotify.proto
│       │   ├── PlayerLocationInfo.proto
│       │   ├── PlayerLoginReq.proto
│       │   ├── PlayerLoginRsp.proto
│       │   ├── PlayerPropChangeNotify.proto
│       │   ├── PlayerPropChangeReasonNotify.proto
│       │   ├── PlayerPropNotify.proto
│       │   ├── PlayerRTTInfo.proto
│       │   ├── PlayerSetPauseReq.proto
│       │   ├── PlayerSetPauseRsp.proto
│       │   ├── PlayerStoreNotify.proto
│       │   ├── PlayerWidgetInfo.proto
│       │   ├── PlayerWorldLocationInfo.proto
│       │   ├── PlayerWorldSceneInfo.proto
│       │   ├── PlayerWorldSceneInfoListNotify.proto
│       │   ├── PostEnterSceneReq.proto
│       │   ├── PostEnterSceneRsp.proto
│       │   ├── ProfilePicture.proto
│       │   ├── PropChangeReason.proto
│       │   ├── PropPair.proto
│       │   ├── PropValue.proto
│       │   ├── ProtEntityType.proto
│       │   ├── QueryCurrRegionHttpRsp.proto
│       │   ├── QueryRegionListHttpRsp.proto
│       │   ├── RegionInfo.proto
│       │   ├── RegionSimpleInfo.proto
│       │   ├── Reliquary.proto
│       │   ├── ResVersionConfig.proto
│       │   ├── Retcode.proto
│       │   ├── RoguelikeGadgetInfo.proto
│       │   ├── Route.proto
│       │   ├── RoutePoint.proto
│       │   ├── SceneAvatarInfo.proto
│       │   ├── SceneDataNotify.proto
│       │   ├── SceneEntityAiInfo.proto
│       │   ├── SceneEntityAppearNotify.proto
│       │   ├── SceneEntityInfo.proto
│       │   ├── SceneFishInfo.proto
│       │   ├── SceneGadgetInfo.proto
│       │   ├── SceneInitFinishReq.proto
│       │   ├── SceneInitFinishRsp.proto
│       │   ├── SceneMonsterInfo.proto
│       │   ├── SceneNpcInfo.proto
│       │   ├── ScenePlayerInfo.proto
│       │   ├── ScenePlayerInfoNotify.proto
│       │   ├── ScenePlayerLocationNotify.proto
│       │   ├── SceneReliquaryInfo.proto
│       │   ├── SceneTeamAvatar.proto
│       │   ├── SceneTeamUpdateNotify.proto
│       │   ├── SceneTimeNotify.proto
│       │   ├── SceneWeaponInfo.proto
│       │   ├── ScreenInfo.proto
│       │   ├── ServantInfo.proto
│       │   ├── ServerBuff.proto
│       │   ├── ServerDisconnectClientNotify.proto
│       │   ├── ServerTimeNotify.proto
│       │   ├── SetPlayerBornDataReq.proto
│       │   ├── SetPlayerBornDataRsp.proto
│       │   ├── SetPlayerPropReq.proto
│       │   ├── SetPlayerPropRsp.proto
│       │   ├── ShortAbilityHashPair.proto
│       │   ├── SocialDetail.proto
│       │   ├── SocialShowAvatarInfo.proto
│       │   ├── StatueGadgetInfo.proto
│       │   ├── StopServerInfo.proto
│       │   ├── StoreType.proto
│       │   ├── StoreWeightLimitNotify.proto
│       │   ├── SyncScenePlayTeamEntityNotify.proto
│       │   ├── SyncTeamEntityNotify.proto
│       │   ├── TeamEnterSceneInfo.proto
│       │   ├── TeamEntityInfo.proto
│       │   ├── TrackingIOInfo.proto
│       │   ├── TrialAvatarGrantRecord.proto
│       │   ├── TrialAvatarInfo.proto
│       │   ├── UnionCmd.proto
│       │   ├── UnionCmdNotify.proto
│       │   ├── Vector.proto
│       │   ├── VehicleInfo.proto
│       │   ├── VehicleLocationInfo.proto
│       │   ├── VehicleMember.proto
│       │   ├── VisionType.proto
│       │   ├── Weapon.proto
│       │   ├── WeatherInfo.proto
│       │   ├── WeeklyBossResinDiscountInfo.proto
│       │   ├── WidgetSlotData.proto
│       │   ├── WidgetSlotTag.proto
│       │   ├── WorktopInfo.proto
│       │   ├── WorldDataNotify.proto
│       │   ├── WorldPlayerDieNotify.proto
│       │   ├── WorldPlayerInfoNotify.proto
│       │   ├── WorldPlayerLocationNotify.proto
│       │   ├── WorldPlayerRTTNotify.proto
│       │   ├── WorldPlayerReviveReq.proto
│       │   ├── WorldPlayerReviveRsp.proto
│       │   └── server_side/
│       │       ├── bin.block.proto
│       │       ├── bin.home.proto
│       │       ├── bin.server.proto
│       │       ├── bin_common.server.proto
│       │       ├── cmd_activity.server.proto
│       │       ├── cmd_id_config.proto
│       │       ├── cmd_match.server.proto
│       │       ├── cmd_misc.server.proto
│       │       ├── cmd_mp.server.proto
│       │       ├── cmd_muip.server.proto
│       │       ├── cmd_offline_op.server.proto
│       │       ├── cmd_player.server.proto
│       │       ├── config.server.proto
│       │       ├── define.proto
│       │       └── enum.server.proto
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── proto/
│                           └── ProtoTest.kt
├── sorapointa-task/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── task/
│       │                   ├── Cron.kt
│       │                   ├── CronTask.kt
│       │                   └── TaskManager.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── task/
│                           └── TaskManagerTest.kt
└── sorapointa-utils/
    ├── build.gradle.kts
    ├── sorapointa-utils-all/
    │   └── build.gradle.kts
    ├── sorapointa-utils-core/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── main/
    │       │   └── kotlin/
    │       │       └── org/
    │       │           └── sorapointa/
    │       │               └── utils/
    │       │                   ├── Annotations.kt
    │       │                   ├── ByteUtils.kt
    │       │                   ├── Cast.kt
    │       │                   ├── Collection.kt
    │       │                   ├── Environment.kt
    │       │                   ├── File.kt
    │       │                   ├── Files.kt
    │       │                   ├── JVM.kt
    │       │                   ├── Locks.kt
    │       │                   ├── ModuleScope.kt
    │       │                   ├── Optional.kt
    │       │                   ├── Random.kt
    │       │                   ├── Reflection.kt
    │       │                   ├── String.kt
    │       │                   ├── Test.kt
    │       │                   ├── XML.kt
    │       │                   ├── encoding/
    │       │                   │   ├── Base64Provider.kt
    │       │                   │   ├── Digest.kt
    │       │                   │   ├── Hex.kt
    │       │                   │   └── RSAProvider.kt
    │       │                   └── logging/
    │       │                       └── PatternLayoutNoLambda.kt
    │       └── test/
    │           └── kotlin/
    │               └── org/
    │                   └── sorapointa/
    │                       └── utils/
    │                           ├── FilesTest.kt
    │                           ├── LocksTest.kt
    │                           ├── ScopeTest.kt
    │                           ├── StringTest.kt
    │                           └── encoding/
    │                               ├── Base64.kt
    │                               └── HexTest.kt
    ├── sorapointa-utils-crypto/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── main/
    │       │   └── kotlin/
    │       │       └── org/
    │       │           └── sorapointa/
    │       │               └── utils/
    │       │                   ├── ByteReadUtils.kt
    │       │                   └── crypto/
    │       │                       ├── Ec2b.kt
    │       │                       ├── Ec2bAes.kt
    │       │                       ├── MT19937.kt
    │       │                       ├── Magic.kt
    │       │                       └── RSA.kt
    │       └── test/
    │           └── kotlin/
    │               └── org/
    │                   └── sorapointa/
    │                       └── utils/
    │                           ├── ScopeTest.kt
    │                           └── crypto/
    │                               ├── Ec2bTest.kt
    │                               ├── MT64Test.kt
    │                               └── RSAKeyTest.kt
    ├── sorapointa-utils-serialization/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── main/
    │       │   └── kotlin/
    │       │       └── org/
    │       │           └── sorapointa/
    │       │               └── utils/
    │       │                   ├── Json.kt
    │       │                   └── Yaml.kt
    │       └── test/
    │           └── kotlin/
    │               └── org/
    │                   └── sorapointa/
    │                       └── utils/
    │                           └── YamlCompatible.kt
    └── sorapointa-utils-time/
        ├── build.gradle.kts
        └── src/
            └── main/
                └── kotlin/
                    └── org/
                        └── sorapointa/
                            └── utils/
                                └── Time.kt

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
[*]
insert_final_newline = true
charset = utf-8
indent_style = space
end_of_line = lf

[{*.kt,*.kts}]
indent_size = 4
max_line_length = 120

[*.rs]
indent_size = 4
max_line_length = 120

[{*.yml,*.yaml}]
indent_size = 2

[*.md]
indent_size = 2


================================================
FILE: .git-hooks/commit-msg
================================================
#!/usr/bin/env bash

INPUT_FILE=$1

START_LINE=$(head -n1 "$INPUT_FILE")

PATTERN='^(feat(ure)?|fix|docs|style|refactor|ci|chore|perf|build|test|revert)(\(.+\))?(!)?: .+$'

if [ "${#START_LINE}" -gt "72" ]; then
  echo -e "Message too long! Assert length <= 72."
  exit
fi

if ! [[ "$START_LINE" =~ $PATTERN ]]; then
  echo -e "$START_LINE"
  echo
  echo -e "↑ Bad commit message, it does not meet the Conventional Commit standard."
  echo -e "See more: https://www.conventionalcommits.org/en/v1.0.0/"
  exit 1
fi


================================================
FILE: .git-hooks/pre-commit
================================================
#!/bin/bash

echo "[pre-commit check]"

if ! [ -x "$(command -v cargo)" ]; then
  echo -e 'Rust toolchains are not installed!'
  echo
  echo 'If you are running on Unix-like platform, you can follow the on-screen instructions:'
  echo
  echo "  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
  echo
  echo 'If you are running on Windows, or you want to install'
  echo 'Rust toolchains via package managers, you can see:'
  echo
  echo '- [Install Rust - rust-lang.org](https://www.rust-lang.org/tools/install)'
  echo '- [Other Rust Installation Methods](https://forge.rust-lang.org/infra/other-installation-methods.html)'
  exit 1
fi

kotlin() {
  if [ ! -e "./gradlew" ]; then
    return 0
  fi

  CHANGED_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $NF ~ /\.kts?$/ { print $NF }')"

  if [ -z "$CHANGED_FILES" ]; then
    echo "No Kotlin staged files."
    return 0
  fi

  echo '[pre-commit] Executing Gradle spotlessCheck before commit'

  git stash --quiet --keep-index

  ./gradlew spotlessCheck --daemon

  RESULT=$?

  git stash pop -q

  if [ "$RESULT" -ne "0" ]; then
    echo -e "spotlessCheck failed..."
    echo -e 'You can try "./gradlew spotlessApply" to apply auto-fixes.'
  fi

  return $RESULT
}

rust() {
  cd sorapointa-native || return 0

  CHANGED_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $NF ~ /\.rs$/ { print $NF }')"

  if [ -z "$CHANGED_FILES" ]; then
    echo "No Rust staged files."
    return 0
  fi

  if ! cargo clippy -- -D warnings; then
    echo -e "cargo clippy failed..."
    return 1
  fi

  if ! cargo fmt --all -- --check; then
    echo -e "cargo fmt failed..."
    echo -e "You can manually run 'cargo fmt' at './sorapointa-native' for auto format"
    return 1
  fi
}

if ! kotlin; then
  exit 1
fi

if ! rust; then
  exit 1
fi


================================================
FILE: .gitattributes
================================================
# Linux start script should use lf
/gradlew        text eol=lf

# These are Windows script files and should use crlf
*.bat           text eol=crlf


================================================
FILE: .github/workflows/api_check.yml
================================================
name: API Check

on:
  workflow_dispatch:
  push:
    branches: [ master ]
    paths:
      - '**.kt'
      - '**.kts'
      - '**.proto'
      - '.github/workflows/*.yml'
  pull_request:
    branches:
      - '*'
    paths:
      - '**.kt'
      - '**.kts'
      - '**.proto'
      - '.github/workflows/*.yml'

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'

      - uses: burrunan/gradle-cache-action@v1
        name: Checker
        with:
          job-id: api-checker
          arguments: apiCheck
          gradle-version: wrapper


================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on:
  workflow_dispatch:
  push:
    branches: [ master ]
    paths:
      - '**.kt'
      - '**.kts'
      - '**.rs'
      - '**.proto'
      - 'Cargo.toml'
      - 'gradle-wrapper.properties'
      - 'gradle/libs.versions.toml'
      - '.github/workflows/*.yml'
  pull_request:
    branches:
      - '*'
    paths:
      - '**.kt'
      - '**.kts'
      - '**.rs'
      - '**.proto'
      - 'Cargo.toml'
      - 'gradle-wrapper.properties'
      - 'gradle/libs.versions.toml'
      - '.github/workflows/*.yml'

jobs:
  clippy_rustfmt:
    runs-on: ubuntu-latest
    steps:
        - uses: actions/checkout@v3
        - name: Set up Rust Toolchains
          uses: actions-rs/toolchain@v1
          with:
            profile: minimal
            toolchain: stable
            components: rustfmt, clippy

        - name: Rust Cache
          uses: Swatinem/rust-cache@v2
          with:
            prefix-key: "v0-rust"
            workspaces: "sorapointa-native -> target"

        - name: Clippy
          uses: giraffate/clippy-action@v1
          with:
            workdir: ./sorapointa-native
            reporter: 'github-pr-review'
            github_token: ${{ secrets.GITHUB_TOKEN }}
            clippy_flags: -- -Dwarnings

        - name: rustfmt
          working-directory: ./sorapointa-native
          run: cargo fmt --all -- --check

  spotlessCheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'

      - name: Set up Rust Toolchains
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable

      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          prefix-key: "v0-rust"
          workspaces: "sorapointa-native -> target"

      - uses: burrunan/gradle-cache-action@v1
        name: Checker
        with:
          job-id: checker
          arguments: spotlessCheck
          gradle-version: wrapper

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'

      - name: Set up Rust Toolchains
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable

      - name: Rust Cache
        uses: Swatinem/rust-cache@v2
        with:
          prefix-key: "v0-rust"
          workspaces: "sorapointa-native -> target"

      - uses: burrunan/gradle-cache-action@v1
        name: Checker
        with:
          job-id: checker
          arguments: test
          gradle-version: wrapper


================================================
FILE: .gitignore
================================================
### Gradle ###
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### Cargo ###
target/
Cargo.lock

### IntelliJ IDEA ###
/.idea/**/*
!/.idea/encodings.xml

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store

### Proto ###
generated/

### Tmp ###
**/tmp

### Logs ###
**/logs/*
*.log
**/src/main/resources/logback.xml
**/src/test/resources/logback-test.xml
**/src/main/resources/langs/
!sorapointa-core/src/main/resources/logback.xml
!sorapointa-core/src/test/resources/logback-test.xml

### Resources ###
/resources

### Build Config ###
local.properties

### Sorapointa Config & Generated Files ###
sorapointa-core/langs/
sorapointa-dispatch/langs/
**/config/
/cache


### Database ###
**/*.db
*.db-journal


================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
    <file url="PROJECT" charset="UTF-8" />
  </component>
</project>


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2022 Sorapointa Organization and Contributors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: build.gradle.kts
================================================
import com.diffplug.gradle.spotless.FormatExtension

plugins {
    kotlin("jvm") apply false
    java
    // NOT AN ERROR, see: https://youtrack.jetbrains.com/issue/KTIJ-19369
    // You can install a plugin to suppress it:
    // https://plugins.jetbrains.com/plugin/18949-gradle-libs-error-suppressor
    alias(libs.plugins.spotless)
    alias(libs.plugins.licensee)
    alias(libs.plugins.kotlin.serialization)
    alias(libs.plugins.ktlint)
    alias(libs.plugins.abi.validator)
    alias(libs.plugins.wire) apply false
    alias(libs.plugins.rust.wrapper) apply false
}

subprojects {
    if (!arrayOf("sorapointa-native", "sorapointa-utils").contains(project.name)) {
        apply(plugin = "app.cash.licensee")
        configureLicensee()
    }
    apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
    afterEvaluate {
        configureLogbackCopy()
    }
}

allprojects {
    repositories {
        mavenCentral()
    }
    group = "moe.sdl.sorapointa"
    version = "0.1.0-SNAPSHOT"
}

installGitHooks()

spotless {
    fun FormatExtension.excludes() {
        targetExclude("**/build/", "**/generated/", "**/resources/")
    }

    fun FormatExtension.common() {
        trimTrailingWhitespace()
        lineEndings = com.diffplug.spotless.LineEnding.UNIX
        endWithNewline()
    }

    val ktlintConfig = mapOf(
        "ij_kotlin_allow_trailing_comma" to "true",
        "ij_kotlin_allow_trailing_comma_on_call_site" to "true",
        "trailing-comma-on-declaration-site" to "true",
        "trailing-comma-on-call-site" to "true",
        "ktlint_standard_no-wildcard-imports" to "disabled",
        "ktlint_disabled_import-ordering" to "disabled",
    )

    kotlin {
        target("**/*.kt")
        excludes()
        common()
        ktlint(libs.versions.ktlint.get()).editorConfigOverride(ktlintConfig)
    }

    kotlinGradle {
        target("**/*.gradle.kts")
        excludes()
        common()
        ktlint(libs.versions.ktlint.get()).editorConfigOverride(ktlintConfig)
    }
}

fun Project.configureLicensee() = this.configure<app.cash.licensee.LicenseeExtension>() {
    val allowedLicenses = arrayOf(
        "Apache-2.0",
        "MIT",
        "ISC",
        "BSD-2-Clause",
        "BSD-3-Clause",
        "CC0-1.0",
        "EPL-1.0",
        "GPL-2.0-with-classpath-exception",
    )
    allowedLicenses.forEach { allow(it) }
    ignoreDependencies("org.postgresql", "postgresql") {
        because("BSD-2-Clause, but typo in license URL")
    }
}


================================================
FILE: buildSrc/build.gradle.kts
================================================
plugins {
    `kotlin-dsl`
}

repositories {
    gradlePluginPortal()
    mavenCentral()
    maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
}

dependencies {
    implementation(libs.kotlin.gradle.plugin)
    implementation(libs.build.kotlinpoet)
    implementation(libs.build.buildconfig)
    implementation(libs.build.shadow)
}

sourceSets {
    main {
        groovy {
            setSrcDirs(emptySet<File>()) // No Groovy
        }
        java {
            setSrcDirs(setOf("kotlin")) // No Java
        }
    }
    test {
        groovy {
            setSrcDirs(emptySet<File>())
        }
        java {
            setSrcDirs(setOf("kotlin"))
        }
    }
}

kotlin {
    jvmToolchain {
        this.languageVersion.set(JavaLanguageVersion.of(17))
    }
}


================================================
FILE: buildSrc/settings.gradle.kts
================================================
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}


================================================
FILE: buildSrc/src/main/kotlin/BuildConfigExtension.kt
================================================
import com.github.gmazzo.gradle.plugins.BuildConfigSourceSet

fun BuildConfigSourceSet.string(name: String, value: String) = buildConfigField("String", name, "\"$value\"")
fun BuildConfigSourceSet.stringNullable(name: String, value: String?) =
    buildConfigField("String?", name, value?.let { "\"$value\"" } ?: "null")

fun BuildConfigSourceSet.long(name: String, value: Long) = buildConfigField("long", name, value.toString())
fun BuildConfigSourceSet.longNullable(name: String, value: Long?) =
    buildConfigField("Long?", name, value?.let { "$value" } ?: "null")

fun BuildConfigSourceSet.int(name: String, value: Int) = buildConfigField("int", name, value.toString())
fun BuildConfigSourceSet.intNullable(name: String, value: Int?) =
    buildConfigField("int", name, value?.let { "$value" } ?: "null")

fun BuildConfigSourceSet.boolean(name: String, value: Boolean) = buildConfigField("boolean", name, value.toString())


================================================
FILE: buildSrc/src/main/kotlin/GitHook.kt
================================================
import org.gradle.api.Project
import org.gradle.internal.os.OperatingSystem
import java.io.File
import java.nio.file.Files

fun Project.installGitHooks() {
    val target = File(project.rootProject.rootDir, ".git/hooks")
    val source = File(project.rootProject.rootDir, ".git-hooks")
    if (target.canonicalFile == source) return
    target.deleteRecursively()
    if (OperatingSystem.current().isWindows) {
        source.copyRecursively(target)
    } else {
        Files.createSymbolicLink(target.toPath(), source.toPath())
    }
}


================================================
FILE: buildSrc/src/main/kotlin/JniHeader.kt
================================================
import org.gradle.api.Project
import org.gradle.api.tasks.TaskContainer
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import java.io.ByteArrayOutputStream

val bodyExtractingRegex = Regex("""^.+\Rpublic \w* ?class ([^\s]+).*\{\R((?s:.+))\}\R$""")
val nativeMethodExtractingRegex = Regex(""".*\bnative\b.*""")

fun Project.jniHeaderTask(tasks: TaskContainer) = tasks.create("generateJniHeaders") {
    group = "build"
    dependsOn(tasks.getByName("compileKotlin"))

    project.kotlinExtension.sourceSets.getByName("main").kotlin.srcDirs.filter {
        it.exists()
    }.forEach {
        inputs.dir(it)
    }
    outputs.dir("src/main/generated/jni")

    doLast {
        val javaHome = org.gradle.internal.jvm.Jvm.current().javaHome
        val javap = javaHome.resolve("bin").walk()
            .firstOrNull { it.name.startsWith("javap") }
            ?.absolutePath ?: error("javap not found")
        val javac = javaHome.resolve("bin").walk()
            .firstOrNull { it.name.startsWith("javac") }
            ?.absolutePath ?: error("javac not found")
        val buildDir = file("build/classes/kotlin/main")
        val tmpDir = file("build/tmp/jvmJni")
        tmpDir.mkdirs()

        buildDir.walk()
            .asSequence()
            .filter { "META" !in it.absolutePath }
            .filter { it.isFile }
            .filter { it.extension == "class" }
            .forEach { file ->
                val output = ByteArrayOutputStream().use {
                    project.exec {
                        commandLine(javap, "-private", "-cp", buildDir.absolutePath, file.absolutePath)
                        standardOutput = it
                    }.assertNormalExitValue()
                    it.toString()
                }

                val (qualifiedName, methodInfo) =
                    bodyExtractingRegex
                        .find(output)?.destructured
                        ?: return@forEach

                val lastDot = qualifiedName.lastIndexOf('.')
                val packageName = qualifiedName.substring(0, lastDot)
                val className = qualifiedName.substring(lastDot + 1, qualifiedName.length)

                val nativeMethods =
                    nativeMethodExtractingRegex.findAll(methodInfo).map { it.groups }
                        .flatMap { it.asSequence().mapNotNull { group -> group?.value } }.toList()
                if (nativeMethods.isEmpty()) return@forEach

                val generatedCode = buildString {
                    appendLine("package $packageName;")
                    appendLine("public class $className {")
                    nativeMethods.forEach { method ->
                        val newMethod = if (method.contains("()")) {
                            method
                        } else {
                            buildString {
                                append(method)
                                var count = 0
                                var i = 0
                                while (i < length) {
                                    if (this[i] == ',' || this[i] == ')') {
                                        count++
                                        insert(i, " arg$count".also { i += it.length + 1 })
                                    } else {
                                        i++
                                    }
                                }
                            }
                        }
                        appendLine(newMethod)
                    }
                    appendLine("}")
                }
                val javaFile = tmpDir
                    .resolve(packageName.replace(".", "/"))
                    .resolve("$className.java")
                javaFile.parentFile.mkdirs()
                if (javaFile.exists()) delete()
                javaFile.createNewFile()
                javaFile.writeText(generatedCode)
                project.exec {
                    commandLine(javac, "-h", "src/main/generated/jni", javaFile.absolutePath)
                }.assertNormalExitValue()
            }
    }
}


================================================
FILE: buildSrc/src/main/kotlin/OptInAnnotations.kt
================================================
object OptInAnnotations {
    val list = listOf(
        "kotlin.ExperimentalUnsignedTypes",
        "kotlin.contracts.ExperimentalContracts",
        "org.sorapointa.utils.SorapointaInternal",
    )
}


================================================
FILE: buildSrc/src/main/kotlin/Properties.kt
================================================
import org.gradle.api.Project
import org.gradle.kotlin.dsl.extra
import java.util.*

fun Project.getRootProjectLocalProps(): Map<String, String> {
    val file = project.rootProject.file("local.properties")
    return if (file.exists()) {
        file.reader().use {
            Properties().apply {
                load(it)
            }
        }.toMap().map {
            it.key.toString() to it.value.toString()
        }.toMap()
    } else {
        emptyMap()
    }
}

fun Project.getExtraString(name: String) = runCatching { this.extra[name]?.toString() }.getOrNull()

fun Project.getExtraBoolean(name: String) = runCatching { this.extra[name] as Boolean }.getOrNull()


================================================
FILE: buildSrc/src/main/kotlin/ResourcesCopy.kt
================================================
import org.gradle.api.Project
import org.gradle.api.tasks.Copy
import org.gradle.kotlin.dsl.register

internal fun Project.resourceTaskDep(task: String) {
    tasks.named("classes") {
        dependsOn(task)
    }

    tasks.named("processResources") {
        dependsOn(task)
    }
}

internal fun Project.testResourceTaskDep(task: String) {
    tasks.named("testClasses") {
        dependsOn(task)
    }

    tasks.named("processTestResources") {
        dependsOn(task)
    }
}

fun Project.configureLogbackCopy() {
    if (!pluginManager.hasPlugin("org.jetbrains.kotlin.jvm")) return
    if (name == "sorapointa-core") return

    fun registerCopyPath(name: String, source: String, dest: String) {
        tasks.register(name, Copy::class) {
            group = "resources"
            from(
                rootProject.subprojects.first { it.name == "sorapointa-core" }
                    .layout.projectDirectory.dir(source),
            )
            into(project.layout.projectDirectory.dir(dest))
        }
    }

    registerCopyPath(
        name = "copyLogbackXml",
        source = "./src/main/resources/logback.xml",
        dest = "./src/main/resources/",
    )
    registerCopyPath(
        name = "copyLogbackTestXml",
        source = "./src/test/resources/logback-test.xml",
        dest = "./src/test/resources/",
    )

    afterEvaluate {
        resourceTaskDep("copyLogbackXml")
        testResourceTaskDep("copyLogbackTestXml")
    }
}


================================================
FILE: buildSrc/src/main/kotlin/Test.kt
================================================
val isCI = System.getenv("CI") != null


================================================
FILE: buildSrc/src/main/kotlin/sorapointa-conventions.gradle.kts
================================================
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm")
    id("com.github.gmazzo.buildconfig")
    id("com.github.johnrengelman.shadow")
    java
}

repositories {
    mavenCentral()
    maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
    maven("https://plugins.gradle.org/m2/")
}

dependencies {
    constraints {
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    testImplementation(kotlin("test"))
}

sourceSets {
    main {
        java {
            setSrcDirs(setOf("kotlin")) // No Java, and Kotlin Only
        }
    }
    test {
        java {
            setSrcDirs(setOf("kotlin")) // No Java, and Kotlin Only
        }
    }
}

tasks.test {
    dependsOn("generateTestBuildConfig")
    useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
    kotlinOptions.apply {
        jvmTarget = "17"
        OptInAnnotations.list.forEach {
            freeCompilerArgs = freeCompilerArgs + "-opt-in=$it"
        }
    }
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

configurations {
    create("test")
}

tasks.register<Jar>("testArchive") {
    archiveBaseName.set("${project.name}-test")
    from(project.the<SourceSetContainer>()["test"].output)
}

artifacts {
    add("test", tasks["testArchive"])
}

tasks.withType<Jar>() {
    exclude("main") // duplicated jar root main, very confusing
    exclude("logback-test.xml")
    exclude("*.proto")
}

tasks.shadowJar {
    exclude("checkstyle.xml")
    exclude("**/*.html")
    exclude("CronUtilsI18N*.properties")
    exclude("DebugProbesKt.bin")
    exclude("custom.config.*")

    // SQLite
    exclude("org/sqlite/native/FreeBSD/**/*")
    exclude("org/sqlite/native/Linux-Android/**/*")
    exclude("org/sqlite/native/Linux-Musl/**/*")
    arrayOf("arm", "armv6", "armv7", "ppc64", "x86").forEach {
        exclude("org/sqlite/native/Linux/$it/**/*")
        exclude("org/sqlite/native/Windows/$it/**/*")
    }
    arrayOf("freebsd32", "freebsd64", "linux32", "windows32").forEach {
        exclude("META-INF/native/$it/**/*")
    }

    // JNA
    arrayOf("aix", "freebsd", "openbsd", "sunos").forEach {
        exclude("com/sun/jna/$it*/**/*")
    }
    arrayOf("arm", "armel", "loongarch64", "mips64el", "ppc", "ppc64le", "riscv64", "s390x", "x86").forEach {
        exclude("com/sun/jna/linux-$it/**/*")
        exclude("com/sun/jna/win32-$it/**/*")
    }

    // Jansi Native Lib
    exclude("org/fusesource/jansi/internal/native/FreeBSD")
    arrayOf("arm", "armv6", "armv7", "ppc64", "x86").forEach {
        exclude("org/fusesource/jansi/internal/native/Linux/$it/**/*")
        exclude("org/fusesource/jansi/internal/native/Mac/$it/**/*")
        exclude("org/fusesource/jansi/internal/native/Windows/$it/**/*")
    }
}


================================================
FILE: buildSrc/src/main/kotlin/sorapointa-publish.gradle.kts
================================================
plugins {
    `java-library`
    `maven-publish`
    signing
}

java {
    withJavadocJar()
    withSourcesJar()
}

val secretPropsFile: File = project.rootProject.file("local.properties")
if (secretPropsFile.exists()) {
    val props = getRootProjectLocalProps()
    props.forEach { t, u -> ext[t] = u }
} else {
    ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
    ext["signing.password"] = System.getenv("SIGNING_PASSWORD")
    ext["signing.secretKeyRingFile"] = System.getenv("SIGNING_SECRET_KEY_RING_FILE")
    ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME")
    ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD")
}

publishing {
    publications {
        create<MavenPublication>("mavenKotlin") {
            artifactId = project.name
            from(components["java"])
            versionMapping {
                usage("java-api") {
                    fromResolutionOf("runtimeClasspath")
                }
                usage("java-runtime") {
                    fromResolutionResult()
                }
            }

            pom {
                name.set("Sorapointa")
                description.set("A server software implementation for a certain anime game, and avoid sorapointa")
                url.set("https://github.com/Sorapointa/Sorapointa")

                licenses {
                    license {
                        name.set("Apache License, Version 2.0")
                        url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
                    }
                }

                developers {
                    developer {
                        id.set("WetABQ")
                    }
                    developer {
                        id.set("Colerar")
                    }
                }

                scm {
                    connection.set("scm:git:git://github.com/Sorapointa/Sorapointa.git")
                    developerConnection.set("scm:git:ssh://github.com/Sorapointa/Sorapointa.git")
                    url.set("https://github.com/Sorapointa/Sorapointa")
                }
            }
        }

        repositories {
            maven {
                name = "sonatype"
                val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
                val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
                val url = if (version.toString().contains("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl
                setUrl(url)
                credentials {
                    username = getExtraString("ossrhUsername")
                    password = getExtraString("ossrhPassword")
                }
            }
        }
    }
}

signing {
    sign(publishing.publications["mavenKotlin"])
}

tasks.javadoc {
    exclude("org.sorapointa.proto")

    if (JavaVersion.current().isJava9Compatible) {
        (options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
    }
}


================================================
FILE: docs/CONTRIBUTING.md
================================================
# Contributing Guideline

[简体中文](CONTRIBUTING.zh-CN.md)

## Code Style

- [Kotlin Official Code Style](https://kotlinlang.org/docs/coding-conventions.html)
- Indent is 4 spaces, the `.editorconfig` in our project will help your IDE to automatically set it
- Star import is allowed
- We require **all PRs to pass the `ktlint` check** before they merge into the active branch
- We recommended you to format your code using `Ktlint` before committing.
- You can run Gradle task `spotlessCheck` via `./gradlew spotlessCheck` command.
- About Rust code style, see: [sorapointa-native/README.md](../sorapointa-native/README.md)

## Git

### Branch

- **Active branch** refers to the `dev`, the development branch
- All commits or changes that want to merge into the **active branch**
  will be required to submit PR and pass all CI check.

### Push

- If you want to submit your commits or changes into **active branch**,
  you **must submit those through PR** and **pass all CI check**.

### Merge Branch

- Please **don't** pull any upstream updates when you open a new branch (or fork),
  which are used for submitting your changes or commits,
  except those updates are required for your changes or commits.
  Even though, you need to update your branch following [this rule](#incompatible-changes-and-sync-upstream-updates)
- You must turn on the `rebase` option to pull upstream updates
  - It shouldn't occur any conflicts, except you had pulled upstream updates after your committed your own code.
- Merge PR with different methods determined by different situations
  - If the PR contains big changes, we often merge it into active branch with `merge` or `squash`
  - If the PR contains small changes, we often merge it into active branch with `rebase`

### Incompatible Changes and Sync Upstream Updates

- Please **don't** pull any upstream updates when you open a new branch (or fork),
  but if these updates are necessary for you, please follow this process.
  - Create a new branch `xxx-update` from the current latest upstream branch to the local
  - Merge your commits into the `xxx-update` branch
    through `rebase`(if there are no conflicts) or `cherrypick`
  - Resolve all conflicts and fix all compatibility errors
  - Submit PR to make `xxx-update` merge into active branch
- When you have made any incompatibility changes,
  please follow the same process as above and make a new branch, like `xxx-premerge`,
  with all incompatibility issues fixed
  - Note: If there are other PRs or branches that are also affected by your incompatible update,
    set the merge target of the other PRs to `xxx-premerge`, which is equivalent to a staging branch
  - When all affected PRs or branches have been merged into `xxx-premerge`
    and all compatibility issues have been fixed,
    submit a PR to make it merge into the active branch

### Commit

- Write commit messages in English
- Short message template: `type(scope): message`. For example, `feat(network): impl KCP protocol`
  - For `type` field, abbreviated and qualified name are both acceptable.
- Use `#issue_number` to mention related issue for easy tracking

You can try [IDEA Conventional Commits](https://plugins.jetbrains.com/plugin/13389-conventional-commit)
plugin for smart completion.

[![](https://user-images.githubusercontent.com/62297254/196744218-e6bad849-5307-4761-a8b6-baa147c1852b.png)](https://plugins.jetbrains.com/plugin/13389-conventional-commit)

For reference: [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)

## More Documentations

- [Unit Test](guides/unit-test.md)
  - Please write unit tests to ensure the reliability of the code. 
  - All commits merged into the master branch must pass unit tests.
- Concurrent Safety
  - [Kotlin AtomicFU Guideline](guides/kotlin-atomicfu.md)
  - [Concurrent Safety](guides/concurrency.md)
- [Database Operation Safety](guides/database.md)
- Others: [guides](./guides)


================================================
FILE: docs/CONTRIBUTING.zh-CN.md
================================================
# 贡献指南

[English](CONTRIBUTING.zh-CN.md)

## 代码风格

- Kotlin 官方代码风格 - [中文站参考](https://book.kotlincn.net/text/coding-conventions.html)
- 缩进 4 空格,项目中的 `.editorconfig` 配置会帮助您的 IDE 自动设置
- 可以使用 `*` 导入
- 我们会要求**所有并入活跃分支**的 PR 通过 `ktlint` 检查
- 可使用命令 `./gradlew spotlessCheck` 运行 Gradle 任务
- 关于 Rust 代码风格,参见:[sorapointa-native/README.md](../sorapointa-native/README.zh-CN.md)

## Git 规范

### 关于分支

- 一般而言**活跃分支**指的是 `dev` 开发分支
- 在**活跃分支**上进行修改必须通过其他分支提交 PR 并通过所有的 CI 检查

### Push 规范

- 若您希望在**活跃分支**上提交您的修改,则**必须通过其他分支**提交 PR 并**通过所有的 CI 检查**

可参考:[Git 使用规范流程](https://www.ruanyifeng.com/blog/2015/08/git-use-process.html)

### 关于合并

- 在当您开启了一个新分支(或者 `fork`),用于提交您的代码修改时,请在 `fork` 后**不要拉取任何上游的更新**,
  除非这些更新对于你的开发是必须的,如果是必须的,请按照 [关于不兼容性修改与同步上游更新](#关于不兼容性修改与同步上游更新)
- 从远程拉取到本地时,必须使用 `rebase`
  - 不应该存在冲突,若存在冲突一般情况下是您在提交修改的同时拉取了上游的更新
- 将 PR 合并时,应该酌情使用不同的合并方式
  - 如大修改,一般考虑 `merge` 或者 `squash`
  - 小修改一般考虑 `rebase`

### 关于不兼容性修改与同步上游更新

- 一般而言,最好不要在 `fork` 后拉取任何上游的更新,但是若这些更新对您来说是必须地请按照以下流程进行更新:
  - 从当前最新的上游分支中新建分支 `xxx-update` 到本地
  - 将您已经在其他分支提交的更改 `rebase`(若无冲突) 或者 `cherrypick` 进入 `xxx-update` 分支
  - 解决所有冲突,并修正所有的兼容性错误
  - 将 `xxx-update` 提交 PR 并入活跃分支
- 当您进行了任何的不兼容性修改,请同样按照上述流程,将修复了所有不兼容性问题的分支作为新分支,如 `xxx-premerge`
  - 注意,若此时同时存在其他 PR 或者分支,并同样受到您的不兼容性更新影响,
    请将其他 PR 的合并目标设置为 `xxx-premerge` 也就是相当于一个暂存分支
  - 当所有受到影响 PR 或者分支,都合并进入了 `xxx-premerge` 并解决了所有兼容性问题后,提交 PR 申请并入活跃分支

### Commit 规范

- `commit` 中的信息请使用英语
- 短消息格式满足:`类型(作用域): 消息`,例如 `feat(network): impl KCP protocol`
  - `类型` 字段无论用缩写或全称都可行。
- 用 `#issue 编号` 提及相关的 issue,便于跟踪

可以使用 [IDEA Conventional Commits](https://plugins.jetbrains.com/plugin/13389-conventional-commit)
插件智能补全:

[![](https://user-images.githubusercontent.com/62297254/196744218-e6bad849-5307-4761-a8b6-baa147c1852b.png)](https://plugins.jetbrains.com/plugin/13389-conventional-commit)

另可参考:

- [约定式提交 v1.0.0](https://www.conventionalcommits.org/zh-hans/v1.0.0/)
- [Commit Message 和 Change Log 编写指南](https://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html)

## 更多文档

- [关于单元测试](guides/unit-test.zh-CN.md)
  - 尽量写单测保证代码可靠性。
  - 与此同时,如果你提交的 PR 无法通过 CI 中的单元测试,将无法并入活跃分支
- 关于并发安全
  - [AtomicFU 指南](guides/kotlin-atomicfu.zh-CN.md)
  - [关于并发安全](guides/concurrency.zh-CN.md)
- [关于数据库操作安全](guides/database.zh-CN.md)
- 其他: 请参见 [guides](./guides)


================================================
FILE: docs/README.md
================================================
<!--Logo-->

![Sorapointa Logo](https://socialify.git.ci/Sorapointa/Sorapointa/image?description=1&descriptionEditable=A%20server%20software%20re-implementation%20for%20a%20certain%20anime%20game%2C%20and%20avoid%20sorapointa&font=Bitter&forks=1&issues=1&logo=https%3A%2F%2Fuser-images.githubusercontent.com%2F62297254%2F171603732-a594e3e0-6968-485f-bb50-344ac7b3a57d.png&name=1&owner=1&pattern=Signal&pulls=1&stargazers=1&theme=Light)

<!--Badges-->

<p align="center">
<a href="https://kotlinlang.org"><img 
src="https://img.shields.io/badge/kotlin-%230095D5.svg?style=for-the-badge&logo=kotlin&logoColor=white" 
alt="Kotlin"/></a><a href="https://www.rust-lang.org/"><img 
src="https://img.shields.io/badge/rust-%23704b34.svg?style=for-the-badge&logo=rust&logoColor=white" 
alt="Rust"/></a><a 
href="https://gradle.org/"><img 
src="https://img.shields.io/badge/Gradle-02303A.svg?style=for-the-badge&logo=Gradle&logoColor=white" 
alt="Gradle"/></a><a 
href="https://www.jetbrains.com/idea/"><img 
src="https://img.shields.io/badge/IDEA-000000.svg?style=for-the-badge&logo=intellij-idea&logoColor=white" 
alt="IntelliJ IDEA"/></a>
</p>

<p align="center">
<a 
href="https://www.apache.org/licenses/LICENSE-2.0"><img 
src="https://img.shields.io/badge/License-Apache2.0-lightgreen?style=for-the-badge&logo=opensourceinitiative&logoColor=white" 
alt="Apache 2.0 Open Source License"/></a><a 
href="https://s01.oss.sonatype.org/content/repositories/snapshots/moe/sdl/sorapointa/"><img 
src="https://img.shields.io/nexus/s/moe.sdl.sorapointa/sorapointa-core?logo=apache-maven&label=Maven%20Dev&server=https%3A%2F%2Fs01.oss.sonatype.org&style=for-the-badge" 
alt="Maven Developer"/></a>

<div align="center"><a href="https://discord.gg/MRadGNhqce"><img alt="Discord - Sorapointa" src="https://img.shields.io/discord/976764233029140550?label=Discord&logo=discord&style=for-the-badge"></a></div>

<!--Content-->

English | [简体中文](README.zh-CN.md)

**WIP**: This project is under active development, you can take a part in contributing, but most of the features is
unavailable.

## Name

As you see, our project name is **Sorapointa**. This name was inspired from Java well-known `NullPointerException`.
We translated `NullPointer` to Japanese and phoneticized it in English.

Sorapointa is aim to reduce runtime-error, write readable, easy-to-maintain code. So, **Sorapointa avoid Sorapointa**.

Sorapointa can be written as the Chinese equivalent of "空想家", but in any case, please read it as <ruby>Sorapointa<rt>
ソラポインタ</rt></ruby>

## Build

Requirement:

- JDK 17
- Rust Toolchains, see:[sorapointa-native/README.md](../sorapointa-native/README.md)

```shell
./gradlew shadowJar
# if you want to run test
./gradlew test
```

### Available Build Configs

Create `local.properties` in project root and edit it. Config uses Java `.properties` format.

| key                    | description                     | available value        |
|------------------------|---------------------------------|------------------------|
| `database.default`     | default database for new config | `SQLITE`, `POSTGRESQL` |
| `database.driver.list` | database drivers to build       | `SQLITE`, `POSTGRESQL` |

Example:

```properties
database.default=SQLITE
database.driver.list=SQLITE,POSTGRESQL
```

## Thanks

### Person

- [HolographicHat](https://github.com/HolographicHat) - Supports a lot on algorithms and computer security.

### Project

- [JVM](https://openjdk.org/) - The best programming language VM
- [Kotlin](https://github.com/JetBrains/kotlin) - A modern programming language that makes developers happier.
- [Rust](https://github.com/rust-lang/rust) - A language empowering everyone to build reliable and efficient software.
- [IDEA](https://www.jetbrains.com/idea/) - Capable and Ergonomic IDE for JVM
- [Grasscutter](https://github.com/Grasscutters/Grasscutter)
- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) - Kotlin multiplatform / multi-format
  **reflectionless** serialization
- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - A rich library for coroutines developed by
  JetBrains
- [kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) - The idiomatic way to use atomic operations in Kotlin
- [Ktor](https://github.com/ktorio/ktor) - An asynchronous framework for creating microservices, web applications and
  more.
- [Netty](https://netty.io/) - Netty is an asynchronous event-driven network application framework
- [Exposed](https://github.com/JetBrains/Exposed) - Kotlin SQL Framework
- [Clikt](https://github.com/ajalt/clikt/tree/master/clikt) - Multiplatform command line interface parsing for Kotlin
- [Kotlin-Logging](https://github.com/MicroUtils/kotlin-logging) - Lightweight logging framework for Kotlin
- [Password4j](https://github.com/Password4j/password4j) - Password4j is a user-friendly cryptographic library that
  supports Argon2 and so on
- [JLine](https://github.com/jline/jline3) - JLine is a Java library for handling console input.
- [kaml](https://github.com/charleskorn/kaml) - YAML support for kotlinx.serialization
- [Protobuf](https://developers.google.com/protocol-buffers) - Protocol buffers are a language-neutral, platform-neutral
  extensible mechanism for serializing structured data


================================================
FILE: docs/README.zh-CN.md
================================================
<!--Logo-->

![Sorapointa Logo](https://socialify.git.ci/Sorapointa/Sorapointa/image?description=1&descriptionEditable=A%20server%20software%20re-implementation%20for%20a%20certain%20anime%20game%2C%20and%20avoid%20sorapointa&font=Bitter&forks=1&issues=1&logo=https%3A%2F%2Fuser-images.githubusercontent.com%2F62297254%2F171603732-a594e3e0-6968-485f-bb50-344ac7b3a57d.png&name=1&owner=1&pattern=Signal&pulls=1&stargazers=1&theme=Light)

<!--Badges-->

<p align="center">
<a href="https://kotlinlang.org"><img 
src="https://img.shields.io/badge/kotlin-%230095D5.svg?style=for-the-badge&logo=kotlin&logoColor=white" 
alt="Kotlin"/></a><a href="https://www.rust-lang.org/zh-CN/"><img 
src="https://img.shields.io/badge/rust-%23704b34.svg?style=for-the-badge&logo=rust&logoColor=white" 
alt="Rust"/></a><a 
href="https://gradle.org/"><img 
src="https://img.shields.io/badge/Gradle-02303A.svg?style=for-the-badge&logo=Gradle&logoColor=white" 
alt="Gradle"/></a><a 
href="https://www.jetbrains.com/idea/"><img 
src="https://img.shields.io/badge/IDEA-000000.svg?style=for-the-badge&logo=intellij-idea&logoColor=white" 
alt="IntelliJ IDEA"/></a>
</p>

<p align="center">
<a 
href="https://www.apache.org/licenses/LICENSE-2.0"><img 
src="https://img.shields.io/badge/License-Apache2.0-lightgreen?style=for-the-badge&logo=opensourceinitiative&logoColor=white" 
alt="Apache 2.0 Open Source License"/></a><a 
href="https://s01.oss.sonatype.org/content/repositories/snapshots/moe/sdl/sorapointa/"><img 
src="https://img.shields.io/nexus/s/moe.sdl.sorapointa/sorapointa-core?logo=apache-maven&label=Maven%20Dev&server=https%3A%2F%2Fs01.oss.sonatype.org&style=for-the-badge" 
alt="Maven Developer"/></a>
</p>

<!--Content-->

[English](README.md) | 简体中文

**WIP**: 该项目正被积极开发,你可以参与贡献,但大多数功能不可用。

## 名称

如你所见,我们的项目名为 **Sorapointa**。这来自于 `Java` 中闻名的 `NullPointerException`。
我们将 `NullPointer` 日语化再以英语拟音,得到了这个名字。

Sorapointa 旨在减少运行时错误,编写可读性高、易于维护的代码。因此,**Sorapointa 避免 Sorapointa**。

Sorapointa 可以写成对应的汉字「空想家」,但无论如何,请你读作 <ruby>Sorapointa<rt>ソラポインタ</rt></ruby>

## Build

需要:

- JDK 17
- Rust 工具链,详情参见:[sorapointa-native/README.md](../sorapointa-native/README.zh-CN.md)

```shell
./gradlew shadowJar
# 如果你想运行测试
./gradlew test
```

### 可用的构建选项

在项目根目录创建 `local.properties` 并编辑。配置使用 Java `.properties` 格式。

| key                    | 描述         | 可用值                    |
|------------------------|------------|------------------------|
| `database.default`     | 默认配置使用的数据库 | `SQLITE`, `POSTGRESQL` |
| `database.driver.list` | 编译时打包哪些数据库 | `SQLITE`, `POSTGRESQL` |

示例:

```properties
database.default=SQLITE
database.driver.list=SQLITE,POSTGRESQL
```

## Contributing

参见:[CONTRIBUTING](CONTRIBUTING.zh-CN.md),以及对应模块的 README。

## Thanks

### Person

- [HolographicHat](https://github.com/HolographicHat) - Supports a lot on algorithms and computer security.

### Project

- [JVM](https://openjdk.org/) - The best programming language VM
- [Kotlin](https://github.com/JetBrains/kotlin) - A modern programming language that makes developers happier.
- [Rust](https://github.com/rust-lang/rust) - A language empowering everyone to build reliable and efficient software.
- [IDEA](https://www.jetbrains.com/idea/) - Capable and Ergonomic IDE for JVM
- [Grasscutter](https://github.com/Grasscutters/Grasscutter)
- [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) - Kotlin multiplatform / multi-format
  **reflectionless** serialization
- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) - A rich library for coroutines developed by
  JetBrains
- [kotlinx.atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) - The idiomatic way to use atomic operations in Kotlin
- [Ktor](https://github.com/ktorio/ktor) - An asynchronous framework for creating microservices, web applications and
  more.
- [Netty](https://netty.io/) - Netty is an asynchronous event-driven network application framework
- [Exposed](https://github.com/JetBrains/Exposed) - Kotlin SQL Framework
- [Clikt](https://github.com/ajalt/clikt/tree/master/clikt) - Multiplatform command line interface parsing for Kotlin
- [Kotlin-Logging](https://github.com/MicroUtils/kotlin-logging) - Lightweight logging framework for Kotlin
- [Password4j](https://github.com/Password4j/password4j) - Password4j is a user-friendly cryptographic library that
  supports Argon2 and so on
- [JLine](https://github.com/jline/jline3) - JLine is a Java library for handling console input.
- [kaml](https://github.com/charleskorn/kaml) - YAML support for kotlinx.serialization
- [Protobuf](https://developers.google.com/protocol-buffers) - Protocol buffers are a language-neutral, platform-neutral
  extensible mechanism for serializing structured data


================================================
FILE: docs/guides/concurrency.md
================================================
# Concurrency Safety

The Sorapointa project has applied a lot of coroutine and multi-thread techniques, 
so please to be very careful with thread-safe and 
the code performance in the multi-thread and high volume situation.

## Shared Mutable State

During development, it is best to expose **unmodifiable** variables and collections 
such as `val`, `List`, `Map` to avoid thread safety issues caused by sharing mutable data

If you must share mutable data, 
use a thread-safe data structure such as Atomic, 
Sorapointa has already used the AtomicFU framework.

About AtomicFU,please refers to [AtomicFU Guideline](kotlin-atomicfu.md)

But for collections, 
we usually use `ConcurrentHashMap` or some similar thread-safe data structures

However, using thread-safe data structures does not guarantee are safe; 
atomicity is not magic or comes from void, it needs to be carefully maintained

For example, for `ConcurrentHashMap`, 
you must use the provided methods such as `get`, `put`, `getOrPut` to ensure thread safety.
A thread-safe data structure can only guarantee the atomicity of the methods it provides.

So, when using thread-safe data structures, combining their atomic operations 
can lead to a loss of atomicity and to be thread-unsafe, for example

```kotlin
val map = ConcurrentHashMap<Int, String>()
if (!map.containsKey(123)) {
    map.put(123,"foobar")
}
```

This is thread-unsafe because when multiple threads use this method at the same time,
they will both arrive at `containsKey` and both find there is not `123` 
and it will repeatedly execute following code

So, that's why you must use the methods `getOrPut()` or `putIfCompute()` 
provided by `ConcurrentHashMap` to ensure thread safety.

Refers to [Shared Mutable State](https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html)

## Thread Safety Review

For every object, if any properties is variable
and will be access by multiple threads, you have to use Atomic.
You cannot leak the atomic reference to public,
and must use the provided update functions.

If there are any methods in the object that use Atomic objects,
ensure that every operation accesses it
is completely independent (no branches and local variables).
If there is a dependent operation,
you must ensure the atomicity of that dependent operation.
(As mentioned above, atomic methods cannot be combined,
or locks can be used to ensure atomicity)

If this object itself already guarantees atomicity for all operations,
it is necessary ensure that the operations of the object is atomic.
(and the outer methods must not combine atomic methods, and so on).

## Requirements for Thread Safety

Fix as many thread-safety issues as possible that can be fixed easily,
such as using the atomic delegation, ConcurrentHashMap
and other built-in atomic objects.
If all built-in atomic methods are no longer enough for your needs,
try using the simple Mutex.
(Please follow the guideline for properly using the Mutex)

But before using Mutex or more complex thread-safe mechanism,
first think about whether I can accept the risk of problems occurring
(e.g., the [Primogem](https://genshin-impact.fandom.com/wiki/Primogem)-related operation is very sensitive to
But repeatedly rewriting insertDefault
or repeatedly starting some coroutines because of high concurrency.
This cost is affordable.)

Acceptable cost means that
the resulting data changes are acceptable,
the resulting performance loss is acceptable,
and the program will not crash with errors.

## Lock

In Kotlin Coroutine, some types of lock (such as Mutex) that is thread-bind,
would easily cause the deadlock issue.

We highly recommend you to use `withReentrantLock` method located in `sorapointa-utils` module,
to keep the mutex lock consistency in the coroutine context.

Please refer to [Phantom of the Coroutine](https://elizarov.medium.com/phantom-of-the-coroutine-afc63b03a131)


## Structured Concurrency

The code is cooperative and use structured concurrency to
ensure that all concurrent processes do not leak and are manageable.

In Sorapointa, we use ModuleScope to
ensure that the task structure between concurrent processes is proper.

In general,please don't implement `CoroutineScope` interface to make coroutine to be structured concurrency,
and also don't add context in the parameter of `launch` method.
For specific reasons, please refer to, [Kotlin CoroutineScope Documentation](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/),
[Why your class probably should not implement CoroutineScope](https://proandroiddev.com/why-your-class-probably-shouldnt-implement-coroutinescope-eb34f722e510),
[Structured Concurrency Anniversary](https://elizarov.medium.com/structured-concurrency-anniversary-f2cc748b2401),
[Legacy Convention of CoroutineScope](https://maxkim.eu/things-every-kotlin-developer-should-know-about-coroutines-part-2-coroutinescope)

In short, it's an outdated approach.

You can write cooperative code by referring to TaskManager, EventManager, etc.


================================================
FILE: docs/guides/concurrency.zh-CN.md
================================================
# 关于并发安全

Sorapointa 项目大量使用了协程与多线程技术,请时刻注意检查代码在多线程以及高并发环境下的运行状况

## 共享可修改数据

在开发期间,最好暴露不可修改的变量和集合如 `val`, `List`, `Map`,以避免共享可修改数据造成的线程安全问题

如果一定要共享可修改数据,请使用线程安全的数据结构 ,如 Atomic,Sorapointa 使用了 AtomicFU 框架封装了 Java 的原子方法

关于 AtomicFU,参考 [AtomicFU 指南](/docs/guides/kotlin-atomicfu.zh-CN.md)

但是对于集合,我们一般使用 `ConcurrentHashMap` 或同样类似的线程安全的数据结构

但使用线程安全的数据结构,并不代表一定安全,原子性并不凭空产生,需要小心维护

比如对于 `ConcurrentHashMap`,必须使用其自身提供的方法如 `get`,`put` 才能保证线程安全,
线程安全的数据结构也只能保证它提供的方法的线程安全和原子性。

在使用线程安全的数据结构时,组合其原子操作会导致原子性的丢失,比如

```kotlin
val map = ConcurrentHashMap<Int, String>()
if (!map.containsKey(123)) {
    map.put(123,"foobar")
}
```

就是线程不安全的,因为当多线程同时使用这个方法时,
他们会同时抵达 `containsKey` 并都发现没有 `123`,并重复执行了下面的代码

你必须使用 `ConcurrentHashMap` 提供的方法 `getOrPut()` 或 `putIfCompute()` 以确保线程安全

了解更多,关于 [线程间共享可修改数据](https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html)


## 线程安全分析

对于每个对象,如果对象中的任何成员变量是可修改的,且存在被多线程访问的情况,就需要使用 Atomic ,
并且不可以外泄 Atomic 的赋值权,必须使用 Atomic 自带的更新方法。

如果对象中的任何方法中存在使用 Atomic 对象,保证每次访问 Atomic 对象的操作是完全独立的(不产生分支和引用),
如果是存在依赖性操作,必须保证该依赖性操作的原子性。(也就是上文提到的,不可组合原子方法,或使用锁保证原子性)

如果这个对象本身已经保证所有操作的原子性,就需要保证调用这个对象的对象操作的原子性(外层也不可组合原子方法,以此类推)。

## 对于线程安全的要求

对能简单修复的线程安全问题尽量予以修复,比如 使用 `atomic` 代理,
使用 `ConcurrentHashMap` 以及其内置的其他原子方法,
如果内置的所有原子方法已经不足以满足你的需求,可以尝试使用简单的 `Mutex`
(请遵循指导在协程下正确使用 `Mutex`)

但是在使用 `Mutex` 或更复杂的线程安全机制前, 
首先思考,我是否能接受发生问题的风险(如原石操作是很敏感的,
但是重复复写入 `insertDefault` 或者是因为高并发重复启动一些协程,
这个成本是可以承受的)

成本可接受指的是,造成的数据变更可接受,造成的性能损耗可接受,程序不会报错崩溃

## 关于锁

在 Kotlin 协程中,与线程绑定的锁会容易造成死锁问题(比如 `Mutex`),
建议使用 `sorapointa-utils` 模块中拓展的 `withReentrantLock` 方法,以确保锁的上下文一致性。

可以参考,[Phantom of the Coroutine](https://elizarov.medium.com/phantom-of-the-coroutine-afc63b03a131)

## 结构化并发

代码应该是合作式的,并使用结构化并发确保所有的协程不会泄漏与可被管理

在 Sorapointa 中,我们使用了 `ModuleScope` 以确保正确建立协程之间的任务结构

你可以参照 `TaskManager`,`EventManager` 等,写出合作式的代码

通常情况下,请不要实现 `CoroutineScope` 接口以实现结构化并发,也不要往 `launch` 方法中添加上下文。
具体原因可以参考,[Kotlin CoroutineScope 文档](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/),
[为什么你不应该实现 CoroutineScope 接口](https://proandroiddev.com/why-your-class-probably-shouldnt-implement-coroutinescope-eb34f722e510),
[结构化并发周年庆 - Roman Elizarov](https://elizarov.medium.com/structured-concurrency-anniversary-f2cc748b2401),
[CoroutineScope 的 Legacy Convention](https://maxkim.eu/things-every-kotlin-developer-should-know-about-coroutines-part-2-coroutinescope)

简而言之,这是一种过时了的方法。

了解更多,关于 [结构化并发](https://kotlinlang.org/docs/composing-suspending-functions.html#structured-concurrency-with-async)


================================================
FILE: docs/guides/database.md
================================================
# Database Operation Safety

## Transaction

Nested use of `transaction` is prohibited when it is not required, 
which would result in sessions started at `READ_COMMITTED` and above isolation levels 
not being able to read any changes within the nested transaction, 
resulting in various unintended and unpredictable outcomes, 
so transactions cannot be created within API methods, 
and transactions must be guaranteed to be invoked 
by the outermost code, not by the api layer.

Any error during the execution of the transaction 
will cause the entire operation of the transaction to be rolled back.

### Transaction Isolation

The reason for the above requirement is that the Repeatable Read isolation level 
only sees data committed before the transaction began; 
it never sees either uncommitted data or changes committed during transaction execution by concurrent transactions. 
(However, the query does see the effects of previous updates executed within its own transaction, 
even though they are not yet committed.)

This level is different from Read Committed in that a query in a repeatable read transaction 
sees a snapshot as of the start of the first non-transaction-control statement in the transaction, 
not as of the start of the current statement within the transaction. 
Thus, successive `SELECT` commands within a single transaction see the same data, 
i.e., they do not see changes made by other transactions 
that committed after their own transaction started.

So if you use nested transactions, 
it will make outer transaction could not see updates from the inner transaction, 
and further cause unintended results.

Refers to [PostgreSQL Manual - Transaction Isolation](https://www.postgresql.org/docs/current/transaction-iso.html)

### Asynchronous Transactions

Asynchronous transactions are allowed, 
but it is not allowed to switch threads / coroutine in the same transaction 
to operate on the database. 
This will cause deadlocks during high concurrency. 
All database operations within the same transaction must be 
executed synchronously under the same thread.

## Table Structure

Once the table structure is defined, 
it is best not to make any changes (including deleting fields and modifying field properties).
Because it will require user to manually sync new table structure, 
only adding fields can be updated automatically.

## API Method

Any database operations API provided to the upper layer should not include any transaction block.
It will cause nested transactions and raises the issues mentioned above due to transaction isolation.


================================================
FILE: docs/guides/database.zh-CN.md
================================================
# 数据库操作安全

## 事务

在非必须时禁止使用嵌套使用 `transaction`,这会导致 `READ_COMMITTED` 
及以上的事物隔离级别启动的会话**无法读取到嵌套事务内的任何变更**,
以造成各种不符合预期和难以预测的结果,
所以**不能在 API 方法内创建事务**,
事务**必须保证是被最外层应用端调用**,而非底层。

事务执行过程中发生任何错误都会使得整个事务的操作被回滚。

### 事务隔离

对于上述要求的原因是,`READ_COMMITTED` 隔离级别在 `PostgreSQL` 中,
在 `READ_COMMITTED` 事务中的查询看到的是事务开始时的快照,
而不是该事务内部当前查询开始时的快照,这样,单个事务内部的 `SELECT` 命令总是看到同样的数据,
也就是说,它们看不到它们自身事务开始之后提交的其他事务所做出的改变。

所以如果使用嵌套事务,将会使得外层事务无法看到内层事务的更新,并进一步造成不符合预期的结果。

请参考 [PostgreSQL 手册 - 事务隔离](http://www.postgres.cn/docs/9.3/transaction-iso.html)

### 事务的异步

在任何一个事务中都禁止使用任何异步方法来操作数据库,
异步在事务是允许的,但是不允许在同一事务中切线程 / 协程操作数据库,
这会在高并发时造成死锁,在同一事务内所有数据库操作要求必须在同一线程下同步执行。

## 表结构安全

表结构确定了之后最好不要进行任何修改(包括删除字段,修改字段的属性),
因为这需要用户手动同步,只有增加字段的操作可以被自动更新。

## API 安全

任何提供给上层的封装后的数据库操作都**不要自己启动事务**,
这会造成事务的嵌套,并引发在上面提到由于事务隔离所产生的问题。


================================================
FILE: docs/guides/kotlin-atomicfu.md
================================================
# Kotlin AtomicFU Guideline

[简体中文](kotlin-atomicfu.zh-CN.md)

## Setup

```kotlin
dependencies {
  implementation("org.jetbrains.kotlinx:atomicfu:_")
}
```

## Usage

```kotlin
val atomicInt = atomic(123)
atomicInt.value // read
atomicInt.value = 1234 // write
```

Must ensure all operations are atomic. You can only operate value with provided methods.

If you only need read and write operation, use kotlin delegation:

```kotlin
var delegatedAtomic by atomic(1231)
delegatedAtomic // read
delegatedAtomic = 123 // write
```

**BUT, please use the methods `atomicfu` provided only. Atomicity requires careful maintenance. The snippet below
is completely wrong:**

**Multiple atomic methods cannot be combined, combining atomic methods will lose atomicity**

```kotlin
var delegatedAtomic by atomic(123)

if (delegatedAtomic == 1) delegatedAtomic = 1000
```

Should be:

```kotlin
val atomicInt = atomic(123)
atomicInt.compareAndSet(expect = 1, update = 1000)
// or... using high-order function
atomicInt.getAndUpdate { if (it == 1) 1000 else it }
```

Idiomatic lock-free methods:

```kotlin
fun push(v: Value) = top.update { cur -> Node(v, cur) }
fun pop(): Value? = top.getAndUpdate { cur -> cur?.next }?.value
```

Int and Long atomics provide all the usual `getAndIncrement`, `incrementAndGet`, `getAndAdd`, `addAndGet`, etc. They can
be also atomically modified via `+=` and `-=` operators.

## Notice

### Avoid using unprovided API

Notice again, atomicity needs careful maintenance. You must use provided API.

- Do not read references on atomic variables into local variables
- Do not introduce complex data flow in parameters to atomic variable operations, please use function
  like `atomicValue.update` instead

### Hide internal implementation

As you can see, it's hard to maintenance thread-safe, so do not leak reference to other modules.

Use the following convention if you need to expose the value of atomic property to the public:

```kotlin
private val _foo = atomic(100) // private atomic, starts with underscore
public var foo: Int by _foo    // public delegated property (val/var)
```


================================================
FILE: docs/guides/kotlin-atomicfu.zh-CN.md
================================================
# Kotlin AtomicFU 使用指南

[English](kotlin-atomicfu.md)

## Setup

```kotlin
dependencies {
  implementation("org.jetbrains.kotlinx:atomicfu:_")
}
```

## 用法

```kotlin
val atomicInt = atomic(123)
atomicInt.value // 读
atomicInt.value = 1234 // 写
```

必须确保所有的操作都是原子的。亦即只能通过 `atomicfu` 已经提供的方法操作。

如果只需要读/写操作,可以使用代理:

```kotlin
var delegatedAtomic by atomic(1231)
delegatedAtomic // 读
delegatedAtomic = 123 // 写
```

**但切记只能通过 `atomicfu` 已经提供的方法操作,原子性需要小心维护,以下的写法是完全错误的:**

**多个原子方法不可组合使用,组合原子方法会失去原子性**

```kotlin
var delegatedAtomic by atomic(123)

if (delegatedAtomic == 1) delegatedAtomic = 1000
```

正确的写法:

```kotlin
val atomicInt = atomic(123)
atomicInt.compareAndSet(expect = 1, update = 1000)
// 或... 使用高阶函数
atomicInt.getAndUpdate { if (it == 1) 1000 else it }
```

无需加锁的函数式写法:

```kotlin
fun push(v: Value) = top.update { cur -> Node(v, cur) }
fun pop(): Value? = top.getAndUpdate { cur -> cur?.next }?.value
```

`Int` 和 `Long` 也有 `getAndIncrement`, `incrementAndGet`, `getAndAdd`, `addAndGet` 方法,以及对 `+=` `-=` 的操作符重载。

## 注意事项

### 避免使用未提供的操作

再次强调,原子性并不凭空产生。你需要使用已经提供的原子性 API。

- 不要使用局部变量存储 atomic 变量的引用
- 不要使用复杂的表达式,任何有分支的语句都会造成问题,需要时请使用诸如 `atomicValue.update` 的方法。

### 隐藏内部实现

如你所见,维护线程安全并不容易,所以不要向外泄漏引用。

需要向外公开API时,请像这样:

```kotlin
private val _foo = atomic(100) // 内部原子变量,以 _ 开头
public var foo: Int by _foo    // 公开代理属性 (val/var)
```


================================================
FILE: docs/guides/unit-test.md
================================================
# JUnit Guideline

[简体中文](unit-test.zh-CN.md)

[JUnit Official User Guide](https://junit.org/junit5/docs/current/user-guide/)

## Basic Usage

You can create unit test in `test` source set,
the package structure should keep the same as the `main` source set.
For example, you're going to write unit test for `org.sorapointa.db.Account`,
you can create class `org.sorapointa.db.AccountTest`:

```kotlin
// org.sorapointa.db.Account
data class Account(
  val name: String,
  val level: Int,
)

// org.sorapointa.db.AccountTest
class AccountTest {
  @Test
  fun `account must be equals`() {
    val var1 = Account(name = "foo", level = 20)
    val var2 = var1.copy()
    assertEquals(var1, var2)
  }
}
```

By annotating test function with `@Test`, JUnit and IDEA can recognize it.

You may have noticed the test name is surrounded by backticks.
It's for readability, you can use natural language in test name.
(By the way, under_score_style is popular in Java and Android test.)
See [Kotlin documentation](https://kotlinlang.org/docs/coding-conventions.html#names-for-test-methods).

Assertion is a way for test by describing the program **MUST** or **MUST NOT** do something.
If condition equals to false, assertion throw exception, then test fails.

Following method is frequently used:

- `assert(condition)`
- `assertEquals(excepted, actual)`
- `assertContentEquals(excepted, actual)` for arrays
- `assertTrue { block }`

## BeforeAll / BeforeEach

1.

```kotlin
@TestInstance(Lifecycle.PER_CLASS)
class DatabaseTest {
  @BeforeAll
  fun initDatabase() {
    // do init
  }

  @Test
  fun test1() {
    // do test...
  }
}
```

2.

```kotlin
class DatabaseTest {
  companion object {
    @BeforeAll
    @JvmStatic
    fun initDatabase() {
      // do init
    }
  }

  @Test
  fun test1() {
    // do test...
  }
}
```

Both 1 and 2 are correct. The function annotated by `@BeforeAll` will be invoked before other function.

`@BeforeEach` is similar, but the function will be invoked before each function.

## Test Dependency

If a dependency is test-only, add it in `build.gradle.kts` in this way:

```kotlin
dependencies {
  testImplementation("com.example.artifact:example:version")
  // add test dependency from subprojects:
  testImplementation(project(":sorapointa-event", "test"))
}
```

## Sorapointa Test Util

### Properties

- `TEST_DIR` is the Gradle root project directory
- `IS_CI`, is run in CI

### Functions

- `runTest` provide a suspendable block, and SKIP_OPTIONs

```kotlin
// SKIP_CI: skip test if run in CI
runTest(TestOption.SKIP_CI) {
  val atomicInt = atomic(0)
  (1..10).map {
    // suspend and CoroutineScope extension function is applicable
    launch {
      repeat(1000) {
        atomicInt.getAndIncrement()
      }
    }
  }.joinAll()
  assertEquals(10000, 10000)
}
```

### High Volume Test

- Do not run high volume test in the IDE's `Debug` mode, which will cause `OutOfMemory` and deadlocks for unknown reasons


================================================
FILE: docs/guides/unit-test.zh-CN.md
================================================
# JUnit 使用指南

[English](unit-test.md)

[JUnit 官方文档](https://junit.org/junit5/docs/current/user-guide/)

## 基本用法

你可以在 `test` 源集中创建单元测试,包结构应该和 `main` 源集保持一致。
例如,要为 `org.sorapointa.db.Account` 编写单元测试,
就可以创建 `org.sorapointa.db.AccountTest`。

```kotlin
// org.sorapointa.db.Account
data class Account(
  val name: String,
  val level: Int,
)

// org.sorapointa.db.AccountTest
class AccountTest {
  @Test
  fun `account must be equals`() {
    val var1 = Account(name = "foo", level = 20)
    val var2 = var1.copy()
    assertEquals(var1, var2)
  }
}
```

通过添加 `@Test` 注解,JUnit 和 IDEA 就可以识别出单元测试。

你或许已经注意到,测试方法名被反引号包裹。
这是为了可读性,你可以在测试名中使用自然语言。
(另外,下划线命名法在 Java 和 Android 测试中也很流行。)
参见:[Kotlin 文档](https://kotlinlang.org/docs/coding-conventions.html#names-for-test-methods).

断言通过描述**必须**(MUST)和**一定不能**(MUST NOT)做的事,以测试程序。
如果条件为 false,断言抛出异常,同时测试也失败。

以下方法经常用到:

- `assert(条件)`
- `assertEquals(预期值, 实际值)`
- `assertContentEquals(预期值, 实际值)` 用于数组
- `assertTrue { 代码块 }`

## BeforeAll / BeforeEach

1.

```kotlin
@TestInstance(Lifecycle.PER_CLASS)
class DatabaseTest {
  @BeforeAll
  fun initDatabase() {
    // 初始化
  }

  @Test
  fun test1() {
    // 测试...
  }
}
```

2.

```kotlin
class DatabaseTest {
  companion object {
    @BeforeAll
    @JvmStatic
    fun initDatabase() {
      // 初始化
    }
  }

  @Test
  fun test1() {
    // 测试...
  }
}
```

1 和 2 都是正确的。被 `@BeforeAll` 注解的方法将会在所有方法之前被调用。

`@BeforeEach` 类似,但函数在每个方法之前被调用。

## 测试依赖

如果一个依赖仅用于测试,在 `build.gradle.kts` 中这样添加:

```kotlin
dependencies {
  testImplementation("com.example.artifact:example:version")
  // 添加子模块的依赖:
  testImplementation(project(":sorapointa-event", "test"))
}
```

## Sorapointa 测试工具

### 属性

- `TEST_DIR` is the Gradle root project directory
- `IS_CI`, is run in CI

### 方法

- `runTest` 提供 suspend 块和 SKIP_OPTION

```kotlin
// SKIP_CI: 如果运行在 CI,跳过该测试
runTest(TestOption.SKIP_CI) {
  val atomicInt = atomic(0)
  (1..10).map {
    // 可以使用 suspend 函数和 CoroutineScope 的拓展方法
    launch {
      repeat(1000) {
        atomicInt.getAndIncrement()
      }
    }
  }.joinAll()
  assertEquals(10000, 10000)
}
```


### 关于高并发测试

- 请不要在 IDE 的 `Debug` 模式下运行高并发测试,这容易导致 `OutOfMemory` 和未知原因的死锁


================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
kotlin = "1.8.10"
ktor = "2.1.2"
ktlint = "0.48.1"
exposed = "0.41.1"

[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "10.3.0" }
abi-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.12.1" }
wire = { id = "com.squareup.wire", version = "4.4.3" }
rust-wrapper = { id = "fr.stardustenterprises.rust.wrapper", version = "3.2.5" }
spotless = { id = "com.diffplug.spotless", version = "6.15.0" }
licensee = { id = "app.cash.licensee", version = "1.6.0" }

[libraries]
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }

kotlinx-serialization-core = "org.jetbrains.kotlinx:kotlinx-serialization-core:1.4.1"
kotlinx-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
kaml = "com.charleskorn.kaml:kaml:0.49.0"
moshi = "com.squareup.moshi:moshi:1.14.0"
wire-moshi-adapter = "com.squareup.wire:wire-moshi-adapter:4.4.3"

kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0"

microutils-logging = "io.github.microutils:kotlin-logging-jvm:3.0.4"
logback = "ch.qos.logback:logback-classic:1.4.5"

atomicfu = "org.jetbrains.kotlinx:atomicfu:0.18.3"
netty = "io.netty:netty-handler:4.1.86.Final"
kcp = "moe.sdl.kcp:grasskcpper:2.0.0"
yac = "moe.sdl.yac:core:1.0.1"
jline = "org.jline:jline:3.22.0"
password4j = "com.password4j:password4j:1.6.3"
classgraph = "io.github.classgraph:classgraph:4.8.154"

exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
exposed-kotlin-datetime = { module = "org.jetbrains.exposed:exposed-kotlin-datetime", version.ref = "exposed" }

hikaricp = "com.zaxxer:HikariCP:5.0.1"

sqlite = "org.xerial:sqlite-jdbc:3.40.0.0"
postgresql = "org.postgresql:postgresql:42.5.1"

ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation-jvm", version.ref = "ktor" }
ktor-server-logging = { module = "io.ktor:ktor-server-call-logging-jvm", version.ref = "ktor" }
ktor-server-compression = { module = "io.ktor:ktor-server-compression-jvm", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" }
ktor-server-status-page = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
ktor-server-html = { module = "io.ktor:ktor-server-html-builder", version.ref = "ktor" }
ktor-server-wss = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }

ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }

ktor-network-cert = { module = "io.ktor:ktor-network-tls-certificates", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }

ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" }

ktor-utils = { module = "io.ktor:ktor-utils", version.ref = "ktor" }

cron-utils = "com.cronutils:cron-utils:9.2.0"
wire-runtime = "com.squareup.wire:wire-runtime:4.4.3"
okio = "com.squareup.okio:okio:3.3.0"

build-kotlinpoet = "com.squareup:kotlinpoet:1.12.0"
build-buildconfig = "com.github.gmazzo:gradle-buildconfig-plugin:3.1.0"
build-shadow = "gradle.plugin.com.github.johnrengelman:shadow:7.1.2"
build-atomicfu = "org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5"

yanl = "fr.stardustenterprises:yanl:0.8.1"
plat4k = "fr.stardustenterprises:plat4k:1.6.3"

[bundles]
log = ["microutils-logging", "logback"]


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: gradle.properties
================================================
kotlin.code.style=official
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.warning.mode=summary
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8


================================================
FILE: gradlew
================================================
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
#
#   Gradle start up script for POSIX generated by Gradle.
#
#   Important for running:
#
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
#       noncompliant, but you have some other compliant shell such as ksh or
#       bash, then to run this script, type that shell name before the whole
#       command line, like:
#
#           ksh Gradle
#
#       Busybox and similar reduced shells will NOT work, because this script
#       requires all of these POSIX shell features:
#         * functions;
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
#         * compound commands having a testable exit status, especially «case»;
#         * various built-in commands including «command», «set», and «ulimit».
#
#   Important for patching:
#
#   (2) This script targets any POSIX shell, so it avoids extensions provided
#       by Bash, Ksh, etc; in particular arrays are avoided.
#
#       The "traditional" practice of packing multiple parameters into a
#       space-separated string is a well documented source of bugs and security
#       problems, so this is (mostly) avoided, by progressively accumulating
#       options in "$@", and eventually passing that to Java.
#
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
#       see the in-line comments for details.
#
#       There are tweaks for specific operating systems such as AIX, CygWin,
#       Darwin, MinGW, and NonStop.
#
#   (3) This script is generated from the Groovy template
#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
#       within the Gradle project.
#
#       You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
    echo "$*"
} >&2

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in                #(
  CYGWIN* )         cygwin=true  ;; #(
  Darwin* )         darwin=true  ;; #(
  MSYS* | MINGW* )  msys=true    ;; #(
  NONSTOP* )        nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD=$JAVA_HOME/jre/sh/java
    else
        JAVACMD=$JAVA_HOME/bin/java
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD=java
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
    case $MAX_FD in #(
      max*)
        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
        # shellcheck disable=SC3045 
        MAX_FD=$( ulimit -H -n ) ||
            warn "Could not query maximum file descriptor limit"
    esac
    case $MAX_FD in  #(
      '' | soft) :;; #(
      *)
        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
        # shellcheck disable=SC3045 
        ulimit -n "$MAX_FD" ||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
    esac
fi

# Collect all arguments for the java command, stacking in reverse order:
#   * args from the command line
#   * the main class name
#   * -classpath
#   * -D...appname settings
#   * --module-path (only if needed)
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

    JAVACMD=$( cygpath --unix "$JAVACMD" )

    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    for arg do
        if
            case $arg in                                #(
              -*)   false ;;                            # don't mess with options #(
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                    [ -e "$t" ] ;;                      #(
              *)    false ;;
            esac
        then
            arg=$( cygpath --path --ignore --mixed "$arg" )
        fi
        # Roll the args list around exactly as many times as the number of
        # args, so each arg winds up back in the position where it started, but
        # possibly modified.
        #
        # NB: a `for` loop captures its iteration list before it begins, so
        # changing the positional parameters here affects neither the number of
        # iterations, nor the values presented in `arg`.
        shift                   # remove old arg
        set -- "$@" "$arg"      # push replacement arg
    done
fi

# Collect all arguments for the java command;
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
#     shell script including quotes and variable substitutions, so put them in
#     double quotes to make sure that they get re-expanded; and
#   * put everything else in single quotes, so that it's not re-expanded.

set -- \
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
        -classpath "$CLASSPATH" \
        org.gradle.wrapper.GradleWrapperMain \
        "$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
    die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
#   set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- $(
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
        xargs -n1 |
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
        tr '\n' ' '
    )" '"$@"'

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: renovate.json
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:base"
  ]
}


================================================
FILE: settings.gradle.kts
================================================
rootProject.name = "Sorapointa"

include("sorapointa-core")
include("sorapointa-crypto")
include("sorapointa-dataloader")
include("sorapointa-dataprovider")
include("sorapointa-dispatch")
include("sorapointa-event")
include("sorapointa-i18n")
include("sorapointa-native")
include("sorapointa-native-wrapper")
include("sorapointa-proto")
include("sorapointa-task")

include("sorapointa-utils")
include("sorapointa-utils:sorapointa-utils-all")
include("sorapointa-utils:sorapointa-utils-core")
include("sorapointa-utils:sorapointa-utils-crypto")
include("sorapointa-utils:sorapointa-utils-serialization")
include("sorapointa-utils:sorapointa-utils-time")

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
    }
}


================================================
FILE: sorapointa-core/README.md
================================================
# Core Module

[简体中文](README.zh-CN.md)

## Command System

The command system is based on [**Yac**](https://githubfast.com/Colerar/Yac)
(A variety of [**clikt**](https://ajalt.github.io/clikt/)),
usage is basically the same as **clikt**.

The main components are: `Command`, `CommandSender`, `CommandManager`

- `Command`: The command object, with `run` function and parser provided by **Yac**
- `CommandSender`: The sender who invoke the command
- `CommandManager`: A manager to register, invoke `Command` asynchronously and thread-safely.

### Create

```kotlin
class ExampleCommand(
  private val sender: CommandSender,
) : Command(sender, ExampleCommand) {
  companion object : Entry(
    name = "example",
    help = "An example command",
    alias = listOf("alias")
  )

  val option by option("--option").flag()

  override suspend fun run() {
    sender.sendMessage("Hello Command! Option: $option")
  }
}
```

There is a little trick, properties of command are store in `Entry` object,
so we can declare a companion object.
Then we can access it with class name directly.

For more information, see [Clikt Documentation](https://ajalt.github.io/clikt/).

### Register

Register a command:

```kotlin
val command = CommandNode(ExampleCommand) { sender -> ExampleCommand(sender) }
CommandManager.registerCommand(command)
```

The first parameter of `CommandNode` is `CommandEntry`, the companion object we declared before.
The second parameter is a high-order function, used to construct a command instance.

Register a list of commands:

```kotlin
val commands = listOf(
  CommandNode(ExampleCommand) { sender -> ExampleCommand(sender) },
  CommandNode(Foo) { sender -> Foo(sender) },
  CommandNode(Bar) { sender -> Bar(sender) },
  CommandNode(Help) { sender -> Help(sender) },
)
CommandManager.registerCommands(commands)
```

### Invoke

At most of the time, you do not need to invoke command manually, core will invoke for you.

But in some cases, you may want to call command manually in your code, 
or invoke command when player trigger event.

```kotlin
// just an example for invoking command, 
// related module still in progress,
// so it may be not applicable now

// current this: Player
CommandManager.invokeCommand(this.asSender(), ExampleCommand.name)
```


================================================
FILE: sorapointa-core/README.zh-CN.md
================================================
# Core 模块

[English](README.zh-CN.md)

## 命令系统

命令系统基于 [**Yac**](https://githubfast.com/Colerar/Yac) ([**clikt**](https://ajalt.github.io/clikt/) 的变体),
用法基本和 **clikt** 相同。

主要组成有: `Command`, `CommandSender`, `CommandManager`

- `Command`: 命令对象,具有 `run` 函数和由 **Yac** 提供的命令解析功能
- `CommandSender`: 命令发送者
- `CommandManager`: 命令管理器,提供并发、线程安全的命令注册和调用

### 创建

```kotlin
class ExampleCommand(
  private val sender: CommandSender,
) : Command(sender, ExampleCommand) {
  companion object : Entry(
    name = "example",
    help = "示例指令",
    alias = listOf("示例")
  )

  val option by option("--option").flag()

  override suspend fun run() {
    sender.sendMessage("你好!选项:$option")
  }
}
```

请注意这里的小技巧,命令的属性用 `Entry` 对象存储,我们可以将其定义为伴生对象(companion object),以便于用类名直接获取。

关于命令解析,请参见:[Clikt 文档](https://ajalt.github.io/clikt/)

### 注册

注册单个命令:

```kotlin
val command = CommandNode(ExampleCommand) { sender -> ExampleCommand(sender) }
CommandManager.registerCommand(command)
```

`CommandNode`的第一个参数是 `CommandEntry`,之前我们将其定义为伴生对象;
第二个参数是一个高阶函数,用于构造新的命令实例。

注册命令清单:

```kotlin
val commands = listOf(
  CommandNode(ExampleCommand) { sender -> ExampleCommand(sender) },
  CommandNode(Foo) { sender -> Foo(sender) },
  CommandNode(Bar) { sender -> Bar(sender) },
  CommandNode(Help) { sender -> Help(sender) },
)
CommandManager.registerCommands(commands)
```

### 调用

多数情况下你不需要手动调用命令,core 会帮你调用。

但在有些情况,你可能想在代码里手动调用命令,或在玩家触发某些事件时调用。

```kotlin
// 只是一个示例,代码可能并不可用,因为相关模块尚未完成
// 上下文中的 this: Player
CommandManager.invokeCommand(this.asSender(), ExampleCommand.name)
```


================================================
FILE: sorapointa-core/build.gradle.kts
================================================
@file:Suppress("GradlePackageUpdate")

plugins {
    `sorapointa-conventions`
    `sorapointa-publish`
    application
}

dependencies {
    // Project submodules
    implementation(project(":sorapointa-dataloader"))
    implementation(project(":sorapointa-dispatch"))
    api(project(":sorapointa-dataprovider"))
    api(project(":sorapointa-event"))
    api(project(":sorapointa-i18n"))
    api(project(":sorapointa-proto"))
    api(project(":sorapointa-task"))
    api(project(":sorapointa-crypto"))
    api(project(":sorapointa-utils:sorapointa-utils-all"))

    // KotlinX
    implementation(libs.atomicfu)

    // network
    implementation(libs.netty)
    implementation(libs.kcp)
    // Ktor
    implementation(libs.ktor.server.wss)
    // Command
    api(libs.yac)
    // Console
    implementation(libs.jline)
    implementation(libs.password4j)

    implementation(libs.okio)

    testImplementation(project(":sorapointa-dispatch", "test"))
    testImplementation(project(":sorapointa-dataprovider", "test"))
}

configurations.all {
    resolutionStrategy.cacheChangingModulesFor(0, "seconds")
}

application {
    applicationName = "sorapointa"
    mainClass.set("org.sorapointa.MainKt")
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/CoreBundle.kt
================================================
package org.sorapointa

import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.PropertyKey
import org.sorapointa.utils.MessageBundle
import java.util.*

internal const val BUNDLE = "messages.CoreBundle"

object CoreBundle : MessageBundle(BUNDLE) {
    @Nls
    @JvmStatic
    fun message(
        @PropertyKey(resourceBundle = BUNDLE) key: String,
        vararg params: Any?,
        locale: Locale? = null,
    ): String = getString(key, *params, locale = locale)
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/Main.kt
================================================
package org.sorapointa

import io.ktor.server.application.*
import kotlinx.coroutines.*
import moe.sdl.yac.core.CliktCommand
import moe.sdl.yac.core.CommandResult
import moe.sdl.yac.parameters.groups.OptionGroup
import moe.sdl.yac.parameters.groups.defaultByName
import moe.sdl.yac.parameters.groups.groupSwitch
import moe.sdl.yac.parameters.options.*
import mu.KotlinLogging
import org.jline.reader.EndOfFileException
import org.jline.reader.UserInterruptException
import org.sorapointa.SorapointaMain.Mode.*
import org.sorapointa.command.CommandManager
import org.sorapointa.command.ConsoleCommandSender
import org.sorapointa.command.defaults.defaultsCommand
import org.sorapointa.config.*
import org.sorapointa.config.registeredConfig
import org.sorapointa.config.registeredDatabaseTable
import org.sorapointa.console.Console
import org.sorapointa.console.setupConsoleClient
import org.sorapointa.console.setupWebConsoleServer
import org.sorapointa.data.provider.DatabaseManager
import org.sorapointa.dataloader.ResourceHolder
import org.sorapointa.event.EventManager
import org.sorapointa.game.data.SorapointaStore
import org.sorapointa.task.TaskManager
import org.sorapointa.utils.ModuleScope
import org.sorapointa.utils.absPath
import org.sorapointa.utils.addShutdownHook
import org.sorapointa.utils.globalWorkDirectory
import java.io.File
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis

private val logger = KotlinLogging.logger {}

class SorapointaMain : CliktCommand(name = "sorapointa") {
    private val workingDirectory by option("-D", "--working-directory", help = "Set working directory")
        .convert { File(it) }
        .check("File must be directory") { (it.exists() && it.isDirectory) || (!it.exists()) }

    private val noOut by option("-N", "--no-out", help = "stdout and stderr will be disable")
        .flag(default = false)

    private val noRedirect by option("-R", "--no-redirect", help = "Whether redirect to JLine's printAbove")
        .flag(default = false)

    private sealed class Mode : OptionGroup() {
        class Server : Mode()
        class Client : Mode() {
            val username by option("--username", "--usr", "-u").required()
            val password by option("--password", "--pwd", "-p").default("")
            val wssUrl by option("--wss-url", "--url", "-I").default("wss://localhost:443/webconsole")
        }

        class Mixed : Mode()

        class Local : Mode()
    }

    private val mode by option().groupSwitch(
        "--server" to Server(),
        "--client" to Client(),
        "--local" to Local(),
        "--mixed" to Mixed(),
    ).defaultByName("--local")

    override suspend fun run(): Unit = scope.launch {
        setupShutdownHook()

        logger.info { "Version: $VERSION-$BUILD_BRANCH+$COMMIT_HASH" }

        workingDirectory?.let { System.setProperty("user.dir", it.absPath) }
        logger.info { "Sorapointa is working in $globalWorkDirectory" }

        redirectPrint()

        when (val m = mode) {
            is Server -> {
                val server = setupServer {
                    it.setupWebConsoleServer()
                }
                server.join()
            }
            is Mixed -> {
                val server = setupServer {
                    it.setupWebConsoleServer()
                }
                setupLocalConsole()
                server.join()
            }
            is Local -> {
                val server = setupServer()
                setupLocalConsole()
                server.join()
            }
            is Client -> {
                setupConsoleClient(m.username, m.password, m.wssUrl)
            }
        }
    }.join()

    private fun setupShutdownHook() {
        addShutdownHook {
            closeAll()
            println("\nExiting Sorapointa...")
            Console.redirectToNull()
        }
    }

    private fun setupServer(config: (Application) -> Unit = {}) = scope.launch {
        setupRegisteredConfigs().join()

        setupDefaultsCommand()
        Console.setupCompletion()

        EventManager.init(scope.coroutineContext)
        TaskManager.init(scope.coroutineContext)

        setupDatabase().join()

        setupDataloader().join()

        Sorapointa.init(this, scope.coroutineContext, config)
    }

    private fun setupLocalConsole() = scope.launch {
        val consoleSender = ConsoleCommandSender()
        while (isActive) {
            try {
                CommandManager.invokeCommand(consoleSender, Console.readln()).join()
            } catch (e: UserInterruptException) { // Ctrl + C
                println("<Interrupted> use 'quit' command to exit process")
            } catch (e: EndOfFileException) { // Ctrl + D
                exitProcess(0)
            }
        }
    }

    private fun setupRegisteredConfigs(): Job =
        scope.launch {
            logger.info { "Loading Sorapointa configs..." }
            val ms = measureTimeMillis {
                registeredConfig.map {
                    launch {
                        it.init()
                        it.save()
                    }
                }.joinAll()
            }
            logger.info { "Costed $ms ms for loading all configs" }
        }

    private fun setupDataloader(): Job {
        return scope.launch {
            logger.info { "Loading Sorapointa excel data..." }
            runCatching {
                val count: Int
                val time = measureTimeMillis {
                    count = ResourceHolder.findAndRegister()
                    ResourceHolder.loadAll(scope.coroutineContext)
                }
                logger.info { "Loaded $count excel data in $time ms" }
            }.onFailure {
                logger.error(it) { "Could not load sorapointa resources data" }
                exitProcess(1)
            }
        }
    }

    private fun setupDatabase(): Job =
        scope.launch {
            logger.info { "Loading Sorapointa database..." }
            val time = measureTimeMillis {
                DatabaseManager.loadDatabase()
                DatabaseManager.loadTables(registeredDatabaseTable)
            }
            logger.info { "Loaded ${registeredDatabaseTable.size} tables in $time ms" }
            SorapointaStore.initDefaultEntry()
        }

    private fun setupDefaultsCommand() {
        CommandManager.registerCommands(defaultsCommand)
        val registered = defaultsCommand.joinToString(", ") { it.entry.name }
        logger.info { "Registered defaults command, total ${defaultsCommand.size}: $registered" }
    }

    private fun redirectPrint() {
        Console.initReader()
        when {
            noOut -> Console.redirectToNull()
            !noOut && !noRedirect -> Console.redirectToJLine()
            else -> {
                // keep origin
            }
        }
    }

    companion object {

        private val scope = ModuleScope("SorapointaRootScope")

        internal fun closeAll() {
            scope.dispose()
            scope.cancel()
        }
    }
}

suspend fun main(args: Array<String>) {
    when (val result = SorapointaMain().main(args)) {
        is CommandResult.Success -> {
            exitProcess(0)
        }
        is CommandResult.Error -> {
            println(result.userMessage)
            exitProcess(1)
        }
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/Sorapointa.kt
================================================
package org.sorapointa

import io.ktor.server.application.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.sorapointa.command.CommandManager
import org.sorapointa.dispatch.DispatchServer
import org.sorapointa.dispatch.plugins.getCurrentRegionHttpRsp
import org.sorapointa.dispatch.plugins.saveCache
import org.sorapointa.game.Player
import org.sorapointa.server.ServerNetwork
import org.sorapointa.utils.ModuleScope
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

object Sorapointa {
    private var scope = ModuleScope("Sorapointa")

    private val playerMap = ConcurrentHashMap<Int, Player>()

    internal fun init(
        serverScope: CoroutineScope,
        parentContext: CoroutineContext = EmptyCoroutineContext,
        config: (Application) -> Unit = {},
    ): Job {
        scope = ModuleScope("Sorapointa", parentContext)
        CommandManager.init(scope.coroutineContext)
        ServerNetwork.boot(scope.coroutineContext)
        return if (SorapointaConfig.data.startWithDispatch) {
            DispatchServer.startDispatch(serverScope, config = config)
        } else {
            // If we don't start with dispatch server,
            // we will need some CurRegHttpRsp data for future processing.
            // And since we don't have original request from player client, or sth,
            // so we can only use the hardcode URL in
            scope.launch {
                getCurrentRegionHttpRsp().saveCache()
            }
        }
    }

    suspend fun addPlayer(player: Player) {
        player.init()
        playerMap[player.uid] = player
    }

    fun removePlayer(uid: Int) {
        playerMap.remove(uid)
    }

    fun getPlayerList(): List<Player> {
        return playerMap.values.toList()
    }

    fun findPlayerById(id: Int) =
        playerMap[id] ?: error("Cannot find player with uid $id")

    fun findPlayerByIdOrNull(id: Int) =
        playerMap[id]
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/SorapointaConfig.kt
================================================
package org.sorapointa

import com.charleskorn.kaml.YamlComment
import kotlinx.datetime.TimeZone
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.sorapointa.data.provider.DataFilePersist
import org.sorapointa.utils.configDirectory
import org.sorapointa.utils.lenientYaml
import java.io.File
import kotlin.time.Duration

object SorapointaConfig : DataFilePersist<SorapointaConfig.Data>(
    File(configDirectory, "sorapointaConfig.yaml"),
    Data(),
    Data.serializer(),
    lenientYaml,
) {

    @Serializable
    data class Data(
        @YamlComment("Run sorapointa with dispatch server, sorapointa allow you to run them separately")
        val startWithDispatch: Boolean = true,
        @YamlComment("Use current region info for login rsp")
        val useCurrentRegionForLoginRsp: Boolean = true,
        val offsetHours: Int = 4,
        @SerialName("timeZone")
        private val _timeZone: String = TimeZone.currentSystemDefault().toString(),
        @YamlComment("Game server network setting")
        val networkSetting: NetworkSetting = NetworkSetting(),
        @YamlComment("Player inventory store limits")
        val inventoryLimits: InventoryLimits = InventoryLimits(),
        @YamlComment(
            "" +
                "Debug setting for developers",
            "Notice: if you want to enable debug log, " +
                "YOU SHOULD ENABLE IT BY `-Dlogback.configurationFile=path/to/logback.xml`",
        )
        val debugSetting: DebugSetting = DebugSetting(),
    ) {

        val timeZone by lazy {
            TimeZone.of(_timeZone)
        }
    }

    @Serializable
    data class InventoryLimits(
        val weapon: Int = 2000,
        val reliquary: Int = 2000,
        val material: Int = 2000,
        val furniture: Int = 2000,
        val allWeight: Int = 30000,
    )

    @Serializable
    data class NetworkSetting(
        @YamlComment("Game server bind port")
        val bindPort: Int = 22101,
        @YamlComment("Auto disconnect session if client dosen't send `PingReq` in specified time")
        @SerialName("pingTimeout")
        private val _pingTimeout: String = "20s",
        @YamlComment(
            "Game server kcp setting, don't change those settings if you don't know about KCP",
            "See more: https://github.com/skywind3000/kcp",
        )
        val uKcpSetting: UKcpSetting = UKcpSetting(),
    ) {
        val pingTimeout: Duration
            get() = Duration.parse(_pingTimeout)
    }

    @Serializable
    data class UKcpSetting(
        val noDelay: Boolean = true,
        val interval: Int = 40,
        val fastResend: Int = 2,
        val noCongestionWindow: Boolean = true,
        val MTU: Int = 1400,
        val sendWindow: Int = 256,
        val receiveWindow: Int = 256,
        val timeoutMillis: Long = 30 * 1000, // KCP Timeout > Protocol Ping Timeout
        val ackNoDelay: Boolean = false,
    )

    @Serializable
    data class DebugSetting(
        @YamlComment("Use CamelCase rather than SNAKE_CASE for packet name")
        val camelCasePacketName: Boolean = true,
        @YamlComment("Turn on means use blocklist to filter packet, off means use allowlist to filter packet")
        val blockListPacketWatcher: Boolean = true,
        @YamlComment("Skip born cutscene and auto choose name and avatar")
        val skipBornCutscene: Boolean = false,
        @YamlComment("Blocklist of Packet Watcher")
        val blocklist: List<String> = listOf(
            "PingReq",
            "PingRsp",
            "UnionCmdNotify",
            "PlayerSetPauseReq",
            "PlayerSetPauseRsp",
        ),
        @YamlComment("Allowlist of Packet Watcher")
        val allowlist: List<String> = listOf(),
    )
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/Command.kt
================================================
package org.sorapointa.command

import moe.sdl.yac.core.CliktCommand
import moe.sdl.yac.core.context
import org.jetbrains.annotations.PropertyKey
import org.sorapointa.BUNDLE
import org.sorapointa.CoreBundle
import org.sorapointa.game.Player

abstract class Command(
    sender: CommandSender,
    entry: Entry,
    option: Option = Option(),
) : CliktCommand(
    name = entry.name,
    help = CoreBundle.message(entry.helpKey, locale = sender.locale),
    invokeWithoutSubcommand = option.invokeWithoutSubCommand,
    printHelpOnEmptyArgs = option.printHelpOnEmptyArgs,
    allowMultipleSubcommands = option.allowMultipleSubcommands,
    treatUnknownOptionsAsArgs = option.treatUnknownOptionsAsArgs,
) {
    class Option(
        val invokeWithoutSubCommand: Boolean = false,
        val printHelpOnEmptyArgs: Boolean = false,
        val allowMultipleSubcommands: Boolean = false,
        val treatUnknownOptionsAsArgs: Boolean = false,
    )

    /**
     * @param helpKey, will be invoked with i18n
     */
    open class Entry(
        val name: String,
        @PropertyKey(resourceBundle = BUNDLE) val helpKey: String,
        val alias: List<String> = emptyList(),
        val permissionRequired: Int = 0,
    )

    init {
        context { localization = CommandLocalization }
    }
}

abstract class ConsoleCommand(
    sender: ConsoleCommandSender,
    entry: Entry,
    option: Option = Option(),
) : Command(sender, entry, option)

abstract class PlayerCommand(
    sender: Player,
    entry: Entry,
    option: Option = Option(),
) : Command(sender, entry, option)


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/CommandLocalization.kt
================================================
package org.sorapointa.command

import moe.sdl.yac.core.*
import moe.sdl.yac.output.HelpFormatter
import moe.sdl.yac.output.Localization
import moe.sdl.yac.parameters.groups.ChoiceGroup
import moe.sdl.yac.parameters.groups.MutuallyExclusiveOptions
import org.sorapointa.CoreBundle

/** An object to let clikt use i18n */
object CommandLocalization : Localization {
    /** [Abort] was thrown */
    override fun aborted() = CoreBundle.message("clikt.aborted")

    /** Prefix for any [UsageError] */
    override fun usageError(message: String) = CoreBundle.message("clikt.usage.error", message)

    /** Message for [BadParameterValue] */
    override fun badParameter() = CoreBundle.message("clikt.bad.parameter")

    /** Message for [BadParameterValue] */
    override fun badParameterWithMessage(message: String) =
        CoreBundle.message("clikt.bad.parameter.with.message", message)

    /** Message for [BadParameterValue] */
    override fun badParameterWithParam(paramName: String) =
        CoreBundle.message("clikt.bad.parameter.with.param", paramName)

    /** Message for [BadParameterValue] */
    override fun badParameterWithMessageAndParam(paramName: String, message: String) =
        CoreBundle.message("clikt.bad.parameter.with.message.param", paramName, message)

    /** Message for [MissingOption] */
    override fun missingOption(paramName: String) = CoreBundle.message("clikt.missing.option", paramName)

    /** Message for [MissingArgument] */
    override fun missingArgument(paramName: String) = CoreBundle.message("clikt.missing.argument", paramName)

    /** Message for [NoSuchSubcommand] */
    override fun noSuchSubcommand(name: String, possibilities: List<String>): String {
        return CoreBundle.message("clikt.no.such.subcommand") + when (possibilities.size) {
            0 -> ""
            1 -> CoreBundle.message("clikt.no.such.subcommand.one", possibilities.first())
            else -> possibilities.joinToString(
                prefix = CoreBundle.message("clikt.no.such.subcommand.else.prefix"),
                postfix = ")",
            )
        }
    }

    /** Message for [NoSuchOption] */
    override fun noSuchOption(name: String, possibilities: List<String>): String {
        return CoreBundle.message("clikt.no.such.option") + when (possibilities.size) {
            0 -> ""
            1 -> CoreBundle.message("clikt.no.such.option.one", possibilities.first())
            else -> possibilities.joinToString(
                prefix = CoreBundle.message("clikt.no.such.option.else.prefix"),
                postfix = ")",
            )
        }
    }

    /**
     * Message for [IncorrectOptionValueCount]
     *
     * @param count non-negative count of required values
     */
    override fun incorrectOptionValueCount(name: String, count: Int): String {
        return when (count) {
            0 -> CoreBundle.message("clikt.incorrect.option.value.count.zero", name)
            1 -> CoreBundle.message("clikt.incorrect.option.value.count.one", name)
            else -> CoreBundle.message("clikt.incorrect.option.value.count.else", name, count)
        }
    }

    /**
     * Message for [IncorrectArgumentValueCount]
     *
     * @param count non-negative count of required values
     */
    override fun incorrectArgumentValueCount(name: String, count: Int): String {
        return when (count) {
            0 -> CoreBundle.message("clikt.incorrect.argument.value.count.zero", name)
            1 -> CoreBundle.message("clikt.incorrect.argument.value.count.one", name)
            else -> CoreBundle.message("clikt.incorrect.argument.value.count.else", name, count)
        }
    }

    /**
     * Message for [MutuallyExclusiveGroupException]
     *
     * @param others non-empty list of other options in the group
     */
    override fun mutexGroupException(name: String, others: List<String>): String {
        return CoreBundle.message(
            "clikt.mutex.group.exception",
            name,
            others.joinToString(
                CoreBundle.message("clikt.mutex.group.exception.separator"),
            ),
        )
    }

    /** Message for [FileNotFound] */
    override fun fileNotFound(filename: String) = CoreBundle.message("clikt.file.not.found", filename)

    /** Message for [InvalidFileFormat]*/
    override fun invalidFileFormat(filename: String, message: String) =
        CoreBundle.message("clikt.invalid.file.format", filename, message)

    /** Message for [InvalidFileFormat]*/
    override fun invalidFileFormat(filename: String, lineNumber: Int, message: String) =
        CoreBundle.message("clikt.invalid.file.format.with.line.number", filename, lineNumber, message)

    /** Error in message for [InvalidFileFormat] */
    override fun unclosedQuote() = CoreBundle.message("clikt.unclosed.quote")

    /** Error in message for [InvalidFileFormat] */
    override fun fileEndsWithSlash() = CoreBundle.message("clikt.file.ends.with.slash")

    /** One extra argument is present */
    override fun extraArgumentOne(name: String) = CoreBundle.message("clikt.extra.argument.one")

    /** More than one extra argument is present */
    override fun extraArgumentMany(name: String, count: Int) = CoreBundle.message("clikt.extra.argument.many")

    /** Error message when reading flag option from a file */
    override fun invalidFlagValueInFile(name: String) =
        CoreBundle.message("clikt.invalid.flag.value.in.file")

    /** Error message when reading switch option from environment variable */
    override fun switchOptionEnvvar() = CoreBundle.message("clikt.switch.option.envvar")

    /** Required [MutuallyExclusiveOptions] was not provided */
    override fun requiredMutexOption(options: String) = CoreBundle.message("clikt.required.mutex.option", options)

    /**
     * [ChoiceGroup] value was invalid
     *
     * @param choices non-empty list of possible choices
     */
    override fun invalidGroupChoice(value: String, choices: List<String>): String {
        return CoreBundle.message("clikt.invalid.group.choice", value, choices.joinToString())
    }

    /** Invalid value for a parameter of type [Double] or [Float] */
    override fun floatConversionError(value: String) = CoreBundle.message("clikt.conversion.error.float", value)

    /** Invalid value for a parameter of type [Int] or [Long] */
    override fun intConversionError(value: String) = CoreBundle.message("clikt.conversion.error.int", value)

    /** Invalid value for a parameter of type [Boolean] */
    override fun boolConversionError(value: String) = CoreBundle.message("clikt.conversion.error.bool", value)

    /** Invalid value falls outside range */
    override fun rangeExceededMax(value: String, limit: String) =
        CoreBundle.message("clikt.range.exceeded.max", value, limit)

    /** Invalid value falls outside range */
    override fun rangeExceededMin(value: String, limit: String) =
        CoreBundle.message("clikt.range.exceeded.min", value, limit)

    /** Invalid value falls outside range */
    override fun rangeExceededBoth(value: String, min: String, max: String) =
        CoreBundle.message("clikt.range.exceeded.both", value, min, max)

    /**
     * Invalid value for `choice` parameter
     *
     * @param choices non-empty list of possible choices
     */
    override fun invalidChoice(choice: String, choices: List<String>): String =
        CoreBundle.message("clikt.invalid.choice", choice, choices.joinToString())

    /** The `pathType` parameter to [pathDoesNotExist] and other `path*` errors */
    override fun pathTypeFile() = CoreBundle.message("clikt.path.type.file")

    /** The `pathType` parameter to [pathDoesNotExist] and other `path*` errors */
    override fun pathTypeDirectory() = CoreBundle.message("clikt.path.type.directory")

    /** The `pathType` parameter to [pathDoesNotExist] and other `path*` errors */
    override fun pathTypeOther() = CoreBundle.message("clikt.path.type.other")

    /** Invalid path type */
    override fun pathDoesNotExist(pathType: String, path: String) =
        CoreBundle.message("clikt.path.does.not.exist", pathType, path)

    /** Invalid path type */
    override fun pathIsFile(pathType: String, path: String) = CoreBundle.message("clikt.path.is.file", pathType, path)

    /** Invalid path type */
    override fun pathIsDirectory(pathType: String, path: String) =
        CoreBundle.message("clikt.path.is.directory", pathType, path)

    /** Invalid path type */
    override fun pathIsNotWritable(pathType: String, path: String) =
        CoreBundle.message("clikt.path.is.not.writable", pathType, path)

    /** Invalid path type */
    override fun pathIsNotReadable(pathType: String, path: String) =
        CoreBundle.message("clikt.path.is.not.readable", pathType, path)

    /** Invalid path type */
    override fun pathIsSymlink(pathType: String, path: String) =
        CoreBundle.message("clikt.path.is.symlink", pathType, path)

    /** Metavar used for options with unspecified value type */
    override fun defaultMetavar() = CoreBundle.message("clikt.meta.var.default")

    /** Metavar used for options that take [String] values */
    override fun stringMetavar() = CoreBundle.message("clikt.meta.var.string")

    /** Metavar used for options that take [Float] or [Double] values */
    override fun floatMetavar() = CoreBundle.message("clikt.meta.var.float")

    /** Metavar used for options that take [Int] or [Long] values */
    override fun intMetavar() = CoreBundle.message("clikt.meta.var.int")

    /** Metavar used for options that take `File` or `Path` values */
    override fun pathMetavar() = CoreBundle.message("clikt.meta.var.path")

    /** Metavar used for options that take `InputStream` or `OutputStream` values */
    override fun fileMetavar() = CoreBundle.message("clikt.meta.var.file")

    /** The title for the usage section of help output */
    override fun usageTitle(): String = CoreBundle.message("clikt.title.usage")

    /** The title for the options' section of help output */
    override fun optionsTitle(): String = CoreBundle.message("clikt.title.options")

    /** The title for the arguments' section of help output */
    override fun argumentsTitle(): String = CoreBundle.message("clikt.title.arguments")

    /** The title for the subcommands section of help output */
    override fun commandsTitle(): String = CoreBundle.message("clikt.title.commands")

    /** The that indicates where options may be present in the usage help output */
    override fun optionsMetavar(): String = CoreBundle.message("clikt.meta.var.options")

    /** The that indicates where subcommands may be present in the usage help output */
    override fun commandMetavar(): String = CoreBundle.message("clikt.meta.var.command")

    /** Text rendered for parameters tagged with [HelpFormatter.Tags.DEFAULT] */
    override fun helpTagDefault(): String = CoreBundle.message("clikt.help.tag.default")

    /** Text rendered for parameters tagged with [HelpFormatter.Tags.REQUIRED] */
    override fun helpTagRequired(): String = CoreBundle.message("clikt.help.tag.required")

    /** The default message for the `--help` option. */
    override fun helpOptionMessage(): String = CoreBundle.message("clikt.help.option.message")
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/CommandManager.kt
================================================
package org.sorapointa.command

import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import moe.sdl.yac.core.CommandResult
import moe.sdl.yac.core.CommandResult.Error
import moe.sdl.yac.core.CommandResult.Success
import moe.sdl.yac.core.PrintHelpMessage
import moe.sdl.yac.core.parseToArgs
import org.sorapointa.CoreBundle
import org.sorapointa.game.Player
import org.sorapointa.utils.ModuleScope
import org.sorapointa.utils.suggestTypo
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

private val logger = mu.KotlinLogging.logger {}

abstract class AbstractCommandNode<TSender : CommandSender>(
    val entry: Command.Entry,
    val creator: (sender: TSender) -> Command,
)

class CommandNode(
    entry: Command.Entry,
    creator: (CommandSender) -> Command,
) : AbstractCommandNode<CommandSender>(entry, creator)

class ConsoleCommandNode(
    entry: Command.Entry,
    creator: (ConsoleCommandSender) -> Command,
) : AbstractCommandNode<ConsoleCommandSender>(entry, creator)

class PlayerCommandNode(
    entry: Command.Entry,
    creator: (Player) -> Command,
) : AbstractCommandNode<Player>(entry, creator)

object CommandManager {
    val commandMap: Map<String, AbstractCommandNode<*>>
        get() = cmdMap

    private val cmdMap: MutableMap<String, AbstractCommandNode<*>> = ConcurrentHashMap()

    // A map to save the registered commands with alias.
    private val aliasMap: MutableMap<String, AbstractCommandNode<*>> = ConcurrentHashMap()

    val commandEntries: List<Command.Entry> get() = cmdMap.entries.map { it.value.entry }

    private var commandScope = ModuleScope("CommandManager")

    internal fun init(parentContext: CoroutineContext = EmptyCoroutineContext) {
        commandScope = ModuleScope("CommandManager", parentContext)
    }

    @Suppress("unused")
    fun registerCommand(entry: Command.Entry, creator: (CommandSender) -> Command) {
        registerCommand(CommandNode(entry, creator))
    }

    @Suppress("MemberVisibilityCanBePrivate")
    fun registerCommand(commandNode: AbstractCommandNode<*>) {
        val name = commandNode.entry.name
        val alias = commandNode.entry.alias

        cmdMap.putIfAbsent(name, commandNode)?.also {
            logger.warn { "Command name '$name' conflict." }
        }

        alias.forEach {
            aliasMap.putIfAbsent(it, commandNode)?.also {
                logger.warn { "Alias name '$alias' conflict." }
            }
        }
    }

    fun registerCommands(collection: Collection<AbstractCommandNode<*>>): Unit =
        collection.forEach { registerCommand(it) }

    fun invokeCommand(
        sender: CommandSender,
        rawMsg: String,
    ): Job = commandScope.launch {
        if (rawMsg.isBlank()) {
            sender.sendMessage(
                CoreBundle.message("sora.cmd.manager.invoke.empty", locale = sender.locale),
            )
            return@launch
        }

        val args = rawMsg.parseToArgs()
        val mainCommand = args[0]

        val cmd = cmdMap[mainCommand] ?: aliasMap[mainCommand] ?: run {
            val mainTypo = suggestTypo(mainCommand, cmdMap.keys.toList())
            if (mainTypo == null) {
                sender.sendMessage(
                    CoreBundle.message("sora.cmd.manager.invoke.error", mainCommand, locale = sender.locale),
                )
            } else {
                sender.sendMessage(
                    CoreBundle.message(
                        "sora.cmd.manager.invoke.typo.suggest",
                        mainCommand,
                        mainTypo,
                        locale = sender.locale,
                    ),
                )
            }
            return@launch
        }

        val result: CommandResult = run {
            if (sender is Player && sender.account.permissionLevel < cmd.entry.permissionRequired) {
                return@run Error(
                    null,
                    userMessage = CoreBundle.message("sora.cmd.no.permission", locale = sender.locale),
                )
            }
            when (cmd) {
                is CommandNode -> {
                    cmd.creator(sender).execute(args)
                }

                is ConsoleCommandNode -> {
                    if (sender is ConsoleCommandSender) {
                        cmd.creator(sender).execute(args)
                    } else {
                        Error(null, userMessage = CoreBundle.message("sora.cmd.no.permission", locale = sender.locale))
                    }
                }

                is PlayerCommandNode -> {
                    if (sender is Player) {
                        cmd.creator(sender).execute(args)
                    } else {
                        Error(null, userMessage = CoreBundle.message("sora.cmd.is.not.player", locale = sender.locale))
                    }
                }

                else -> Error(null, userMessage = CoreBundle.message("server.error", locale = sender.locale))
            }
        }

        when (result) {
            is Error -> {
                val msg = buildString {
                    append(result.userMessage)
                    if (result.cause is PrintHelpMessage && cmd.entry.alias.isNotEmpty()) {
                        append(
                            CoreBundle.message(
                                "sora.cmd.manager.alias",
                                cmd.entry.alias.joinToString(),
                                locale = sender.locale,
                            ),
                        )
                    }
                }
                sender.sendMessage(msg)
            }

            is Success -> {
                // pass
            }
        }
    }

    /**
     * @param mainCommand main command or alias
     * @return [Boolean] has or not
     */
    fun hasCommand(mainCommand: String): Boolean =
        cmdMap.containsKey(mainCommand) || aliasMap.containsKey(mainCommand)
}

private suspend fun Command.execute(args: List<String>) = this.main(args.drop(1))


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/CommandSender.kt
================================================
package org.sorapointa.command

import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.PropertyKey
import org.sorapointa.BUNDLE
import org.sorapointa.CoreBundle
import org.sorapointa.utils.LocaleAble
import java.util.*

interface CommandSender : LocaleAble {

    /**
     * An abstract function to send a message to the sender.
     *
     * @param msg The message to be sent.
     */
    suspend fun sendMessage(msg: String)
}

@Nls
internal suspend inline fun CommandSender.sendLocaled(
    @PropertyKey(resourceBundle = BUNDLE) key: String,
    vararg params: Any?,
) = sendMessage(CoreBundle.message(key, *params, locale = this.locale))

@Nls
@Suppress("NOTHING_TO_INLINE")
internal inline fun CommandSender.localed(
    @PropertyKey(resourceBundle = BUNDLE) key: String,
    vararg params: Any?,
): String = CoreBundle.message(key, *params, locale = this.locale)


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/ConsoleCommandSender.kt
================================================
package org.sorapointa.command

import io.ktor.server.websocket.*
import kotlinx.coroutines.isActive
import org.sorapointa.console.MessageNotify
import org.sorapointa.console.WebConsolePacket
import java.util.*

open class ConsoleCommandSender(
    override val locale: Locale? = null,
) : CommandSender {
    override suspend fun sendMessage(msg: String): Unit = println(msg)
}

class RemoteCommandSender(
    private val session: DefaultWebSocketServerSession,
    override val locale: Locale?,
) : ConsoleCommandSender() {
    val isActive
        get() = session.isActive

    override suspend fun sendMessage(msg: String) {
        session.sendSerialized<WebConsolePacket>(MessageNotify(msg))
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/Defaults.kt
================================================
package org.sorapointa.command.defaults

import org.sorapointa.command.AbstractCommandNode
import org.sorapointa.command.CommandNode
import org.sorapointa.command.ConsoleCommandNode
import org.sorapointa.command.defaults.console.ConsoleUser
import org.sorapointa.command.defaults.console.Quit
import org.sorapointa.command.defaults.general.Help
import org.sorapointa.command.defaults.general.ListPlayer
import org.sorapointa.command.defaults.general.LocaleCommand
import org.sorapointa.command.defaults.general.Version

val defaultsCommand: List<AbstractCommandNode<*>> = listOf(
    CommandNode(Help) { sender -> Help(sender) },
    CommandNode(ListPlayer) { sender -> ListPlayer(sender) },
    CommandNode(LocaleCommand) { sender -> LocaleCommand(sender) },
    CommandNode(Version) { sender -> Version(sender) },
    ConsoleCommandNode(Quit) { sender -> Quit(sender) },
    ConsoleCommandNode(ConsoleUser) { sender -> ConsoleUser(sender) },
)


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/console/ConsoleUser.kt
================================================
package org.sorapointa.command.defaults.console

import moe.sdl.yac.core.PrintMessage
import moe.sdl.yac.parameters.options.default
import moe.sdl.yac.parameters.options.option
import org.sorapointa.command.*
import org.sorapointa.command.utils.switchSet
import org.sorapointa.console.ConsoleUsers

class ConsoleUser(val sender: ConsoleCommandSender) : ConsoleCommand(
    sender,
    ConsoleUser,
    Option(printHelpOnEmptyArgs = true),
) {
    companion object : Entry(
        name = "consoleuser",
        helpKey = "sora.cmd.console.user.desc",
        alias = listOf("cslusr"),
    )

    private val username by option(
        names = arrayOf("--username", "-u"),
        help = sender.localed("sora.cmd.console.user.opt.user"),
    )

    private val password by option(
        names = arrayOf("--password", "--pwd", "-p"),
        help = sender.localed("sora.cmd.console.user.opt.pwd"),
    )

    enum class Operation {
        ADD, UPDATE, DELETE, LIST
    }

    private val operation by option(
        help = sender.localed("sora.cmd.console.user.opt.operation"),
    ).switchSet(
        setOf("--add", "-a") to Operation.ADD,
        setOf("--update", "--upd", "-U") to Operation.UPDATE,
        setOf("--delete", "--del", "-d") to Operation.DELETE,
        setOf("--list", "--ls", "-l") to Operation.LIST,
    ).default(Operation.ADD)

    private suspend fun addOrUpdate(username: String?) {
        if (username == null) throw PrintMessage(CommandLocalization.missingOption("--username"))
        val sender = this@ConsoleUser.sender
        ConsoleUsers.addOrUpdate(
            username,
            password ?: run {
                sender.sendLocaled("sora.cmd.console.user.msg.empty.pwd")
                ""
            },
        )
    }

    override suspend fun run() {
        when (operation) {
            Operation.UPDATE -> {
                addOrUpdate(username)
                sender.sendLocaled("sora.cmd.console.user.msg.success.update", username)
                ConsoleUsers.save()
            }

            Operation.ADD -> {
                if (ConsoleUsers.data.users.contains(username)) {
                    sender.sendLocaled("sora.cmd.console.user.msg.duplicate", username)
                    return
                }
                addOrUpdate(username)
                sender.sendLocaled("sora.cmd.console.user.msg.success.add", username)
                ConsoleUsers.save()
            }

            Operation.DELETE -> {
                val removed = ConsoleUsers.data.users.remove(username) != null
                if (removed) {
                    sender.sendLocaled("sora.cmd.console.user.msg.success.remove", username)
                } else {
                    sender.sendLocaled("sora.cmd.console.user.msg.nosuch", username)
                }
            }

            Operation.LIST -> {
                val usrs = ConsoleUsers.data.users.keys
                sender.sendLocaled(
                    "sora.cmd.console.user.msg.list",
                    usrs.size,
                    usrs.joinToString(),
                )
            }
        }
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/console/Quit.kt
================================================
package org.sorapointa.command.defaults.console

import org.sorapointa.command.ConsoleCommand
import org.sorapointa.command.ConsoleCommandSender
import kotlin.system.exitProcess

class Quit(sender: ConsoleCommandSender) : ConsoleCommand(sender, Quit) {
    companion object : Entry(
        name = "quit",
        helpKey = "sora.cmd.quit.desc",
        alias = listOf("exit"),
    )

    override suspend fun run() {
        exitProcess(0)
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/Help.kt
================================================
package org.sorapointa.command.defaults.general

import moe.sdl.yac.core.UsageError
import moe.sdl.yac.parameters.arguments.argument
import moe.sdl.yac.parameters.arguments.default
import moe.sdl.yac.parameters.options.convert
import moe.sdl.yac.parameters.options.default
import moe.sdl.yac.parameters.options.option
import moe.sdl.yac.parameters.types.int
import org.sorapointa.command.Command
import org.sorapointa.command.CommandManager
import org.sorapointa.command.CommandSender
import org.sorapointa.command.localed

class Help(private val sender: CommandSender) : Command(sender, Help) {
    companion object : Entry(
        name = "help",
        helpKey = "sora.cmd.help.desc",
        alias = listOf("?"),
    )

    private val pageNum by argument(
        name = sender.localed("sora.cmd.help.arg.page.num.name"),
        help = sender.localed("sora.cmd.help.arg.page.num.desc"),
    ).int().default(1)

    private val pageSize by option(
        "--page-size",
        "-s",
        help = sender.localed("sora.cmd.help.opt.page.size.desc"),
    ).int().convert {
        it.coerceIn(1..50)
    }.default(10)

    override suspend fun run() {
        val cmdList = CommandManager.commandEntries
        // Take out the page items from command list.
        val pageItems = cmdList.chunked(pageSize)
        // Throw exception when the page number exceed.
        if (pageNum !in 1..pageItems.size) {
            throw UsageError(sender.localed("sora.cmd.help.arg.page.num.exceed"))
        }

        // Build the message and send to the sender
        sender.sendMessage(
            buildString {
                appendLine(sender.localed("sora.cmd.help.msg.page", pageNum, pageItems.size))
                val entries = pageItems[pageNum - 1]
                val maxLen = entries.map { it.name }.maxOf { it.length }
                entries.forEach {
                    append(it.name.padEnd(maxLen, ' '))
                    append(" >> ")
                    if (it.helpKey.isNotBlank()) {
                        append(sender.localed(it.helpKey))
                    } else {
                        append(sender.localed("sora.cmd.help.msg.empty.desc"))
                    }
                    appendLine()
                }
                append(sender.localed("sora.cmd.help.msg.more"))
            },
        )
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/ListPlayer.kt
================================================
package org.sorapointa.command.defaults.general

import org.sorapointa.Sorapointa
import org.sorapointa.command.Command
import org.sorapointa.command.CommandSender
import org.sorapointa.command.sendLocaled

class ListPlayer(private val sender: CommandSender) : Command(sender, ListPlayer) {

    companion object : Entry(
        name = "listplayer",
        helpKey = "sora.cmd.list.player.desc",
        alias = listOf("list"),
        permissionRequired = 1,
    )

    override suspend fun run() {
        val list = Sorapointa.getPlayerList()
        sender.sendLocaled(
            "sora.cmd.list.player.msg",
            list.size,
            list.joinToString { "${it.account.userName} (${it.basicComp.nickname}, UID:${it.uid})" },
        )
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/LocaleCommand.kt
================================================
package org.sorapointa.command.defaults.general

import moe.sdl.yac.parameters.arguments.argument
import moe.sdl.yac.parameters.arguments.default
import moe.sdl.yac.parameters.arguments.optional
import moe.sdl.yac.parameters.options.flag
import moe.sdl.yac.parameters.options.option
import moe.sdl.yac.parameters.types.enum
import org.sorapointa.CoreBundle
import org.sorapointa.command.*
import org.sorapointa.game.Player
import org.sorapointa.utils.I18nConfig
import java.util.Locale as JavaLocale

class LocaleCommand(private val sender: CommandSender) : Command(sender, LocaleCommand) {

    companion object : Entry(
        name = "locale",
        helpKey = "sora.cmd.locale.desc",
        alias = listOf("lang"),
    )

    enum class Operation {
        VIEW, SET, LIST,
    }

    private val defaultOp = Operation.VIEW
    private val operation by argument(
        CoreBundle.message("sora.cmd.locale.arg.operation.name", locale = sender.locale),
        help = CoreBundle.message(
            "sora.cmd.locale.arg.operation.desc",
            Operation.values().joinToString { it.name },
            defaultOp.name,
            locale = sender.locale,
        ),
    ).enum<Operation>(ignoreCase = true).default(defaultOp)

    private val newValue by argument(
        sender.localed("sora.cmd.locale.arg.new.value.name"),
        help = sender.localed("sora.cmd.locale.arg.new.value.desc"),
    ).optional()

    private val force by option(
        "--force",
        "-F",
        help = sender.localed("sora.cmd.locale.opt.force.desc"),
    ).flag(default = false)

    private suspend fun sendLocaleInfo(locale: JavaLocale?) {
        val langTag = locale?.toLanguageTag() ?: "NONE"
        sender.sendLocaled("sora.cmd.locale.msg.view", langTag)
    }

    private suspend fun modifyLocaleInfo(modifyValue: suspend (JavaLocale) -> Unit) {
        val newValue = this.newValue // For smart cast
        if (newValue == null) {
            sender.sendLocaled("sora.cmd.locale.msg.new.value.missing")
            return
        }
        if (newValue.length > 20) {
            sender.sendLocaled("sora.cmd.locale.msg.new.value.toolong", 20)
            return
        }
        val locale = JavaLocale.forLanguageTag(newValue)
        val found = CoreBundle.availableLocales().contains(locale)
        if (found && !force) {
            sender.sendLocaled("sora.cmd.locale.msg.new.value.notfound")
            return
        }
        modifyValue(locale)
        sender.sendLocaled("sora.cmd.locale.msg.success", locale.toLanguageTag())
    }

    private suspend inline fun sendAvailableList() =
        sender.sendMessage(CoreBundle.availableLocales().joinToString())

    override suspend fun run() {
        when (sender) {
            is Player -> when (operation) {
                Operation.VIEW -> sendLocaleInfo(sender.locale)
                Operation.SET -> modifyLocaleInfo { sender.locale = it }
                Operation.LIST -> sendAvailableList()
            }

            is ConsoleCommandSender -> when (operation) {
                Operation.VIEW -> sendLocaleInfo(I18nConfig.data.globalLocale)
                Operation.SET -> modifyLocaleInfo {
                    I18nConfig.data.globalLocale = it
                    I18nConfig.save()
                }

                Operation.LIST -> sendAvailableList()
            }

            else -> sender.sendLocaled("sora.cmd.locale.msg.unsupported")
        }
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/Version.kt
================================================
package org.sorapointa.command.defaults.general

import org.sorapointa.command.Command
import org.sorapointa.command.CommandSender
import org.sorapointa.config.BUILD_BRANCH
import org.sorapointa.config.COMMIT_HASH
import org.sorapointa.config.VERSION

class Version(private val sender: CommandSender) : Command(sender, Version) {

    companion object : Entry(
        name = "version",
        helpKey = "sora.cmd.version.desc",
    )

    override suspend fun run() {
        sender.sendMessage("Sorapointa v$VERSION-$BUILD_BRANCH+$COMMIT_HASH")
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/command/utils/Options.kt
================================================
package org.sorapointa.command.utils

import moe.sdl.yac.parameters.groups.ChoiceGroup
import moe.sdl.yac.parameters.groups.OptionGroup
import moe.sdl.yac.parameters.groups.groupSwitch
import moe.sdl.yac.parameters.options.FlagOption
import moe.sdl.yac.parameters.options.RawOption
import moe.sdl.yac.parameters.options.switch

/**
 * Make a multikey pair to group option
 *
 * ### Example:
 *
 * ```
 * option().switch(
 *   setOf("--foo", "-F") to Foo(),
 *   listOf("-b", "--bar") to Bar(),
 * )
 * ```
 */
fun <T : Any> RawOption.switchSet(vararg choices: Pair<Collection<String>, T>): FlagOption<T?> =
    switch(
        choices.flatMap { (keys, value) ->
            keys.map { it to value }
        }.toMap(),
    )

/**
 * Convert the option into a set of flags that each map to an option group.
 * Make a multikey pair to option group
 * ### Example:
 *
 * ```
 * option().groupSwitch(
 *   setOf("--foo", "-F") to FooOptionGroup(),
 *   setOf("--bar", "-b") to BarOptionGroup()
 * )
 * ```
 */
fun <T : OptionGroup> RawOption.groupSwitchSet(vararg choices: Pair<Collection<String>, T>): ChoiceGroup<T, T?> =
    groupSwitch(
        choices.flatMap { (keys, value) ->
            keys.map { it to value }
        }.toMap(),
    )


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/console/Completer.kt
================================================
@file:Suppress("unused")

package org.sorapointa.console

import moe.sdl.yac.core.CliktCommand
import moe.sdl.yac.parameters.arguments.Argument
import moe.sdl.yac.parameters.groups.ParameterGroup
import moe.sdl.yac.parameters.options.Option
import org.jline.builtins.Completers.TreeCompleter.Node
import org.jline.builtins.Completers.TreeCompleter.node
import org.sorapointa.utils.uncheckedCast

private val cliktClazz = CliktCommand::class.java

@Suppress("NOTHING_TO_INLINE")
private inline fun field(name: String) = cliktClazz.getDeclaredField(name).apply {
    trySetAccessible()
} ?: throw NoSuchFieldException(name)

private val subcommands = field("_subcommands")
private val options = field("_options")
private val arguments = field("_arguments")
private val groups = field("_groups")

private fun CliktCommand.subcommands(): List<CliktCommand> = subcommands.get(this).uncheckedCast()
private fun CliktCommand.options(): List<Option> = options.get(this).uncheckedCast()
private fun CliktCommand.arguments(): List<Argument> = arguments.get(this).uncheckedCast()
private fun CliktCommand.groups(): List<ParameterGroup> = groups.get(this).uncheckedCast()

internal fun CliktCommand.toCompleterNodes(): List<Node> =
    subcommands().map { node(it.commandName, it.toCompleterNodes()) } +
        options().flatMap { it.names + it.secondaryNames }.map { node(it) } +
        groups().mapNotNull { it.groupName }.map { node(it) }


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/console/Console.kt
================================================
package org.sorapointa.console

import io.ktor.util.collections.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jline.builtins.Completers.TreeCompleter
import org.jline.builtins.Completers.TreeCompleter.node
import org.jline.reader.LineReader
import org.jline.reader.LineReaderBuilder
import org.jline.reader.impl.LineReaderImpl
import org.jline.reader.impl.history.DefaultHistory
import org.jline.terminal.Terminal
import org.jline.terminal.TerminalBuilder
import org.jline.widget.AutosuggestionWidgets
import org.sorapointa.command.*
import org.sorapointa.utils.*
import java.io.OutputStream
import java.io.PrintStream
import java.util.*
import kotlin.reflect.jvm.ExperimentalReflectionOnLambdas
import kotlin.reflect.jvm.reflect
import kotlin.reflect.typeOf

@Suppress("MemberVisibilityCanBePrivate")
internal object Console {
    internal val terminal: Terminal = TerminalBuilder.terminal()

    private var reader: LineReader? = null

    private object FakeSender : ConsoleCommandSender() {
        override suspend fun sendMessage(msg: String) {}
        override val locale: Locale = Locale.ENGLISH
    }

    internal fun initReader() {
        if (reader != null) return
        reader = LineReaderBuilder.builder()
            .appName("Sorapointa")
            .terminal(terminal)
            .highlighter(SoraHighlighter)
            .build().apply {
                AutosuggestionWidgets(this).enable()
                initHistory()
            }
    }

    @OptIn(ExperimentalReflectionOnLambdas::class)
    internal fun setupCompletion() {
        val completions by lazy {
            CommandManager.commandMap.filter { (_, node) ->
                val type = node.creator.reflect()?.parameters?.firstOrNull()?.type
                type == typeOf<CommandSender>() || type == typeOf<ConsoleCommandSender>()
            }.flatMap { (_, node) ->
                val creator = node.creator.uncheckedCast<(sender: CommandSender) -> Command>()
                val nodes = creator(FakeSender).toCompleterNodes()
                buildList {
                    add(node.entry.name)
                    addAll(node.entry.alias)
                }.map {
                    node(it, *nodes.toTypedArray())
                }
            }.let { TreeCompleter(it) }
        }
        (reader as? LineReaderImpl)?.completer = completions
    }

    private const val HISTORY_FILE = ".sorapointa_history"

    private fun LineReader.initHistory() {
        setVariable(
            LineReader.HISTORY_FILE,
            resolveHome(HISTORY_FILE)
                ?: resolveWorkDirectory(HISTORY_FILE),
        )
        DefaultHistory(this).apply {
            addShutdownHook {
                save()
            }
        }
    }

    private val scope = ModuleScope("RemoteConsole", dispatcher = Dispatchers.IO)

    internal val consoleUsers = ConcurrentSet<RemoteCommandSender>()

    fun readln(prompt: String = "> "): String = reader?.readLine(prompt) ?: error("Reader not prepared")

    fun println(any: Any?) {
        if (consoleUsers.isNotEmpty()) {
            scope.launch {
                consoleUsers.removeIf { !it.isActive }
                consoleUsers.forEach {
                    it.sendMessage(any.toString())
                }
            }
        }
        reader?.printAbove(any.toString()) ?: kotlin.io.println(any)
    }

    @Suppress("NOTHING_TO_INLINE")
    inline fun println(string: String?) = println(any = string)

    internal fun redirectToJLine() {
        if (System.out === JLineRedirector) return
        System.setErr(JLineRedirector)
        System.setOut(JLineRedirector)
    }

    internal fun redirectToNull() {
        val out = PrintStream(OutputStream.nullOutputStream())
        System.setOut(out)
        System.setErr(out)
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/console/JLineRedirector.kt
================================================
package org.sorapointa.console

import java.io.PrintStream
import java.util.*

/**
 * Work around, not a good implementation
 */
internal object JLineRedirector : PrintStream(nullOutputStream()) {
    @Suppress("NOTHING_TO_INLINE")
    private inline fun println0() = Console.println("")

    @Suppress("NOTHING_TO_INLINE")
    private inline fun println0(x: Any?) = Console.println(x)

    override fun println(x: Any?) = println0(x)

    override fun println(x: Boolean) = println0(x)

    override fun println(x: Char) = println0(x)

    override fun println(x: CharArray) = println0(x.contentToString())

    override fun println(x: Double) = println0(x)

    override fun println(x: Float) = println0(x)

    override fun println(x: Int) = println0(x)

    override fun println(x: Long) = println0(x)

    override fun println(x: String?) = println0(x)

    override fun printf(format: String, vararg args: Any?): PrintStream {
        println0(String.format(format, args = args))
        return this
    }

    override fun format(format: String, vararg args: Any?): PrintStream {
        String.format(format, args = args)
        return this
    }

    override fun format(l: Locale?, format: String, vararg args: Any?): PrintStream {
        println0(String.format(l, format, args = args))
        return this
    }

    override fun printf(l: Locale?, format: String, vararg args: Any?): PrintStream {
        return super.printf(l, format, *args)
    }

    override fun println() = println0()

    override fun append(csq: CharSequence?): PrintStream {
        println0(csq)
        return this
    }

    override fun append(csq: CharSequence?, start: Int, end: Int): PrintStream {
        println0(csq)
        return this
    }

    override fun writeBytes(buf: ByteArray?) = println0(
        if (buf != null) {
            println0(String(buf, Charsets.UTF_8))
        } else {
            "null"
        },
    )

    override fun write(buf: ByteArray) {
        println0(String(buf, Charsets.UTF_8))
    }

    override fun write(buf: ByteArray, off: Int, len: Int) {
        println0(String(buf, Charsets.UTF_8))
    }

    // Below functions are single element print,
    // JLine do not support print above of them, so direct print

    override fun print(x: Any?) = kotlin.io.print(x)

    override fun print(x: Boolean) = kotlin.io.print(x)

    override fun print(x: Char) = kotlin.io.print(x)

    override fun print(x: CharArray) = kotlin.io.print(x.joinToString())

    override fun print(x: Double) = kotlin.io.print(x)

    override fun print(x: Float) = kotlin.io.print(x)

    override fun print(x: Int) = kotlin.io.print(x)

    override fun print(x: Long) = kotlin.io.print(x)

    override fun print(x: String?) = kotlin.io.print(x)

    override fun append(c: Char): PrintStream {
        kotlin.io.print(c)
        return this
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/console/SoraHighlighter.kt
================================================
package org.sorapointa.console

import org.jline.reader.Highlighter
import org.jline.reader.LineReader
import org.jline.utils.AttributedString
import org.jline.utils.AttributedStringBuilder
import org.jline.utils.AttributedStyle
import org.jline.utils.AttributedStyle.*
import org.sorapointa.command.CommandManager
import java.util.regex.Pattern

object SoraHighlighter : Highlighter {
    override fun highlight(reader: LineReader?, buffer: String?): AttributedString = runCatching {
        if (buffer.isNullOrEmpty()) return AttributedString("")
        if (buffer.isBlank()) return AttributedString(buffer)

        val builder = AttributedStringBuilder()
        val slices = buffer.splitsNoStrip().map { buffer.slice(it) }
        var hasFirstNonBlank = false
        slices.forEach {
            val isFirstNonBlank = it.isNotBlank() && !hasFirstNonBlank
            if (it.isNotBlank()) hasFirstNonBlank = true

            val style: AttributedStyle = when {
                isFirstNonBlank -> if (CommandManager.hasCommand(it)) {
                    DEFAULT.foreground(YELLOW)
                } else {
                    BOLD.foreground(RED)
                }
                it.startsWith("\"") -> DEFAULT.foreground(YELLOW)
                it.startsWith("-") -> DEFAULT.foreground(CYAN)
                else -> DEFAULT
            }

            builder.append(it, style)
        }
        return builder.toAttributedString()
    }.getOrElse { AttributedString(buffer ?: "") }

    override fun setErrorPattern(errorPattern: Pattern?) =
        throw UnsupportedOperationException("setErrorPattern is not unsupported")

    override fun setErrorIndex(errorIndex: Int) =
        throw UnsupportedOperationException("setErrorIndex is not unsupported")
}

private const val QUOTE = '"'
private const val SPLITTER = ' '
private const val ESCAPE = '\\'

private fun String.splitsNoStrip(): List<IntRange> {
    if (this.isBlank()) return listOf(indices)

    val slices = mutableListOf<IntRange>()

    var idx = 0
    var groupStart = 0
    var inQuote = false
    var inSplitter = false
    var inNormal = false

    while (idx < length) {
        val escaped = this.getOrNull(idx - 1) == ESCAPE
        val char = this[idx]
        val isLast = idx == lastIndex
        if (inNormal) {
            when {
                char == QUOTE || char == SPLITTER -> {
                    slices += groupStart until idx
                    inNormal = false
                }
                isLast -> {
                    slices += groupStart..idx
                    inNormal = false
                }
            }
        }
        if (char == QUOTE && !inQuote && isLast) {
            slices += idx..idx
        }
        if (inQuote && isLast) {
            slices += groupStart..idx
            inQuote = false
        }
        if (!escaped) {
            when (char) {
                QUOTE -> {
                    if (inQuote) {
                        inQuote = false
                        slices += groupStart..idx
                    } else {
                        groupStart = idx
                        inQuote = true
                    }
                }
                SPLITTER -> {
                    if (!inQuote) {
                        val hasNext = this.getOrNull(idx + 1) == SPLITTER
                        when {
                            // single space
                            !inSplitter && !hasNext -> {
                                slices += idx..idx
                            }
                            !inSplitter /* && hasNext */ -> {
                                groupStart = idx
                                inSplitter = true
                            }
                            inSplitter && !hasNext -> {
                                inSplitter = false
                                slices += groupStart..idx
                            }
                            /* inSplitter && hasNext -> just ignore*/
                        }
                    }
                }
            }
        }
        if (char != QUOTE && char != SPLITTER) {
            when {
                !inQuote && !inNormal && this.getOrNull(idx - 1) == SPLITTER && isLast -> {
                    slices += idx..idx
                }
                !inQuote && !inNormal -> {
                    inNormal = true
                    groupStart = idx
                }
            }
        }
        idx++
    }

    return slices
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/console/WebSocketConsole.kt
================================================
package org.sorapointa.console

import com.password4j.Password
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.websocket.*
import io.ktor.serialization.kotlinx.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.server.websocket.WebSockets
import io.ktor.websocket.*
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import mu.KotlinLogging
import org.jline.reader.EndOfFileException
import org.jline.reader.UserInterruptException
import org.sorapointa.command.CommandManager
import org.sorapointa.command.RemoteCommandSender
import org.sorapointa.data.provider.DataFilePersist
import org.sorapointa.dispatch.data.argon2Function
import org.sorapointa.utils.ModuleScope
import org.sorapointa.utils.configDirectory
import org.sorapointa.utils.networkJson
import java.io.File
import java.security.cert.X509Certificate
import java.time.Duration
import java.util.concurrent.ConcurrentHashMap
import javax.net.ssl.X509TrustManager
import kotlin.collections.set
import kotlin.system.exitProcess

private val logger = KotlinLogging.logger {}

@Serializable
internal sealed class WebConsolePacket

/**
 * @param username user to login
 * @param password password
 */
@Serializable
internal class VerifyReq(val username: String, val password: String) : WebConsolePacket()

@Serializable
internal class VerifyResp(val ok: Boolean) : WebConsolePacket()

@Serializable
internal class CommandReq(val command: String) : WebConsolePacket()

@Serializable
internal object CommandEndResp : WebConsolePacket()

@Serializable
internal class MessageNotify(val message: String) : WebConsolePacket()

internal object ConsoleUsers : DataFilePersist<ConsoleUsers.Data>(
    File(configDirectory, "consoleUsers.json"),
    Data(),
    Data.serializer(),
) {
    @Serializable
    data class Data(
        val tokenLength: Int = 128,
        val users: MutableMap<String, String> = ConcurrentHashMap<String, String>(),
    )

    override suspend fun init() = withContext(Dispatchers.IO) {
        super.init()
        save()
    }

    fun addOrUpdate(username: String, password: String) {
        val user = username.lowercase()
        data.users[user] = Password.hash(password).addSalt(user).with(argon2Function).result
    }

    fun verify(username: String, password: String): Boolean {
        val user = username.lowercase()
        val encrypted = data.users[user] ?: return false
        return Password.check(password, encrypted).addSalt(user).with(argon2Function)
    }
}

internal fun Application.setupWebConsoleServer() {
    install(WebSockets) {
        contentConverter = KotlinxWebsocketSerializationConverter(networkJson)
        pingPeriod = Duration.ofSeconds(30)
        timeout = Duration.ofSeconds(60)
    }

    routing {
        webSocket("/webconsole") {
            logger.info { "WebSocket Console Connected" }

            val closedNotifyJob = launch {
                val reason = closeReason.await()
                logger.info { "Closed: $reason" }
            }

            val atomicVerified = atomic(false)
            var verified by atomicVerified

            val remoteSender = RemoteCommandSender(this, null)

            launch {
                delay(30_000)
                if (!verified) {
                    close(CloseReason(CloseReason.Codes.NORMAL, "Client is not verified after 30 seconds"))
                }
            }

            incoming.consumeEach { frame ->
                try {
                    if (frame !is Frame.Text) {
                        close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Only accept for Text data"))
                        logger.info { "WebSocketConsole closed because of illegal data type ${frame.frameType}" }
                        return@consumeEach
                    }
                    val pkt = runCatching {
                        val json = frame.readText()
                        logger.debug { "Received json: $json" }
                        networkJson.decodeFromString<WebConsolePacket>(json)
                    }.onFailure {
                        if (it is CancellationException) {
                            throw it
                        } else {
                            logger.info(it) { "Unexpected exception:" }
                        }
                    }.getOrNull() ?: return@consumeEach
                    when (pkt) {
                        is VerifyReq -> {
                            if (verified) {
                                sendSerialized<WebConsolePacket>(VerifyResp(true))
                                return@consumeEach
                            }

                            val pwd = pkt.password
                            val success = ConsoleUsers.verify(pkt.username, pwd)
                            verified = success
                            if (success) {
                                logger.info { "Successfully verified for user '${pkt.username}'" }
                            }
                            Console.consoleUsers.add(remoteSender)
                            sendSerialized<WebConsolePacket>(VerifyResp(success))
                        }
                        is CommandReq -> {
                            if (!verified) {
                                close(CloseReason(CloseReason.Codes.VIOLATED_POLICY, "Not verified"))
                                return@consumeEach
                            }
                            CommandManager.invokeCommand(remoteSender, pkt.command)
                            sendSerialized<WebConsolePacket>(CommandEndResp)
                        }
                        else ->
                            close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Illegal Data Class"))
                    }
                } catch (e: ClosedReceiveChannelException) {
                    logger.info(e) { "WebSocketConsole Closed by client" }
                }
            }
            closedNotifyJob.join()
        }
    }
}

private val client by lazy {
    HttpClient(CIO) {
        install(io.ktor.client.plugins.websocket.WebSockets) {
            contentConverter = KotlinxWebsocketSerializationConverter(networkJson)
        }
        engine {
            https {
                trustManager = object : X509TrustManager {
                    override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
                    override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
                    override fun getAcceptedIssuers(): Array<X509Certificate>? = null
                }
            }
        }
    }
}

internal suspend fun setupConsoleClient(username: String, password: String, url: String) {
    val scope = ModuleScope("ConsoleClient")
    client.webSocket(url) {
        logger.info { "Try connecting remote console $url..." }
        sendSerialized<WebConsolePacket>(
            VerifyReq(
                username,
                password,
            ),
        )

        val closedNotifyJob = scope.launch {
            val reason = closeReason.await()
            logger.info { "Closed: $reason" }
            exitProcess(0)
        }

        val mutex = Mutex()

        val incomingJob = scope.launch {
            incoming.consumeEach {
                val json = (it as? Frame.Text)?.readText() ?: return@consumeEach
                logger.debug { "Received json: $json" }
                when (val pkt = networkJson.decodeFromString<WebConsolePacket>(json)) {
                    is VerifyResp -> {
                        if (!pkt.ok) {
                            logger.error { "Server denied connection, maybe username or password error" }
                            exitProcess(0)
                        }
                        logger.info { "Successfully connected to remote!" }
                    }
                    is MessageNotify -> println(pkt.message)
                    is CommandEndResp -> mutex.unlock()
                    else -> {
                        // do nothing
                    }
                }
            }
        }

        val inputJob = scope.launch {
            while (isActive) {
                try {
                    mutex.lock()
                    sendSerialized<WebConsolePacket>(CommandReq(Console.readln("> ")))
                } catch (e: CancellationException) {
                    throw e
                } catch (e: UserInterruptException) {
                    println("<Interrupted> use Ctrl + D to exit client")
                } catch (e: EndOfFileException) {
                    exitProcess(0)
                }
            }
        }

        listOf(closedNotifyJob, incomingJob, inputJob).joinAll()
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/events/PlayerEvent.kt
================================================
@file:Suppress("unused")

package org.sorapointa.events

import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import org.sorapointa.event.AbstractEvent
import org.sorapointa.event.CancelableEvent
import org.sorapointa.game.Player
import org.sorapointa.proto.PacketHead
import org.sorapointa.server.network.NetworkHandler
import org.sorapointa.server.network.OutgoingPacket

abstract class PlayerEvent : AbstractEvent() {
    abstract val player: Player
}

// Login order: Init -> (FirstCreate) -> Login -> Disconnect

abstract class RecvPacketTriggerEvent : PlayerEvent() {
    abstract val metadata: PacketHead?

    fun sendPacket(packet: OutgoingPacket<*>) {
        player.sendPacket(packet, metadata)
    }

    suspend fun sendPacketSync(packet: OutgoingPacket<*>) {
        player.sendPacketSync(packet, metadata)
    }
}

class PlayerInitEvent(
    override val player: Player,
) : PlayerEvent()

class PlayerFirstCreateEvent(
    override val player: Player,
    override val metadata: PacketHead?,
    val pickAvatarId: Int,
) : RecvPacketTriggerEvent()

class PlayerLoginEvent(
    override val player: Player,
    override val metadata: PacketHead? = null, // metadata from PlayerLoginReq
) : RecvPacketTriggerEvent()

class PlayerDisconnectEvent(
    override val player: Player,
) : PlayerEvent()

internal abstract class NetworkEvent : AbstractEvent() {
    abstract val networkHandler: NetworkHandler
}

class HandlePacketEvent<T : Message<*, *>>(
    override val player: Player,
    val dataPacket: T,
    val metadata: PacketHead,
    val adapter: ProtoAdapter<T>,
) : PlayerEvent(), CancelableEvent

internal class HandlePreLoginPacketEvent<T : Message<*, *>>(
    override val networkHandler: NetworkHandler,
    val dataPacket: T,
    val metadata: PacketHead,
    val adapter: ProtoAdapter<T>,
) : NetworkEvent(), CancelableEvent

internal class SendPacketEvent<T : Message<*, *>>(
    override val networkHandler: NetworkHandler,
    val dataPacket: OutgoingPacket<T>,
) : NetworkEvent(), CancelableEvent


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/game/AvatarEntity.kt
================================================
package org.sorapointa.game

import org.sorapointa.dataloader.common.ElementType
import org.sorapointa.dataloader.common.EntityIdType
import org.sorapointa.dataloader.common.FightProp.*
import org.sorapointa.dataloader.common.PlayerProp
import org.sorapointa.dataloader.def.*
import org.sorapointa.game.data.Position
import org.sorapointa.proto.*
import org.sorapointa.utils.*
import org.sorapointa.utils.encoding.bkdrHash
import kotlin.contracts.contract

interface AvatarEntity : SceneEntity {

    val ownerPlayer: Player

    val avatar: AbstractAvatar

    val equipWeaponEntityId: Int?
        get() = avatar.equipWeapon?.let {
            ownerPlayer.getOrNextEntityId(EntityIdType.WEAPON, it.guid)
        }
}

abstract class AbstractAvatarEntity : SceneEntityBase(), AvatarEntity {

    internal abstract val avatarProto: AvatarProto
}

class AvatarEntityImpl(
    override val ownerPlayer: Player,
    override val avatar: AbstractAvatar,
) : AbstractAvatarEntity() {

    override val id: Int by lazy {
        ownerPlayer.getOrNextEntityId(EntityIdType.AVATAR, avatar.guid)
    }

    override val scene: Scene = ownerPlayer.scene

    override val position: Position
        get() = ownerPlayer.avatarComp.pbOnlyCurPos

    override val entityType = ProtEntityType.PROT_ENTITY_TYPE_AVATAR

    override val avatarProto: AvatarProto = AvatarProto(this)

    override val entityProto: SceneEntityProto<*> = avatarProto

    override fun toString(): String =
        "Avatar[guid: ${avatar.guid}, position: $position]"
}

@Suppress("NOTHING_TO_INLINE")
inline fun Map<Int, PropValue>.toFlattenPropMap(): Map<Int, Long> =
    map { it.key to it.value.val_ }.toMap()

internal class AvatarProto(
    override val entity: AvatarEntity,
) : AbstractSceneEntityProto<AvatarEntity>() {

    private val avatar = entity.avatar
    private val excelData = avatar.excelData
    private val selectedDepot
        get() = avatar.depot.getSelectedDepot()

    private val protoExcelInfo by lazy {
        AvatarExcelInfo(
            prefab_path_hash = excelData.prefabPathHash.toLong(),
            prefab_path_remote_hash = excelData.prefabPathRemoteHash.toLong(),
            controller_path_hash = excelData.controllerPathHash.toLong(),
            controller_path_remote_hash = excelData.controllerPathRemoteHash.toLong(),
            combat_config_hash = excelData.combatConfigHash.toLong(),
        )
    }

    val protoPropMap
        get() = buildList {
            add(PlayerProp.PROP_LEVEL map avatar.level)
            if (avatar is FormalAvatar) {
                add(PlayerProp.PROP_EXP map avatar.exp)
            }
            add(PlayerProp.PROP_BREAK_LEVEL map avatar.promoteLevel)
            add(PlayerProp.PROP_SATIATION_VAL mapFloat avatar.satiationVal)
            add(PlayerProp.PROP_SATIATION_PENALTY_TIME mapFloat avatar.satiationPenaltyTime)
        }.toMap()

    private val protoSceneReliquaryInfoList
        get() = avatar.equipList.mapNotNull { if (it is ReliquaryItem) it.toSceneReliquaryInfoProto() else null }

    private val protoFightPropMap
        get() = buildList {
            add(FIGHT_PROP_BASE_HP map avatar.excelData.hpBase)
//            add(FIGHT_PROP_HP map avatar.curHp)
//            add(FIGHT_PROP_HP_PERCENT map entity.data.hpPercent)
            add(FIGHT_PROP_BASE_ATTACK map avatar.excelData.attackBase)
//            add(FIGHT_PROP_ATTACK map entity.data.attack)
//            add(FIGHT_PROP_ATTACK_PERCENT map entity.data.attackPercent)
            add(FIGHT_PROP_BASE_DEFENSE map avatar.excelData.defenseBase)
//            add(FIGHT_PROP_DEFENSE map entity.data.defense)
//            add(FIGHT_PROP_DEFENSE_PERCENT map entity.data.defensePercent)
//            add(FIGHT_PROP_BASE_SPEED map entity.data.baseSpeed)
//            add(FIGHT_PROP_SPEED_PERCENT map entity.data.speedPercent)
            add(FIGHT_PROP_CRITICAL map avatar.excelData.critical)
//            add(FIGHT_PROP_ANTI_CRITICAL map entity.data.antiCritical)
            add(FIGHT_PROP_CRITICAL_HURT map avatar.excelData.criticalHurt)
//            add(FIGHT_PROP_CHARGE_EFFICIENCY map entity.data.chargeEfficiency)
//            add(FIGHT_PROP_ADD_HURT map entity.data.addHurt)
//            add(FIGHT_PROP_SUB_HURT map entity.data.subHurt)
//            add(FIGHT_PROP_HEAL_ADD map entity.data.healAdd)
//            add(FIGHT_PROP_HEALED_ADD map entity.data.healedAdd)
//            add(FIGHT_PROP_ELEMENT_MASTERY map entity.data.elementMastery)
//            add(FIGHT_PROP_PHYSICAL_SUB_HURT map entity.data.physicalSubHurt)
//            add(FIGHT_PROP_PHYSICAL_ADD_HURT map entity.data.physicalAddHurt)
//            add(FIGHT_PROP_DEFENCE_IGNORE_RATIO map entity.data.defenceIgnoreRatio)
//            add(FIGHT_PROP_DEFENCE_IGNORE_DELTA map entity.data.defenceIgnoreDelta)
//            add(FIGHT_PROP_FIRE_ADD_HURT map entity.data.fireAddHurt)
//            add(FIGHT_PROP_ELEC_ADD_HURT map entity.data.electricAddHurt)
//            add(FIGHT_PROP_WATER_ADD_HURT map entity.data.waterAddHurt)
//            add(FIGHT_PROP_GRASS_ADD_HURT map entity.data.grassAddHurt)
//            add(FIGHT_PROP_WIND_ADD_HURT map entity.data.windAddHurt)
//            add(FIGHT_PROP_ROCK_ADD_HURT map entity.data.rockAddHurt)
//            add(FIGHT_PROP_ICE_ADD_HURT map entity.data.iceAddHurt)
//            add(FIGHT_PROP_HIT_HEAD_ADD_HURT map entity.data.hitHeadAddHurt)
//            add(FIGHT_PROP_FIRE_SUB_HURT map entity.data.fireSubHurt)
//            add(FIGHT_PROP_ELEC_SUB_HURT map entity.data.electricSubHurt)
//            add(FIGHT_PROP_WATER_SUB_HURT map entity.data.waterSubHurt)
//            add(FIGHT_PROP_GRASS_SUB_HURT map entity.data.grassSubHurt)
//            add(FIGHT_PROP_WIND_SUB_HURT map entity.data.windSubHurt)
//            add(FIGHT_PROP_ROCK_SUB_HURT map entity.data.rockSubHurt)
//            add(FIGHT_PROP_ICE_SUB_HURT map entity.data.iceSubHurt)
//            add(FIGHT_PROP_SKILL_CD_MINUS_RATIO map entity.data.skillCDMinusRatio)
//            add(FIGHT_PROP_SHIELD_COST_MINUS_RATIO map entity.data.shieldCostMinusRatio)
            if (selectedDepot.elementType != ElementType.None) {
                selectedDepot.maxEnergy?.let {
                    add(selectedDepot.elementType.maxEnergyProp map it)
                }
                add(selectedDepot.elementType.curEnergyProp map avatar.curElemEnergy)
            }
            // Must load maxHp before curHp to make sure curHp <= maxHp
            add(FIGHT_PROP_MAX_HP map entity.maxHp)
            add(FIGHT_PROP_CUR_HP map entity.curHp)
            add(FIGHT_PROP_CUR_ATTACK map entity.curAttack)
            add(FIGHT_PROP_CUR_DEFENSE map entity.curDefense)
            FIGHT_PROP_CUR_SPEED map entity.curSpeed
        }.toMap().filter { it.value != 0f }

    override val fightPropPairList: List<FightPropPair>
        get() = protoFightPropMap.map { it.key fightProp it.value } // crazy hoyo

    fun toAvatarInfoProto(): AvatarInfo = AvatarInfo(
        avatar_id = avatar.avatarId,
        guid = avatar.guid,
        prop_map = protoPropMap,
        life_state = avatar.lifeState,
        equip_guid_list = avatar.equipGuidList,
        talent_id_list = selectedDepot.talentList,
        fight_prop_map = protoFightPropMap,
        skill_map = selectedDepot.getProtoSkillMap(),
        skill_depot_id = avatar.depot.selectedDepotId,
        // TODO: Fetter system
        // Official server packet doesn't have `coreProudSkillLevel` field
        core_proud_skill_level = selectedDepot.coreProudSkillLevel,
        inherent_proud_skill_list = selectedDepot.inherentProudSkillList,
        skill_level_map = selectedDepot.getSkillLevelMap(),
        // TODO: Proud skill extra level map
        // TODO: isFocus is whether one of teammate of selected team
        avatar_type = avatar.avatarType.value,
        // TODO: Team resonance
        wearing_flycloak_id = avatar.flyCloakId.value,
        // Still don't know what is `equip_affix_list` in this packet
        // It seems related to weapon passive skill and cd time
        // 比如西风和祭礼系列武器的被动冷却时间 - 持久化数据库存储,就像 protoSkillMap
        born_time = avatar.bornTime,
        // TODO: `pending_promote_reward_list`
        costume_id = avatar.costumeId,
        excel_info = protoExcelInfo,
    )

    override fun SceneEntityInfo.toProto(): SceneEntityInfo {
        val avatarProto = getSceneAvatarInfo()
        return copy(avatar = avatarProto)
    }

    fun getSceneAvatarInfo(): SceneAvatarInfo {
        val weapon = if (avatar.equipWeapon != null && entity.equipWeaponEntityId != null) {
            avatar.equipWeapon!!.toSceneWeaponInfoProto(entity.equipWeaponEntityId!!)
        } else {
            null
        }

        return SceneAvatarInfo(
            uid = entity.ownerPlayer.uid,
            avatar_id = avatar.avatarId,
            guid = avatar.guid,
            peer_id = entity.ownerPlayer.peerId,
            equip_id_list = avatar.equipIdList,
            skill_depot_id = avatar.depot.selectedDepotId,
            talent_id_list = selectedDepot.talentList,
            weapon = weapon,
            reliquary_list = protoSceneReliquaryInfoList,
            core_proud_skill_level = selectedDepot.coreProudSkillLevel,
            inherent_proud_skill_list = selectedDepot.inherentProudSkillList,
            skill_level_map = selectedDepot.getSkillLevelMap(),
            // TODO: Proud skill extra level map
            // TODO: Server Buff List
            // TODO: Team resonance
            wearing_flycloak_id = avatar.flyCloakId.value,
            born_time = avatar.bornTime,
            // TODO: `pending_promote_reward_list`
            costume_id = avatar.costumeId,
            // curVehicleInfo
            excel_info = protoExcelInfo,
            // animHash
        )
    }

    fun getAbilityControlBlock(): AbilityControlBlock {
        // TODO: hardcode
        if (avatar.avatarId == 10000005) {
            val abilities = listOf(
                "",
                "Avatar_PlayerBoy_NormalAttack_DamageHandler",
                "Avatar_Player_FlyingBomber",
                "Avatar_Player_CamCtrl",
                "Avatar_PlayerBoy_FallingAnthem",
                "GrapplingHookSkill_Ability",
                "Avatar_DefaultAbility_VisionReplaceDieInvincible",
                "Avatar_DefaultAbility_AvartarInShaderChange",
                "Avatar_SprintBS_Invincible",
                "Avatar_Freeze_Duration_Reducer",
                "Avatar_Attack_ReviveEnergy",
                "Avatar_Component_Initializer",
                "Avatar_HDMesh_Controller",
                "Avatar_Trampoline_Jump_Controller",
                "Avatar_PlayerBoy_ExtraAttack_Common",
                "Avatar_FallAnthem_Achievement_Listener",
            )
            return AbilityControlBlock(
                ability_embryo_list = abilities.map {
                    AbilityEmbryo(
                        ability_id = abilities.indexOf(it),
                        ability_name_hash = bkdrHash(it),
                        ability_override_name_hash = 1178079449,
                    )
                },
            )
        } else if (avatar.avatarId == 10000007) {
            val abilities = listOf(
                "",
                "Avatar_PlayerGirl_NormalAttack_DamageHandler",
                "Avatar_Player_FlyingBomber",
                "Avatar_Player_CamCtrl",
                "Avatar_PlayerGirl_FallingAnthem",
                "GrapplingHookSkill_Ability",
                "Avatar_DefaultAbility_VisionReplaceDieInvincible",
                "Avatar_DefaultAbility_AvartarInShaderChange",
                "Avatar_SprintBS_Invincible",
                "Avatar_Freeze_Duration_Reducer",
                "Avatar_Attack_ReviveEnergy",
                "Avatar_Component_Initializer",
                "Avatar_HDMesh_Controller",
                "Avatar_Trampoline_Jump_Controller",
                "Avatar_PlayerGirl_ExtraAttack_Common",
                "Avatar_FallAnthem_Achievement_Listener",
            )
            return AbilityControlBlock(
                ability_embryo_list = abilities.map {
                    AbilityEmbryo(
                        ability_id = abilities.indexOf(it),
                        ability_name_hash = bkdrHash(it),
                        ability_override_name_hash = 1178079449,
                    )
                },
            )
        }
        return AbilityControlBlock()
    }
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun AvatarEntity.impl(): AbstractAvatarEntity {
    contract { returns() implies (this@impl is AbstractAvatarEntity) }
    check(this is AbstractAvatarEntity) {
        "A Avatar instance is not instance of AbstractAvatar. Your instance: ${this::class.qualifiedOrSimple}"
    }
    return this
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/game/Player.kt
================================================
package org.sorapointa.game

import com.squareup.wire.Message
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Job
import mu.KotlinLogging
import org.sorapointa.Sorapointa
import org.sorapointa.command.CommandSender
import org.sorapointa.dataloader.common.EntityIdType
import org.sorapointa.dataloader.common.PlayerProp
import org.sorapointa.dispatch.data.Account
import org.sorapointa.event.*
import org.sorapointa.events.PlayerDisconnectEvent
import org.sorapointa.events.PlayerEvent
import org.sorapointa.events.PlayerInitEvent
import org.sorapointa.events.PlayerLoginEvent
import org.sorapointa.game.data.PlayerData
import org.sorapointa.proto.MpSettingType
import org.sorapointa.proto.PacketHead
import org.sorapointa.proto.SoraPacket
import org.sorapointa.proto.bin.PlayerDataBin
import org.sorapointa.server.network.*
import org.sorapointa.utils.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

private val logger = KotlinLogging.logger {}

interface Player : CommandSender {

    override var locale: Locale?

    val state: StateController<PlayerStateI.State, PlayerStateI, Player>

    val account: Account

    val uid: Int

    val scene: Scene

    val world: World

    val playerProto: PlayerProto

    val basicComp: PlayerBasicComp
    val avatarComp: PlayerAvatarComp
    val itemComp: PlayerItemComp
    val sceneComp: PlayerSceneComp
    val socialComp: PlayerSocialComp

    val guidEntityMap: MutableMap<Long, Int> // Guid -> EntityId

    val peerId: Int

    val enterSceneToken: Int

    val isMpModeAvailable: Boolean

    suspend fun init()

    suspend fun close()

    fun hasLoadedScene(id: Int, loadNow: Boolean = true): Boolean

    fun getNextEnterSceneToken(set: Boolean = true): Int

    fun getNextEntityId(idType: EntityIdType): Int

    fun getOrNextEntityId(idType: EntityIdType, guid: Long): Int

    fun <T : Message<*, *>> sendPacket(
        packet: OutgoingPacket<T>,
        metadata: PacketHead? = null,
    ): Job

    suspend fun <T : Message<*, *>> sendPacketSync(
        packet: OutgoingPacket<T>,
        metadata: PacketHead? = null,
    )

    fun forwardHandlePacket(packet: SoraPacket): Job

    suspend fun saveData()
}

interface PlayerStateI : WithState<PlayerStateI.State> {
    enum class State {
        LOGIN,
        OK,
        CLOSED,
    }
}

class PlayerImpl internal constructor(
    override val account: Account,
    private val data: PlayerData,
    initDataBin: PlayerDataBin,
    internal val networkHandler: NetworkHandler,
    parentCoroutineContext: CoroutineContext = EmptyCoroutineContext,
) : Player {

    override val uid: Int = account.id.value

    override var locale: Locale? = data.locale

    private val scope = ModuleScope(toString(), parentCoroutineContext)

    override val state by lazy {
        StateController<PlayerStateI.State, PlayerStateI, Player>(
            scope = scope,
            parentStateClass = this,
            Login(this),
        )
    }

    override val basicComp by lazy {
        PlayerBasicComp(this, initDataBin.basic_bin ?: error("PlayerBasicComp is null"))
    }

    override val avatarComp by lazy {
        PlayerAvatarComp(this, initDataBin.avatar_bin ?: error("PlayerAvatarComp is null"))
    }

    override val itemComp by lazy {
        PlayerItemComp(this, initDataBin.item_bin ?: error("PlayerItemComp is null"))
    }

    override val sceneComp by lazy {
        PlayerSceneComp(this, initDataBin.scene_bin ?: error("PlayerSceneComp is null"))
    }

    override val socialComp by lazy {
        PlayerSocialComp(this, initDataBin.social_bin ?: error("PlayerSocialComp is null"))
    }

    override val scene: Scene
        get() = _scene.value

    private val _scene by lazy {
        atomic(SceneImpl(this, sceneComp.myCurSceneId))
    }

    override val world by lazy {
        WorldImpl(this, scene)
    }

    override val playerProto by lazy {
        PlayerProto(this)
    }

    override val guidEntityMap: MutableMap<Long, Int> = ConcurrentHashMap()

    override val peerId: Int
        get() = scene.playerMap[this] ?: error("Could not find player peer id from scene $scene")

    private val _enterSceneToken = atomic(getNextEnterSceneToken(false))

    override val enterSceneToken: Int
        get() = _enterSceneToken.value

    private val loadedSceneList = ConcurrentLinkedDeque<Int>()

    private val _isMpModeAvailable = atomic(true)

    override val isMpModeAvailable: Boolean
        get() = _isMpModeAvailable.value

    override fun hasLoadedScene(id: Int, loadNow: Boolean): Boolean =
        loadedSceneList.contains(id).also {
            if (!it && loadNow) loadedSceneList.add(id)
        }

    override fun getNextEnterSceneToken(set: Boolean): Int =
        (1000..99999).random().also {
            if (set) _enterSceneToken.value = it
        }

    override fun getNextEntityId(idType: EntityIdType): Int =
        scene.owner.world.getNextEntityId(idType)

    override fun getOrNextEntityId(idType: EntityIdType, guid: Long): Int =
        guidEntityMap[guid] ?: getNextEntityId(idType).also { entityId ->
            guidEntityMap[guid] = entityId
        }

    override suspend fun close() {
        networkHandler.close()
        state.setState(Closed(this))
        scope.dispose()
    }

    override suspend fun init() {
        logger.info { toString() + " has joined to the server" }
        state.init()
        networkHandler.state.observeStateChange { _, state ->
            if (state == NetworkHandlerStateI.State.CLOSED) {
                close()
            }
        }
        basicComp.init()
        avatarComp.init()
        itemComp.init()
        sceneComp.init()
        socialComp.init()
        scene.init()
        data.player = this
        PlayerInitEvent(this).broadcast()
    }

    override suspend fun sendMessage(msg: String) {
    }

    override fun <T : Message<*, *>> sendPacket(
        packet: OutgoingPacket<T>,
        metadata: PacketHead?,
    ): Job = networkHandler.sendPacket(packet, metadata)

    override suspend fun <T : Message<*, *>> sendPacketSync(
        packet: OutgoingPacket<T>,
        metadata: PacketHead?,
    ) = networkHandler.sendPacketSync(packet, metadata)

    override fun forwardHandlePacket(
        packet: SoraPacket,
    ): Job = networkHandler.handlePacket(packet)

    override suspend fun saveData() {
        data.save()
    }

    inner class Login(private val player: PlayerImpl) : PlayerStateI {

        override val state: PlayerStateI.State = PlayerStateI.State.LOGIN

        internal suspend fun onLogin(metadata: PacketHead? = null) {
            PlayerLoginEvent(player, metadata).broadcast()
            basicComp.updateLastLoginTime()
        }
    }

    inner class OK : PlayerStateI {

        override val state: PlayerStateI.State = PlayerStateI.State.OK
    }

    inner class Closed(private val player: PlayerImpl) : PlayerStateI {

        override val state: PlayerStateI.State = PlayerStateI.State.CLOSED

        override suspend fun startState() {
            // onClosed
            player.data.save()
            logger.info { toString() + " has disconnected to the server" }
            PlayerDisconnectEvent(player).broadcast()
            Sorapointa.removePlayer(player.uid)
            basicComp.updateLastLoginTime()
        }
    }

    override fun toString(): String =
        "Player[id: $uid, host: ${networkHandler.host}]"
}

class PlayerProto(
    private val player: Player,
) {

    val propMap
        get() = mapOf(
            PlayerProp.PROP_MAX_SPRING_VOLUME map 100, // TODO: hardcode
            PlayerProp.PROP_CUR_SPRING_VOLUME map player.avatarComp.curSpringVolume,
            PlayerProp.PROP_IS_SPRING_AUTO_USE map player.avatarComp.isSpringAutoUse.toInt(),
            PlayerProp.PROP_SPRING_AUTO_USE_PERCENT map player.avatarComp.springAutoUsePercent,
            PlayerProp.PROP_IS_FLYABLE map player.avatarComp.isFlyable.toInt(),
            PlayerProp.PROP_IS_TRANSFERABLE map player.avatarComp.isTransferable.toInt(),
            PlayerProp.PROP_MAX_STAMINA mapFloat player.basicComp.persistStaminaLimit,
            PlayerProp.PROP_CUR_PERSIST_STAMINA mapFloat player.basicComp.curPersistStamina,
            PlayerProp.PROP_CUR_TEMPORARY_STAMINA mapFloat player.basicComp.curTemporaryStamina,
            PlayerProp.PROP_PLAYER_LEVEL map player.basicComp.level,
            PlayerProp.PROP_EXP map player.basicComp.exp,
            PlayerProp.PROP_PLAYER_HCOIN map player.itemComp.primoGem,
            PlayerProp.PROP_PLAYER_SCOIN map player.itemComp.mora,
            PlayerProp.PROP_PLAYER_MP_SETTING_TYPE map
                MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY.value, // TODO: hardcode
            PlayerProp.PROP_IS_MP_MODE_AVAILABLE map 1, // TODO: hardcode
            PlayerProp.PROP_PLAYER_WORLD_LEVEL map player.sceneComp.world.level,
            PlayerProp.PROP_PLAYER_RESIN map 160, // TODO: hardcode
            PlayerProp.PROP_PLAYER_MCOIN map player.itemComp.genesisCrystal,
            PlayerProp.PROP_PLAYER_LEGENDARY_KEY map player.itemComp.legendaryKey,
            PlayerProp.PROP_IS_HAS_FIRST_SHARE map player.socialComp.isHaveFirstShare.toInt(),
            PlayerProp.PROP_PLAYER_FORGE_POINT map 0, // TODO: hardcode
            PlayerProp.PROP_PLAYER_WORLD_LEVEL_ADJUST_CD map 0, // TODO: hardcode
            PlayerProp.PROP_PLAYER_LEGENDARY_DAILY_TASK_NUM map 0, // TODO: hardcode
            PlayerProp.PROP_PLAYER_HOME_COIN map player.itemComp.homeCoin,
        )
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun Player.impl(): PlayerImpl {
    contract { returns() implies (this@impl is PlayerImpl) }
    check(this is PlayerImpl) {
        "A Player instance is not instance of PlayerImpl. Your instance: ${this::class.qualifiedOrSimple}"
    }
    return this
}

inline fun <reified T : PlayerEvent> Player.registerEventListener(
    noinline listener: suspend T.() -> Unit,
) {
    registerListener<T> {
        if (it.player == this) {
            listener(it)
        }
    }
}

inline fun <reified T : PlayerEvent> Player.registerEventListener(
    priority: EventPriority,
    noinline listener: suspend T.() -> Unit,
) {
    registerListener<T>(priority) {
        if (it.player == this) {
            listener(it)
        }
    }
}

inline fun <reified T : PlayerEvent> Player.registerEventBlockListener(
    noinline listener: suspend T.() -> Unit,
) {
    registerBlockListener<T> {
        if (it.player == this) {
            listener(it)
        }
    }
}

inline fun <reified T : PlayerEvent> Player.registerEventBlockListener(
    priority: EventPriority,
    noinline listener: suspend T.() -> Unit,
) {
    registerBlockListener<T>(priority) {
        if (it.player == this) {
            listener(it)
        }
    }
}


================================================
FILE: sorapointa-core/src/main/kotlin/org/sorapointa/game/PlayerAvatarComp.kt
================================================
package org.sorapointa.game

import kotlinx.atomicfu.atomic
import org.sorapointa.dataloader.common.*
import org.sorapointa.dataloader.def.*
import org.sorapointa.events.PlayerFirstCreateEvent
import org.sorapointa.events.PlayerLoginEvent
import org.sorapointa.game.data.Position
import org.sorapointa.game.data.START_POSITION
import org.sorapointa.proto.AvatarSkillInfo
import org.sorapointa.proto.AvatarTeam
import org.sorapointa.proto.SceneTeamAvatar
import org.sorapointa.proto.bin.*
import org.sorapointa.proto.bin.AvatarType
import org.sorapointa.server.network.AvatarDataNotifyPacket
import org.sorapointa.server.network.OpenStateUpdateNotifyPacket
import org.sorapointa.utils.buildConcurrencyMap
import org.sorapointa.utils.nowSeconds
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue

@Suppress("MemberVisibilityCanBePrivate")
class PlayerAvatarComp(
    override val player: Player,
    private val initAvatarCompBin: PlayerAvatarCompBin,
) : PlayerModule {

    private val avatarMap = buildConcurrencyMap(
        initCapacity = initAvatarCompBin.avatar_list.size,
    ) {
        initAvatarCompBin.avatar_list.forEach {
       
Download .txt
gitextract_y0piscv0/

├── .editorconfig
├── .git-hooks/
│   ├── commit-msg
│   └── pre-commit
├── .gitattributes
├── .github/
│   └── workflows/
│       ├── api_check.yml
│       └── test.yml
├── .gitignore
├── .idea/
│   └── encodings.xml
├── LICENSE
├── build.gradle.kts
├── buildSrc/
│   ├── build.gradle.kts
│   ├── settings.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               ├── BuildConfigExtension.kt
│               ├── GitHook.kt
│               ├── JniHeader.kt
│               ├── OptInAnnotations.kt
│               ├── Properties.kt
│               ├── ResourcesCopy.kt
│               ├── Test.kt
│               ├── sorapointa-conventions.gradle.kts
│               └── sorapointa-publish.gradle.kts
├── docs/
│   ├── CONTRIBUTING.md
│   ├── CONTRIBUTING.zh-CN.md
│   ├── README.md
│   ├── README.zh-CN.md
│   └── guides/
│       ├── concurrency.md
│       ├── concurrency.zh-CN.md
│       ├── database.md
│       ├── database.zh-CN.md
│       ├── kotlin-atomicfu.md
│       ├── kotlin-atomicfu.zh-CN.md
│       ├── unit-test.md
│       └── unit-test.zh-CN.md
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── renovate.json
├── settings.gradle.kts
├── sorapointa-core/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── kotlin/
│       │   │   └── org/
│       │   │       └── sorapointa/
│       │   │           ├── CoreBundle.kt
│       │   │           ├── Main.kt
│       │   │           ├── Sorapointa.kt
│       │   │           ├── SorapointaConfig.kt
│       │   │           ├── command/
│       │   │           │   ├── Command.kt
│       │   │           │   ├── CommandLocalization.kt
│       │   │           │   ├── CommandManager.kt
│       │   │           │   ├── CommandSender.kt
│       │   │           │   ├── ConsoleCommandSender.kt
│       │   │           │   ├── defaults/
│       │   │           │   │   ├── Defaults.kt
│       │   │           │   │   ├── console/
│       │   │           │   │   │   ├── ConsoleUser.kt
│       │   │           │   │   │   └── Quit.kt
│       │   │           │   │   └── general/
│       │   │           │   │       ├── Help.kt
│       │   │           │   │       ├── ListPlayer.kt
│       │   │           │   │       ├── LocaleCommand.kt
│       │   │           │   │       └── Version.kt
│       │   │           │   └── utils/
│       │   │           │       └── Options.kt
│       │   │           ├── console/
│       │   │           │   ├── Completer.kt
│       │   │           │   ├── Console.kt
│       │   │           │   ├── JLineRedirector.kt
│       │   │           │   ├── SoraHighlighter.kt
│       │   │           │   └── WebSocketConsole.kt
│       │   │           ├── events/
│       │   │           │   └── PlayerEvent.kt
│       │   │           ├── game/
│       │   │           │   ├── AvatarEntity.kt
│       │   │           │   ├── Player.kt
│       │   │           │   ├── PlayerAvatarComp.kt
│       │   │           │   ├── PlayerComp.kt
│       │   │           │   ├── PlayerItemComp.kt
│       │   │           │   ├── Scene.kt
│       │   │           │   ├── SceneEntity.kt
│       │   │           │   ├── World.kt
│       │   │           │   └── data/
│       │   │           │       ├── GameConstants.kt
│       │   │           │       ├── PlayerData.kt
│       │   │           │       ├── Position.kt
│       │   │           │       └── SorapointaStoreEntry.kt
│       │   │           ├── server/
│       │   │           │   ├── ServerNetwork.kt
│       │   │           │   └── network/
│       │   │           │       ├── NetworkHandler.kt
│       │   │           │       ├── OutgoingPacket.kt
│       │   │           │       ├── PacketHandler.kt
│       │   │           │       ├── PacketHandlerImpl.kt
│       │   │           │       └── SoraPacket.kt
│       │   │           └── utils/
│       │   │               ├── Console.kt
│       │   │               ├── GameUtils.kt
│       │   │               ├── NetworkUtils.kt
│       │   │               ├── OptionalContainer.kt
│       │   │               ├── PropDelegate.kt
│       │   │               └── TypoSuggestor.kt
│       │   └── resources/
│       │       ├── logback.xml
│       │       └── messages/
│       │           ├── CoreBundle.properties
│       │           └── CoreBundle_zh_CN.properties
│       └── test/
│           ├── kotlin/
│           │   └── org/
│           │       └── sorapointa/
│           │           ├── command/
│           │           │   └── defaults/
│           │           │       └── HelpTest.kt
│           │           └── logger/
│           │               └── LogTest.kt
│           └── resources/
│               └── logback-test.xml
├── sorapointa-crypto/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── crypto/
│                           └── Crypto.kt
├── sorapointa-dataloader/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── dataloader/
│       │                   ├── DataLoader.kt
│       │                   ├── common/
│       │                   │   ├── AddProp.kt
│       │                   │   ├── CurveInfo.kt
│       │                   │   ├── Enum.kt
│       │                   │   ├── ItemParamData.kt
│       │                   │   ├── ItemParamStringData.kt
│       │                   │   ├── OpenCondData.kt
│       │                   │   ├── PointData.kt
│       │                   │   ├── PropGrowCurve.kt
│       │                   │   ├── RewardItemData.kt
│       │                   │   └── ScenePointConfig.kt
│       │                   └── def/
│       │                       ├── AvatarExcelData.kt
│       │                       ├── AvatarSkillData.kt
│       │                       ├── AvatarSkillDepotData.kt
│       │                       ├── MaterialData.kt
│       │                       ├── ReliquaryAffixData.kt
│       │                       ├── ReliquaryData.kt
│       │                       ├── ReliquaryLevelData.kt
│       │                       ├── ReliquaryMainPropData.kt
│       │                       ├── ReliquarySetData.kt
│       │                       ├── SceneData.kt
│       │                       └── WeaponData.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── dataloader/
│                           └── DataLoaderTest.kt
├── sorapointa-dataprovider/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── data/
│       │                   └── provider/
│       │                       ├── AutoLoadFilePersist.kt
│       │                       ├── AutoSaveFilePersist.kt
│       │                       ├── DataFilePersist.kt
│       │                       ├── DatabaseConfig.kt
│       │                       ├── DatabaseManager.kt
│       │                       ├── FilePersist.kt
│       │                       └── sql/
│       │                           ├── SQLJson.kt
│       │                           ├── SQLMap.kt
│       │                           └── SQLSet.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── data/
│                           └── provider/
│                               ├── DatabaseProviderTest.kt
│                               ├── FileProviderTest.kt
│                               └── Init.kt
├── sorapointa-dispatch/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── kotlin/
│       │   │   └── org/
│       │   │       └── sorapointa/
│       │   │           └── dispatch/
│       │   │               ├── DispatchBundle.kt
│       │   │               ├── DispatchServer.kt
│       │   │               ├── data/
│       │   │               │   ├── AccountData.kt
│       │   │               │   ├── DispatchData.kt
│       │   │               │   ├── DispatchKeyData.kt
│       │   │               │   └── SwitchData.kt
│       │   │               ├── events/
│       │   │               │   └── DispatchEvent.kt
│       │   │               ├── plugins/
│       │   │               │   ├── HTTP.kt
│       │   │               │   ├── Monitoring.kt
│       │   │               │   ├── RouteHandler.kt
│       │   │               │   ├── Routing.kt
│       │   │               │   ├── Serialization.kt
│       │   │               │   └── StatusPage.kt
│       │   │               └── utils/
│       │   │                   ├── CertBuilder.kt
│       │   │                   ├── Certificates.kt
│       │   │                   ├── KeyProvider.kt
│       │   │                   └── Route.kt
│       │   └── resources/
│       │       └── messages/
│       │           ├── DispatchBundle.properties
│       │           └── DispatchBundle_zh_CN.properties
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── dispatch/
│                           ├── AccountTest.kt
│                           ├── CertTest.kt
│                           └── DispatchServerTest.kt
├── sorapointa-event/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── event/
│       │                   ├── Event.kt
│       │                   ├── EventManager.kt
│       │                   └── StateController.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── event/
│                           ├── EventPipelineTest.kt
│                           └── StateControllerTest.kt
├── sorapointa-i18n/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── utils/
│       │                   ├── I18n.kt
│       │                   └── MessageBundle.kt
│       └── test/
│           ├── kotlin/
│           │   └── org/
│           │       └── sorapointa/
│           │           └── utils/
│           │               ├── I18nTest.kt
│           │               ├── LocalSerializerTest.kt
│           │               └── TestBundle.kt
│           └── resources/
│               └── messages/
│                   ├── TestBundle.properties
│                   └── TestBundle_nl.properties
├── sorapointa-native/
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── jnienv.rs
│       ├── lib.rs
│       └── logger.rs
├── sorapointa-native-wrapper/
│   ├── README.md
│   ├── README.zh-CN.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── rust/
│       │                   ├── Setup.kt
│       │                   └── logging/
│       │                       └── RustLogger.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── rust/
│                           └── logging/
│                               └── LoggerTest.kt
├── sorapointa-proto/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── proto/
│       │                   ├── PacketUtils.kt
│       │                   └── ProtoInfo.kt
│       ├── proto/
│       │   ├── AbilityAppliedAbility.proto
│       │   ├── AbilityAppliedModifier.proto
│       │   ├── AbilityAttachedModifier.proto
│       │   ├── AbilityControlBlock.proto
│       │   ├── AbilityEmbryo.proto
│       │   ├── AbilityGadgetInfo.proto
│       │   ├── AbilityMixinRecoverInfo.proto
│       │   ├── AbilityScalarType.proto
│       │   ├── AbilityScalarValueEntry.proto
│       │   ├── AbilityString.proto
│       │   ├── AbilitySyncStateInfo.proto
│       │   ├── AdjustTrackingInfo.proto
│       │   ├── AnimatorParameterValueInfo.proto
│       │   ├── AnimatorParameterValueInfoPair.proto
│       │   ├── AvatarDataNotify.proto
│       │   ├── AvatarEnterSceneInfo.proto
│       │   ├── AvatarEquipAffixInfo.proto
│       │   ├── AvatarExcelInfo.proto
│       │   ├── AvatarExpeditionState.proto
│       │   ├── AvatarFetterInfo.proto
│       │   ├── AvatarFightPropNotify.proto
│       │   ├── AvatarFightPropUpdateNotify.proto
│       │   ├── AvatarInfo.proto
│       │   ├── AvatarLifeStateChangeNotify.proto
│       │   ├── AvatarPropChangeReasonNotify.proto
│       │   ├── AvatarPropNotify.proto
│       │   ├── AvatarRenameInfo.proto
│       │   ├── AvatarSkillInfo.proto
│       │   ├── AvatarTeam.proto
│       │   ├── AvatarTeamUpdateNotify.proto
│       │   ├── AvatarUpgradeRsp.proto
│       │   ├── Birthday.proto
│       │   ├── BlockInfo.proto
│       │   ├── BlossomChestInfo.proto
│       │   ├── BossChestInfo.proto
│       │   ├── BreakoutAction.proto
│       │   ├── BreakoutBrickInfo.proto
│       │   ├── BreakoutElementReactionCounter.proto
│       │   ├── BreakoutPhysicalObject.proto
│       │   ├── BreakoutPhysicalObjectModifier.proto
│       │   ├── BreakoutSnapShot.proto
│       │   ├── BreakoutSpawnPoint.proto
│       │   ├── BreakoutSyncAction.proto
│       │   ├── BreakoutSyncConnectUidInfo.proto
│       │   ├── BreakoutSyncCreateConnect.proto
│       │   ├── BreakoutSyncFinishGame.proto
│       │   ├── BreakoutSyncPing.proto
│       │   ├── BreakoutSyncSnapShot.proto
│       │   ├── BreakoutVector2.proto
│       │   ├── ChangeGameTimeReq.proto
│       │   ├── ChangeGameTimeRsp.proto
│       │   ├── ClientGadgetInfo.proto
│       │   ├── CoinCollectOperatorInfo.proto
│       │   ├── CurVehicleInfo.proto
│       │   ├── CustomCommonNodeInfo.proto
│       │   ├── CustomGadgetTreeInfo.proto
│       │   ├── DeshretObeliskGadgetInfo.proto
│       │   ├── DoSetPlayerBornDataNotify.proto
│       │   ├── EchoShellInfo.proto
│       │   ├── EnterSceneDoneReq.proto
│       │   ├── EnterSceneDoneRsp.proto
│       │   ├── EnterScenePeerNotify.proto
│       │   ├── EnterSceneReadyReq.proto
│       │   ├── EnterSceneReadyRsp.proto
│       │   ├── EnterType.proto
│       │   ├── EntityAuthorityInfo.proto
│       │   ├── EntityClientData.proto
│       │   ├── EntityClientExtraInfo.proto
│       │   ├── EntityEnvironmentInfo.proto
│       │   ├── EntityRendererChangedInfo.proto
│       │   ├── Equip.proto
│       │   ├── FeatureBlockInfo.proto
│       │   ├── FetterData.proto
│       │   ├── FightPropPair.proto
│       │   ├── FishPoolInfo.proto
│       │   ├── FishtankFishInfo.proto
│       │   ├── ForceUpdateInfo.proto
│       │   ├── FoundationInfo.proto
│       │   ├── FoundationStatus.proto
│       │   ├── FriendEnterHomeOption.proto
│       │   ├── FriendOnlineState.proto
│       │   ├── Furniture.proto
│       │   ├── GadgetBornType.proto
│       │   ├── GadgetCrucibleInfo.proto
│       │   ├── GadgetGeneralRewardInfo.proto
│       │   ├── GadgetPlayInfo.proto
│       │   ├── GatherGadgetInfo.proto
│       │   ├── GetPlayerSocialDetailReq.proto
│       │   ├── GetPlayerSocialDetailRsp.proto
│       │   ├── GetPlayerTokenReq.proto
│       │   ├── GetPlayerTokenRsp.proto
│       │   ├── HostPlayerNotify.proto
│       │   ├── Item.proto
│       │   ├── ItemParam.proto
│       │   ├── LifeStateChangeNotify.proto
│       │   ├── MPLevelEntityInfo.proto
│       │   ├── MassivePropParam.proto
│       │   ├── MassivePropSyncInfo.proto
│       │   ├── Material.proto
│       │   ├── MaterialDeleteInfo.proto
│       │   ├── MathQuaternion.proto
│       │   ├── ModifierDurability.proto
│       │   ├── MonsterBornType.proto
│       │   ├── MonsterRoute.proto
│       │   ├── MotionInfo.proto
│       │   ├── MotionState.proto
│       │   ├── MovingPlatformType.proto
│       │   ├── MpPlayRewardInfo.proto
│       │   ├── MpSettingType.proto
│       │   ├── NightCrowGadgetInfo.proto
│       │   ├── OfferingInfo.proto
│       │   ├── OnlinePlayerInfo.proto
│       │   ├── OpenStateUpdateNotify.proto
│       │   ├── PacketHead.proto
│       │   ├── PingReq.proto
│       │   ├── PingRsp.proto
│       │   ├── PlatformInfo.proto
│       │   ├── PlayTeamEntityInfo.proto
│       │   ├── PlayerDataNotify.proto
│       │   ├── PlayerDieOption.proto
│       │   ├── PlayerDieType.proto
│       │   ├── PlayerEnterSceneInfoNotify.proto
│       │   ├── PlayerEnterSceneNotify.proto
│       │   ├── PlayerGameTimeNotify.proto
│       │   ├── PlayerLocationInfo.proto
│       │   ├── PlayerLoginReq.proto
│       │   ├── PlayerLoginRsp.proto
│       │   ├── PlayerPropChangeNotify.proto
│       │   ├── PlayerPropChangeReasonNotify.proto
│       │   ├── PlayerPropNotify.proto
│       │   ├── PlayerRTTInfo.proto
│       │   ├── PlayerSetPauseReq.proto
│       │   ├── PlayerSetPauseRsp.proto
│       │   ├── PlayerStoreNotify.proto
│       │   ├── PlayerWidgetInfo.proto
│       │   ├── PlayerWorldLocationInfo.proto
│       │   ├── PlayerWorldSceneInfo.proto
│       │   ├── PlayerWorldSceneInfoListNotify.proto
│       │   ├── PostEnterSceneReq.proto
│       │   ├── PostEnterSceneRsp.proto
│       │   ├── ProfilePicture.proto
│       │   ├── PropChangeReason.proto
│       │   ├── PropPair.proto
│       │   ├── PropValue.proto
│       │   ├── ProtEntityType.proto
│       │   ├── QueryCurrRegionHttpRsp.proto
│       │   ├── QueryRegionListHttpRsp.proto
│       │   ├── RegionInfo.proto
│       │   ├── RegionSimpleInfo.proto
│       │   ├── Reliquary.proto
│       │   ├── ResVersionConfig.proto
│       │   ├── Retcode.proto
│       │   ├── RoguelikeGadgetInfo.proto
│       │   ├── Route.proto
│       │   ├── RoutePoint.proto
│       │   ├── SceneAvatarInfo.proto
│       │   ├── SceneDataNotify.proto
│       │   ├── SceneEntityAiInfo.proto
│       │   ├── SceneEntityAppearNotify.proto
│       │   ├── SceneEntityInfo.proto
│       │   ├── SceneFishInfo.proto
│       │   ├── SceneGadgetInfo.proto
│       │   ├── SceneInitFinishReq.proto
│       │   ├── SceneInitFinishRsp.proto
│       │   ├── SceneMonsterInfo.proto
│       │   ├── SceneNpcInfo.proto
│       │   ├── ScenePlayerInfo.proto
│       │   ├── ScenePlayerInfoNotify.proto
│       │   ├── ScenePlayerLocationNotify.proto
│       │   ├── SceneReliquaryInfo.proto
│       │   ├── SceneTeamAvatar.proto
│       │   ├── SceneTeamUpdateNotify.proto
│       │   ├── SceneTimeNotify.proto
│       │   ├── SceneWeaponInfo.proto
│       │   ├── ScreenInfo.proto
│       │   ├── ServantInfo.proto
│       │   ├── ServerBuff.proto
│       │   ├── ServerDisconnectClientNotify.proto
│       │   ├── ServerTimeNotify.proto
│       │   ├── SetPlayerBornDataReq.proto
│       │   ├── SetPlayerBornDataRsp.proto
│       │   ├── SetPlayerPropReq.proto
│       │   ├── SetPlayerPropRsp.proto
│       │   ├── ShortAbilityHashPair.proto
│       │   ├── SocialDetail.proto
│       │   ├── SocialShowAvatarInfo.proto
│       │   ├── StatueGadgetInfo.proto
│       │   ├── StopServerInfo.proto
│       │   ├── StoreType.proto
│       │   ├── StoreWeightLimitNotify.proto
│       │   ├── SyncScenePlayTeamEntityNotify.proto
│       │   ├── SyncTeamEntityNotify.proto
│       │   ├── TeamEnterSceneInfo.proto
│       │   ├── TeamEntityInfo.proto
│       │   ├── TrackingIOInfo.proto
│       │   ├── TrialAvatarGrantRecord.proto
│       │   ├── TrialAvatarInfo.proto
│       │   ├── UnionCmd.proto
│       │   ├── UnionCmdNotify.proto
│       │   ├── Vector.proto
│       │   ├── VehicleInfo.proto
│       │   ├── VehicleLocationInfo.proto
│       │   ├── VehicleMember.proto
│       │   ├── VisionType.proto
│       │   ├── Weapon.proto
│       │   ├── WeatherInfo.proto
│       │   ├── WeeklyBossResinDiscountInfo.proto
│       │   ├── WidgetSlotData.proto
│       │   ├── WidgetSlotTag.proto
│       │   ├── WorktopInfo.proto
│       │   ├── WorldDataNotify.proto
│       │   ├── WorldPlayerDieNotify.proto
│       │   ├── WorldPlayerInfoNotify.proto
│       │   ├── WorldPlayerLocationNotify.proto
│       │   ├── WorldPlayerRTTNotify.proto
│       │   ├── WorldPlayerReviveReq.proto
│       │   ├── WorldPlayerReviveRsp.proto
│       │   └── server_side/
│       │       ├── bin.block.proto
│       │       ├── bin.home.proto
│       │       ├── bin.server.proto
│       │       ├── bin_common.server.proto
│       │       ├── cmd_activity.server.proto
│       │       ├── cmd_id_config.proto
│       │       ├── cmd_match.server.proto
│       │       ├── cmd_misc.server.proto
│       │       ├── cmd_mp.server.proto
│       │       ├── cmd_muip.server.proto
│       │       ├── cmd_offline_op.server.proto
│       │       ├── cmd_player.server.proto
│       │       ├── config.server.proto
│       │       ├── define.proto
│       │       └── enum.server.proto
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── proto/
│                           └── ProtoTest.kt
├── sorapointa-task/
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   └── kotlin/
│       │       └── org/
│       │           └── sorapointa/
│       │               └── task/
│       │                   ├── Cron.kt
│       │                   ├── CronTask.kt
│       │                   └── TaskManager.kt
│       └── test/
│           └── kotlin/
│               └── org/
│                   └── sorapointa/
│                       └── task/
│                           └── TaskManagerTest.kt
└── sorapointa-utils/
    ├── build.gradle.kts
    ├── sorapointa-utils-all/
    │   └── build.gradle.kts
    ├── sorapointa-utils-core/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── main/
    │       │   └── kotlin/
    │       │       └── org/
    │       │           └── sorapointa/
    │       │               └── utils/
    │       │                   ├── Annotations.kt
    │       │                   ├── ByteUtils.kt
    │       │                   ├── Cast.kt
    │       │                   ├── Collection.kt
    │       │                   ├── Environment.kt
    │       │                   ├── File.kt
    │       │                   ├── Files.kt
    │       │                   ├── JVM.kt
    │       │                   ├── Locks.kt
    │       │                   ├── ModuleScope.kt
    │       │                   ├── Optional.kt
    │       │                   ├── Random.kt
    │       │                   ├── Reflection.kt
    │       │                   ├── String.kt
    │       │                   ├── Test.kt
    │       │                   ├── XML.kt
    │       │                   ├── encoding/
    │       │                   │   ├── Base64Provider.kt
    │       │                   │   ├── Digest.kt
    │       │                   │   ├── Hex.kt
    │       │                   │   └── RSAProvider.kt
    │       │                   └── logging/
    │       │                       └── PatternLayoutNoLambda.kt
    │       └── test/
    │           └── kotlin/
    │               └── org/
    │                   └── sorapointa/
    │                       └── utils/
    │                           ├── FilesTest.kt
    │                           ├── LocksTest.kt
    │                           ├── ScopeTest.kt
    │                           ├── StringTest.kt
    │                           └── encoding/
    │                               ├── Base64.kt
    │                               └── HexTest.kt
    ├── sorapointa-utils-crypto/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── main/
    │       │   └── kotlin/
    │       │       └── org/
    │       │           └── sorapointa/
    │       │               └── utils/
    │       │                   ├── ByteReadUtils.kt
    │       │                   └── crypto/
    │       │                       ├── Ec2b.kt
    │       │                       ├── Ec2bAes.kt
    │       │                       ├── MT19937.kt
    │       │                       ├── Magic.kt
    │       │                       └── RSA.kt
    │       └── test/
    │           └── kotlin/
    │               └── org/
    │                   └── sorapointa/
    │                       └── utils/
    │                           ├── ScopeTest.kt
    │                           └── crypto/
    │                               ├── Ec2bTest.kt
    │                               ├── MT64Test.kt
    │                               └── RSAKeyTest.kt
    ├── sorapointa-utils-serialization/
    │   ├── build.gradle.kts
    │   └── src/
    │       ├── main/
    │       │   └── kotlin/
    │       │       └── org/
    │       │           └── sorapointa/
    │       │               └── utils/
    │       │                   ├── Json.kt
    │       │                   └── Yaml.kt
    │       └── test/
    │           └── kotlin/
    │               └── org/
    │                   └── sorapointa/
    │                       └── utils/
    │                           └── YamlCompatible.kt
    └── sorapointa-utils-time/
        ├── build.gradle.kts
        └── src/
            └── main/
                └── kotlin/
                    └── org/
                        └── sorapointa/
                            └── utils/
                                └── Time.kt
Download .txt
SYMBOL INDEX (11 symbols across 2 files)

FILE: sorapointa-native/src/jnienv.rs
  type JNIEnvExt (line 3) | pub trait JNIEnvExt<'a> {
    method throw_new_or_eprint (line 4) | fn throw_new_or_eprint<'c, T>(&self, class: T, msg: &str)
  function throw_new_or_eprint (line 10) | fn throw_new_or_eprint<'c, T>(&self, class: T, msg: &str)

FILE: sorapointa-native/src/logger.rs
  constant LOG_CLASS (line 11) | const LOG_CLASS: &str = "org/sorapointa/rust/logging/RustLogger";
  constant LOG_SIG (line 12) | const LOG_SIG: &str = "(Ljava/lang/String;)V";
  constant ILLEGAL_STATE_EXCEPTION (line 13) | const ILLEGAL_STATE_EXCEPTION: &str = "java/lang/IllegalStateException";
  type Logger (line 18) | struct Logger {
    method enabled (line 61) | fn enabled(&self, metadata: &Metadata) -> bool {
    method log (line 65) | fn log(&self, record: &Record) {
    method flush (line 85) | fn flush(&self) {}
  function Java_org_sorapointa_rust_logging_RustLogger_setup (line 90) | pub extern "system" fn Java_org_sorapointa_rust_logging_RustLogger_setup(
Condensed preview — 479 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,319K chars).
[
  {
    "path": ".editorconfig",
    "chars": 244,
    "preview": "[*]\ninsert_final_newline = true\ncharset = utf-8\nindent_style = space\nend_of_line = lf\n\n[{*.kt,*.kts}]\nindent_size = 4\nma"
  },
  {
    "path": ".git-hooks/commit-msg",
    "chars": 514,
    "preview": "#!/usr/bin/env bash\n\nINPUT_FILE=$1\n\nSTART_LINE=$(head -n1 \"$INPUT_FILE\")\n\nPATTERN='^(feat(ure)?|fix|docs|style|refactor|"
  },
  {
    "path": ".git-hooks/pre-commit",
    "chars": 1869,
    "preview": "#!/bin/bash\n\necho \"[pre-commit check]\"\n\nif ! [ -x \"$(command -v cargo)\" ]; then\n  echo -e 'Rust toolchains are not insta"
  },
  {
    "path": ".gitattributes",
    "chars": 147,
    "preview": "# Linux start script should use lf\n/gradlew        text eol=lf\n\n# These are Windows script files and should use crlf\n*.b"
  },
  {
    "path": ".github/workflows/api_check.yml",
    "chars": 716,
    "preview": "name: API Check\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ master ]\n    paths:\n      - '**.kt'\n      - '**.kts'\n "
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 2767,
    "preview": "name: Test\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ master ]\n    paths:\n      - '**.kt'\n      - '**.kts'\n      "
  },
  {
    "path": ".gitignore",
    "chars": 979,
    "preview": "### Gradle ###\n.gradle\nbuild/\n!gradle/wrapper/gradle-wrapper.jar\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### Carg"
  },
  {
    "path": ".idea/encodings.xml",
    "chars": 201,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\" defaultCharsetForPropertiesFil"
  },
  {
    "path": "LICENSE",
    "chars": 11370,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "build.gradle.kts",
    "chars": 2498,
    "preview": "import com.diffplug.gradle.spotless.FormatExtension\n\nplugins {\n    kotlin(\"jvm\") apply false\n    java\n    // NOT AN ERRO"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "chars": 790,
    "preview": "plugins {\n    `kotlin-dsl`\n}\n\nrepositories {\n    gradlePluginPortal()\n    mavenCentral()\n    maven(\"https://s01.oss.sona"
  },
  {
    "path": "buildSrc/settings.gradle.kts",
    "chars": 154,
    "preview": "dependencyResolutionManagement {\n    versionCatalogs {\n        create(\"libs\") {\n            from(files(\"../gradle/libs.v"
  },
  {
    "path": "buildSrc/src/main/kotlin/BuildConfigExtension.kt",
    "chars": 928,
    "preview": "import com.github.gmazzo.gradle.plugins.BuildConfigSourceSet\n\nfun BuildConfigSourceSet.string(name: String, value: Strin"
  },
  {
    "path": "buildSrc/src/main/kotlin/GitHook.kt",
    "chars": 538,
    "preview": "import org.gradle.api.Project\nimport org.gradle.internal.os.OperatingSystem\nimport java.io.File\nimport java.nio.file.Fil"
  },
  {
    "path": "buildSrc/src/main/kotlin/JniHeader.kt",
    "chars": 4107,
    "preview": "import org.gradle.api.Project\nimport org.gradle.api.tasks.TaskContainer\nimport org.jetbrains.kotlin.gradle.dsl.kotlinExt"
  },
  {
    "path": "buildSrc/src/main/kotlin/OptInAnnotations.kt",
    "chars": 202,
    "preview": "object OptInAnnotations {\n    val list = listOf(\n        \"kotlin.ExperimentalUnsignedTypes\",\n        \"kotlin.contracts.E"
  },
  {
    "path": "buildSrc/src/main/kotlin/Properties.kt",
    "chars": 676,
    "preview": "import org.gradle.api.Project\nimport org.gradle.kotlin.dsl.extra\nimport java.util.*\n\nfun Project.getRootProjectLocalProp"
  },
  {
    "path": "buildSrc/src/main/kotlin/ResourcesCopy.kt",
    "chars": 1462,
    "preview": "import org.gradle.api.Project\nimport org.gradle.api.tasks.Copy\nimport org.gradle.kotlin.dsl.register\n\ninternal fun Proje"
  },
  {
    "path": "buildSrc/src/main/kotlin/Test.kt",
    "chars": 39,
    "preview": "val isCI = System.getenv(\"CI\") != null\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/sorapointa-conventions.gradle.kts",
    "chars": 2941,
    "preview": "import org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n    kotlin(\"jvm\")\n    id(\"com.github.gmazzo.buildconfi"
  },
  {
    "path": "buildSrc/src/main/kotlin/sorapointa-publish.gradle.kts",
    "chars": 2990,
    "preview": "plugins {\n    `java-library`\n    `maven-publish`\n    signing\n}\n\njava {\n    withJavadocJar()\n    withSourcesJar()\n}\n\nval "
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "chars": 3927,
    "preview": "# Contributing Guideline\n\n[简体中文](CONTRIBUTING.zh-CN.md)\n\n## Code Style\n\n- [Kotlin Official Code Style](https://kotlinlan"
  },
  {
    "path": "docs/CONTRIBUTING.zh-CN.md",
    "chars": 2275,
    "preview": "# 贡献指南\n\n[English](CONTRIBUTING.zh-CN.md)\n\n## 代码风格\n\n- Kotlin 官方代码风格 - [中文站参考](https://book.kotlincn.net/text/coding-conve"
  },
  {
    "path": "docs/README.md",
    "chars": 5284,
    "preview": "<!--Logo-->\n\n![Sorapointa Logo](https://socialify.git.ci/Sorapointa/Sorapointa/image?description=1&descriptionEditable=A"
  },
  {
    "path": "docs/README.zh-CN.md",
    "chars": 4707,
    "preview": "<!--Logo-->\n\n![Sorapointa Logo](https://socialify.git.ci/Sorapointa/Sorapointa/image?description=1&descriptionEditable=A"
  },
  {
    "path": "docs/guides/concurrency.md",
    "chars": 5055,
    "preview": "# Concurrency Safety\n\nThe Sorapointa project has applied a lot of coroutine and multi-thread techniques, \nso please to b"
  },
  {
    "path": "docs/guides/concurrency.zh-CN.md",
    "chars": 2541,
    "preview": "# 关于并发安全\n\nSorapointa 项目大量使用了协程与多线程技术,请时刻注意检查代码在多线程以及高并发环境下的运行状况\n\n## 共享可修改数据\n\n在开发期间,最好暴露不可修改的变量和集合如 `val`, `List`, `Map`,"
  },
  {
    "path": "docs/guides/database.md",
    "chars": 2580,
    "preview": "# Database Operation Safety\n\n## Transaction\n\nNested use of `transaction` is prohibited when it is not required, \nwhich w"
  },
  {
    "path": "docs/guides/database.zh-CN.md",
    "chars": 791,
    "preview": "# 数据库操作安全\n\n## 事务\n\n在非必须时禁止使用嵌套使用 `transaction`,这会导致 `READ_COMMITTED` \n及以上的事物隔离级别启动的会话**无法读取到嵌套事务内的任何变更**,\n以造成各种不符合预期和难以预测"
  },
  {
    "path": "docs/guides/kotlin-atomicfu.md",
    "chars": 2113,
    "preview": "# Kotlin AtomicFU Guideline\n\n[简体中文](kotlin-atomicfu.zh-CN.md)\n\n## Setup\n\n```kotlin\ndependencies {\n  implementation(\"org."
  },
  {
    "path": "docs/guides/kotlin-atomicfu.zh-CN.md",
    "chars": 1349,
    "preview": "# Kotlin AtomicFU 使用指南\n\n[English](kotlin-atomicfu.md)\n\n## Setup\n\n```kotlin\ndependencies {\n  implementation(\"org.jetbrain"
  },
  {
    "path": "docs/guides/unit-test.md",
    "chars": 2951,
    "preview": "# JUnit Guideline\n\n[简体中文](unit-test.zh-CN.md)\n\n[JUnit Official User Guide](https://junit.org/junit5/docs/current/user-gu"
  },
  {
    "path": "docs/guides/unit-test.zh-CN.md",
    "chars": 2154,
    "preview": "# JUnit 使用指南\n\n[English](unit-test.md)\n\n[JUnit 官方文档](https://junit.org/junit5/docs/current/user-guide/)\n\n## 基本用法\n\n你可以在 `t"
  },
  {
    "path": "gradle/libs.versions.toml",
    "chars": 4258,
    "preview": "[versions]\nkotlin = \"1.8.10\"\nktor = \"2.1.2\"\nktlint = \"0.48.1\"\nexposed = \"0.41.1\"\n\n[plugins]\nkotlin-serialization = { id "
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 221,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "gradle.properties",
    "chars": 225,
    "preview": "kotlin.code.style=official\norg.gradle.caching=true\norg.gradle.parallel=true\norg.gradle.warning.mode=summary\norg.gradle.c"
  },
  {
    "path": "gradlew",
    "chars": 8474,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "gradlew.bat",
    "chars": 2868,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "renovate.json",
    "chars": 107,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "settings.gradle.kts",
    "chars": 746,
    "preview": "rootProject.name = \"Sorapointa\"\n\ninclude(\"sorapointa-core\")\ninclude(\"sorapointa-crypto\")\ninclude(\"sorapointa-dataloader\""
  },
  {
    "path": "sorapointa-core/README.md",
    "chars": 2278,
    "preview": "# Core Module\n\n[简体中文](README.zh-CN.md)\n\n## Command System\n\nThe command system is based on [**Yac**](https://githubfast.c"
  },
  {
    "path": "sorapointa-core/README.zh-CN.md",
    "chars": 1550,
    "preview": "# Core 模块\n\n[English](README.zh-CN.md)\n\n## 命令系统\n\n命令系统基于 [**Yac**](https://githubfast.com/Colerar/Yac) ([**clikt**](https:"
  },
  {
    "path": "sorapointa-core/build.gradle.kts",
    "chars": 1202,
    "preview": "@file:Suppress(\"GradlePackageUpdate\")\n\nplugins {\n    `sorapointa-conventions`\n    `sorapointa-publish`\n    application\n}"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/CoreBundle.kt",
    "chars": 483,
    "preview": "package org.sorapointa\n\nimport org.jetbrains.annotations.Nls\nimport org.jetbrains.annotations.PropertyKey\nimport org.sor"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/Main.kt",
    "chars": 7356,
    "preview": "package org.sorapointa\n\nimport io.ktor.server.application.*\nimport kotlinx.coroutines.*\nimport moe.sdl.yac.core.CliktCom"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/Sorapointa.kt",
    "chars": 2060,
    "preview": "package org.sorapointa\n\nimport io.ktor.server.application.*\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coro"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/SorapointaConfig.kt",
    "chars": 3761,
    "preview": "package org.sorapointa\n\nimport com.charleskorn.kaml.YamlComment\nimport kotlinx.datetime.TimeZone\nimport kotlinx.serializ"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/Command.kt",
    "chars": 1581,
    "preview": "package org.sorapointa.command\n\nimport moe.sdl.yac.core.CliktCommand\nimport moe.sdl.yac.core.context\nimport org.jetbrain"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/CommandLocalization.kt",
    "chars": 11298,
    "preview": "package org.sorapointa.command\n\nimport moe.sdl.yac.core.*\nimport moe.sdl.yac.output.HelpFormatter\nimport moe.sdl.yac.out"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/CommandManager.kt",
    "chars": 6105,
    "preview": "package org.sorapointa.command\n\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport moe.sdl.yac.core.C"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/CommandSender.kt",
    "chars": 884,
    "preview": "package org.sorapointa.command\n\nimport org.jetbrains.annotations.Nls\nimport org.jetbrains.annotations.PropertyKey\nimport"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/ConsoleCommandSender.kt",
    "chars": 706,
    "preview": "package org.sorapointa.command\n\nimport io.ktor.server.websocket.*\nimport kotlinx.coroutines.isActive\nimport org.sorapoin"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/Defaults.kt",
    "chars": 946,
    "preview": "package org.sorapointa.command.defaults\n\nimport org.sorapointa.command.AbstractCommandNode\nimport org.sorapointa.command"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/console/ConsoleUser.kt",
    "chars": 3124,
    "preview": "package org.sorapointa.command.defaults.console\n\nimport moe.sdl.yac.core.PrintMessage\nimport moe.sdl.yac.parameters.opti"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/console/Quit.kt",
    "chars": 449,
    "preview": "package org.sorapointa.command.defaults.console\n\nimport org.sorapointa.command.ConsoleCommand\nimport org.sorapointa.comm"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/Help.kt",
    "chars": 2349,
    "preview": "package org.sorapointa.command.defaults.general\n\nimport moe.sdl.yac.core.UsageError\nimport moe.sdl.yac.parameters.argume"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/ListPlayer.kt",
    "chars": 759,
    "preview": "package org.sorapointa.command.defaults.general\n\nimport org.sorapointa.Sorapointa\nimport org.sorapointa.command.Command\n"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/LocaleCommand.kt",
    "chars": 3457,
    "preview": "package org.sorapointa.command.defaults.general\n\nimport moe.sdl.yac.parameters.arguments.argument\nimport moe.sdl.yac.par"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/defaults/general/Version.kt",
    "chars": 556,
    "preview": "package org.sorapointa.command.defaults.general\n\nimport org.sorapointa.command.Command\nimport org.sorapointa.command.Com"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/command/utils/Options.kt",
    "chars": 1241,
    "preview": "package org.sorapointa.command.utils\n\nimport moe.sdl.yac.parameters.groups.ChoiceGroup\nimport moe.sdl.yac.parameters.gro"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/console/Completer.kt",
    "chars": 1432,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.console\n\nimport moe.sdl.yac.core.CliktCommand\nimport moe.sdl.yac.parame"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/console/Console.kt",
    "chars": 3822,
    "preview": "package org.sorapointa.console\n\nimport io.ktor.util.collections.*\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.c"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/console/JLineRedirector.kt",
    "chars": 2872,
    "preview": "package org.sorapointa.console\n\nimport java.io.PrintStream\nimport java.util.*\n\n/**\n * Work around, not a good implementa"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/console/SoraHighlighter.kt",
    "chars": 4497,
    "preview": "package org.sorapointa.console\n\nimport org.jline.reader.Highlighter\nimport org.jline.reader.LineReader\nimport org.jline."
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/console/WebSocketConsole.kt",
    "chars": 9041,
    "preview": "package org.sorapointa.console\n\nimport com.password4j.Password\nimport io.ktor.client.*\nimport io.ktor.client.engine.cio."
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/events/PlayerEvent.kt",
    "chars": 2047,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.events\n\nimport com.squareup.wire.Message\nimport com.squareup.wire.Proto"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/AvatarEntity.kt",
    "chars": 12804,
    "preview": "package org.sorapointa.game\n\nimport org.sorapointa.dataloader.common.ElementType\nimport org.sorapointa.dataloader.common"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/Player.kt",
    "chars": 11000,
    "preview": "package org.sorapointa.game\n\nimport com.squareup.wire.Message\nimport kotlinx.atomicfu.atomic\nimport kotlinx.coroutines.J"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/PlayerAvatarComp.kt",
    "chars": 22254,
    "preview": "package org.sorapointa.game\n\nimport kotlinx.atomicfu.atomic\nimport org.sorapointa.dataloader.common.*\nimport org.sorapoi"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/PlayerComp.kt",
    "chars": 8907,
    "preview": "package org.sorapointa.game\n\nimport kotlinx.atomicfu.atomic\nimport kotlinx.datetime.Instant\nimport org.sorapointa.events"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/PlayerItemComp.kt",
    "chars": 17134,
    "preview": "package org.sorapointa.game\n\nimport kotlinx.atomicfu.atomic\nimport kotlinx.atomicfu.getAndUpdate\nimport org.sorapointa.d"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/Scene.kt",
    "chars": 2467,
    "preview": "package org.sorapointa.game\n\nimport org.sorapointa.dataloader.common.ClimateType\nimport org.sorapointa.dataloader.def.Sc"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/SceneEntity.kt",
    "chars": 8885,
    "preview": "package org.sorapointa.game\n\nimport org.sorapointa.dataloader.common.EntityIdType\nimport org.sorapointa.dataloader.commo"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/World.kt",
    "chars": 1288,
    "preview": "package org.sorapointa.game\n\nimport kotlinx.atomicfu.atomic\nimport org.sorapointa.dataloader.common.EntityIdType\nimport "
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/data/GameConstants.kt",
    "chars": 331,
    "preview": "package org.sorapointa.game.data\n\n// const val MAIN_CHARACTER_MALE = 10000005\n// const val MAIN_CHARACTER_FEMALE = 10000"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/data/PlayerData.kt",
    "chars": 5275,
    "preview": "package org.sorapointa.game.data\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.sync.Mutex\nimport kotl"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/data/Position.kt",
    "chars": 3357,
    "preview": "package org.sorapointa.game.data\n\nimport kotlinx.serialization.Serializable\nimport org.sorapointa.proto.Vector\nimport or"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/game/data/SorapointaStoreEntry.kt",
    "chars": 1723,
    "preview": "package org.sorapointa.game.data\n\nimport org.jetbrains.exposed.dao.id.EntityID\nimport org.jetbrains.exposed.dao.id.IdTab"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/server/ServerNetwork.kt",
    "chars": 1775,
    "preview": "package org.sorapointa.server\n\nimport kcp.highway.ChannelConfig\nimport kcp.highway.KcpServer\nimport kotlinx.coroutines.J"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/server/network/NetworkHandler.kt",
    "chars": 10485,
    "preview": "package org.sorapointa.server.network\n\nimport com.squareup.wire.Message\nimport io.netty.buffer.ByteBuf\nimport io.netty.b"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/server/network/OutgoingPacket.kt",
    "chars": 22363,
    "preview": "package org.sorapointa.server.network\n\nimport com.squareup.wire.ProtoAdapter\nimport io.ktor.util.*\nimport okio.ByteStrin"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/server/network/PacketHandler.kt",
    "chars": 4985,
    "preview": "package org.sorapointa.server.network\n\nimport com.squareup.wire.Message\nimport com.squareup.wire.ProtoAdapter\nimport org"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/server/network/PacketHandlerImpl.kt",
    "chars": 10343,
    "preview": "package org.sorapointa.server.network\n\nimport com.squareup.wire.ProtoAdapter\nimport io.ktor.util.*\nimport kotlinx.corout"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/server/network/SoraPacket.kt",
    "chars": 1524,
    "preview": "package org.sorapointa.server.network\n\nimport com.squareup.wire.Message\nimport com.squareup.wire.ProtoAdapter\nimport io."
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/utils/Console.kt",
    "chars": 411,
    "preview": "package org.sorapointa.utils\n\nimport org.jline.style.StyleExpression\nimport org.sorapointa.console.Console\n\nprivate val "
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/utils/GameUtils.kt",
    "chars": 2814,
    "preview": "@file:Suppress(\"NOTHING_TO_INLINE\")\n\npackage org.sorapointa.utils\n\nimport kotlinx.datetime.Instant\nimport kotlinx.dateti"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/utils/NetworkUtils.kt",
    "chars": 2055,
    "preview": "package org.sorapointa.utils\n\nimport io.ktor.utils.io.core.*\nimport io.netty.buffer.ByteBuf\nimport mu.KotlinLogging\nimpo"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/utils/OptionalContainer.kt",
    "chars": 546,
    "preview": "package org.sorapointa.utils\n\nclass OptionalContainer<T>(\n    private val defaultValue: T,\n) {\n    var value: T = defaul"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/utils/PropDelegate.kt",
    "chars": 1213,
    "preview": "package org.sorapointa.utils\n\nimport org.jetbrains.exposed.dao.Entity\nimport org.jetbrains.exposed.sql.Column\nimport kot"
  },
  {
    "path": "sorapointa-core/src/main/kotlin/org/sorapointa/utils/TypoSuggestor.kt",
    "chars": 403,
    "preview": "package org.sorapointa.utils\n\nimport moe.sdl.yac.core.jaroWinklerSimilarity\n\nfun suggestTypo(input: String, possibleValu"
  },
  {
    "path": "sorapointa-core/src/main/resources/logback.xml",
    "chars": 2529,
    "preview": "<!--Please edit this file on Sorapointa Core, other copy would be overwritten-->\n<configuration debug=\"false\">\n    <!--N"
  },
  {
    "path": "sorapointa-core/src/main/resources/messages/CoreBundle.properties",
    "chars": 5850,
    "preview": "sora.cmd.manager.alias=\\n\\nAlias={0}\nsora.cmd.manager.invoke.empty=No command input\nsora.cmd.manager.invoke.error=No suc"
  },
  {
    "path": "sorapointa-core/src/main/resources/messages/CoreBundle_zh_CN.properties",
    "chars": 4348,
    "preview": "sora.cmd.manager.alias=\\n\\n别名={0}\nsora.cmd.manager.invoke.empty=未输入命令\nsora.cmd.manager.invoke.error=不存在此命令 \"{0}\"\nsora.cm"
  },
  {
    "path": "sorapointa-core/src/test/kotlin/org/sorapointa/command/defaults/HelpTest.kt",
    "chars": 2208,
    "preview": "package org.sorapointa.command.defaults\n\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.BeforeAll\nim"
  },
  {
    "path": "sorapointa-core/src/test/kotlin/org/sorapointa/logger/LogTest.kt",
    "chars": 1288,
    "preview": "package org.sorapointa.logger\n\nimport mu.KotlinLogging\nimport org.junit.jupiter.api.Test\n\nprivate val logger = KotlinLog"
  },
  {
    "path": "sorapointa-core/src/test/resources/logback-test.xml",
    "chars": 788,
    "preview": "<!--Please edit this file on Sorapointa Core, other copy would be overwritten-->\n<configuration debug=\"true\">\n    <!--Te"
  },
  {
    "path": "sorapointa-crypto/build.gradle.kts",
    "chars": 399,
    "preview": "plugins {\n    `sorapointa-conventions`\n    `sorapointa-publish`\n    kotlin(\"plugin.serialization\")\n}\n\ndependencies {\n   "
  },
  {
    "path": "sorapointa-crypto/src/main/kotlin/org/sorapointa/crypto/Crypto.kt",
    "chars": 10760,
    "preview": "package org.sorapointa.crypto\n\nimport com.charleskorn.kaml.YamlComment\nimport kotlinx.serialization.Serializable\nimport "
  },
  {
    "path": "sorapointa-dataloader/README.md",
    "chars": 1009,
    "preview": "# Data Loader Module\n\n[简体中文](README.zh-CN.md)\n\n## Register Resource\n\nYou can register resource like this:\n\n```kotlin\n@Se"
  },
  {
    "path": "sorapointa-dataloader/README.zh-CN.md",
    "chars": 862,
    "preview": "# Data Loader 模块\n\n[English](README.md)\n\n## 注册 Resource\n\n像这样注册资源:\n\n```kotlin\n@Serializable // 用 kotlinx.serialization 序列化"
  },
  {
    "path": "sorapointa-dataloader/build.gradle.kts",
    "chars": 728,
    "preview": "@file:Suppress(\"GradlePackageUpdate\")\n\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n    `sorapointa"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/DataLoader.kt",
    "chars": 5071,
    "preview": "package org.sorapointa.dataloader\n\nimport io.github.classgraph.ClassGraph\nimport io.github.classgraph.ClassRefTypeSignat"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/AddProp.kt",
    "chars": 451,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/CurveInfo.kt",
    "chars": 319,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/Enum.kt",
    "chars": 26424,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.json.JsonPrimitive\n\nint"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/ItemParamData.kt",
    "chars": 314,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/ItemParamStringData.kt",
    "chars": 269,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/OpenCondData.kt",
    "chars": 298,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/PointData.kt",
    "chars": 547,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/PropGrowCurve.kt",
    "chars": 548,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/RewardItemData.kt",
    "chars": 285,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/common/ScenePointConfig.kt",
    "chars": 278,
    "preview": "package org.sorapointa.dataloader.common\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Js"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/AvatarExcelData.kt",
    "chars": 4262,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/AvatarSkillData.kt",
    "chars": 2441,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/AvatarSkillDepotData.kt",
    "chars": 2406,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/MaterialData.kt",
    "chars": 3906,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/ReliquaryAffixData.kt",
    "chars": 1107,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/ReliquaryData.kt",
    "chars": 3446,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/ReliquaryLevelData.kt",
    "chars": 851,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/ReliquaryMainPropData.kt",
    "chars": 929,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/ReliquarySetData.kt",
    "chars": 839,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/SceneData.kt",
    "chars": 1476,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/main/kotlin/org/sorapointa/dataloader/def/WeaponData.kt",
    "chars": 3538,
    "preview": "package org.sorapointa.dataloader.def\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonN"
  },
  {
    "path": "sorapointa-dataloader/src/test/kotlin/org/sorapointa/dataloader/DataLoaderTest.kt",
    "chars": 665,
    "preview": "package org.sorapointa.dataloader\n\nimport org.junit.jupiter.api.Test\nimport org.sorapointa.dataloader.def.avatarDataList"
  },
  {
    "path": "sorapointa-dataprovider/build.gradle.kts",
    "chars": 1556,
    "preview": "@file:Suppress(\"GradlePackageUpdate\")\n\nimport com.github.gmazzo.gradle.plugins.BuildConfigSourceSet\nimport org.jetbrains"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/AutoLoadFilePersist.kt",
    "chars": 1231,
    "preview": "package org.sorapointa.data.provider\n\nimport kotlinx.coroutines.*\nimport kotlinx.serialization.KSerializer\nimport kotlin"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/AutoSaveFilePersist.kt",
    "chars": 1333,
    "preview": "package org.sorapointa.data.provider\n\nimport kotlinx.coroutines.*\nimport kotlinx.serialization.KSerializer\nimport kotlin"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/DataFilePersist.kt",
    "chars": 2112,
    "preview": "package org.sorapointa.data.provider\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.co"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/DatabaseConfig.kt",
    "chars": 3921,
    "preview": "package org.sorapointa.data.provider\n\nimport com.charleskorn.kaml.YamlComment\nimport kotlinx.serialization.SerialName\nim"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/DatabaseManager.kt",
    "chars": 2583,
    "preview": "package org.sorapointa.data.provider\n\nimport com.zaxxer.hikari.HikariConfig\nimport com.zaxxer.hikari.HikariDataSource\nim"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/FilePersist.kt",
    "chars": 1088,
    "preview": "package org.sorapointa.data.provider\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.serialization.Serializable"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/sql/SQLJson.kt",
    "chars": 1794,
    "preview": "package org.sorapointa.data.provider.sql\n\nimport kotlinx.serialization.KSerializer\nimport kotlinx.serialization.json.Jso"
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/sql/SQLMap.kt",
    "chars": 3643,
    "preview": "package org.sorapointa.data.provider.sql\n\nimport org.jetbrains.exposed.dao.id.EntityID\nimport org.jetbrains.exposed.dao."
  },
  {
    "path": "sorapointa-dataprovider/src/main/kotlin/org/sorapointa/data/provider/sql/SQLSet.kt",
    "chars": 2981,
    "preview": "package org.sorapointa.data.provider.sql\n\nimport org.jetbrains.exposed.dao.id.EntityID\nimport org.jetbrains.exposed.dao."
  },
  {
    "path": "sorapointa-dataprovider/src/test/kotlin/org/sorapointa/data/provider/DatabaseProviderTest.kt",
    "chars": 1275,
    "preview": "package org.sorapointa.data.provider\n\nimport org.jetbrains.exposed.sql.Column\nimport org.jetbrains.exposed.sql.Table\nimp"
  },
  {
    "path": "sorapointa-dataprovider/src/test/kotlin/org/sorapointa/data/provider/FileProviderTest.kt",
    "chars": 2554,
    "preview": "package org.sorapointa.data.provider\n\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport kotli"
  },
  {
    "path": "sorapointa-dataprovider/src/test/kotlin/org/sorapointa/data/provider/Init.kt",
    "chars": 189,
    "preview": "package org.sorapointa.data.provider\n\nimport kotlinx.coroutines.runBlocking\n\nfun initTestDataProvider(): Unit = runBlock"
  },
  {
    "path": "sorapointa-dispatch/build.gradle.kts",
    "chars": 1312,
    "preview": "@file:Suppress(\"GradlePackageUpdate\")\n\nplugins {\n    kotlin(\"plugin.serialization\")\n    application\n    `sorapointa-conv"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/DispatchBundle.kt",
    "chars": 500,
    "preview": "package org.sorapointa.dispatch\n\nimport org.jetbrains.annotations.Nls\nimport org.jetbrains.annotations.PropertyKey\nimpor"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/DispatchServer.kt",
    "chars": 11715,
    "preview": "package org.sorapointa.dispatch\n\nimport com.charleskorn.kaml.YamlComment\nimport com.password4j.types.Argon2\nimport io.kt"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/data/AccountData.kt",
    "chars": 6554,
    "preview": "package org.sorapointa.dispatch.data\n\nimport com.password4j.Argon2Function\nimport com.password4j.Password\nimport io.ktor"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/data/DispatchData.kt",
    "chars": 8792,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.dispatch.data\n\nimport kotlinx.serialization.SerialName\nimport kotlinx.s"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/data/DispatchKeyData.kt",
    "chars": 1685,
    "preview": "package org.sorapointa.dispatch.data\n\nimport org.jetbrains.exposed.dao.Entity\nimport org.jetbrains.exposed.dao.EntityCla"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/data/SwitchData.kt",
    "chars": 17840,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.dispatch.data\n\nobject CodeSwitchData {\n    const val DEFAULT = 0\n    co"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/events/DispatchEvent.kt",
    "chars": 2540,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.dispatch.events\n\nimport io.ktor.server.application.*\nimport org.sorapoi"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/plugins/HTTP.kt",
    "chars": 233,
    "preview": "package org.sorapointa.dispatch.plugins\n\nimport io.ktor.server.application.*\nimport io.ktor.server.plugins.compression.*"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/plugins/Monitoring.kt",
    "chars": 811,
    "preview": "package org.sorapointa.dispatch.plugins\n\nimport io.ktor.http.*\nimport io.ktor.server.application.*\nimport io.ktor.server"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/plugins/RouteHandler.kt",
    "chars": 18120,
    "preview": "package org.sorapointa.dispatch.plugins\n\nimport io.ktor.client.call.*\nimport io.ktor.client.request.*\nimport io.ktor.cli"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/plugins/Routing.kt",
    "chars": 5131,
    "preview": "package org.sorapointa.dispatch.plugins\n\nimport io.ktor.server.application.*\nimport io.ktor.server.response.*\nimport io."
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/plugins/Serialization.kt",
    "chars": 282,
    "preview": "package org.sorapointa.dispatch.plugins\n\nimport io.ktor.serialization.kotlinx.json.*\nimport io.ktor.server.application.*"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/plugins/StatusPage.kt",
    "chars": 615,
    "preview": "package org.sorapointa.dispatch.plugins\n\nimport io.ktor.http.*\nimport io.ktor.server.application.*\nimport io.ktor.server"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/utils/CertBuilder.kt",
    "chars": 4372,
    "preview": "package org.sorapointa.dispatch.utils\n\nimport io.ktor.network.tls.*\nimport io.ktor.network.tls.extensions.*\nimport io.kt"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/utils/Certificates.kt",
    "chars": 12901,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.dispatch.utils\n\nimport io.ktor.network.tls.*\nimport io.ktor.utils.io.co"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/utils/KeyProvider.kt",
    "chars": 3188,
    "preview": "package org.sorapointa.dispatch.utils\n\nimport io.ktor.network.tls.extensions.*\nimport io.ktor.util.*\nimport kotlinx.coro"
  },
  {
    "path": "sorapointa-dispatch/src/main/kotlin/org/sorapointa/dispatch/utils/Route.kt",
    "chars": 393,
    "preview": "package org.sorapointa.dispatch.utils\n\nimport io.ktor.server.application.*\nimport io.ktor.server.routing.*\nimport io.kto"
  },
  {
    "path": "sorapointa-dispatch/src/main/resources/messages/DispatchBundle.properties",
    "chars": 559,
    "preview": "dispatch.login.error.split='Please use `:` to split your username and password in the username input field'\ndispatch.log"
  },
  {
    "path": "sorapointa-dispatch/src/main/resources/messages/DispatchBundle_zh_CN.properties",
    "chars": 352,
    "preview": "dispatch.login.error.split=请使用 `:` 在用户名输入框中分割您的帐号和密码\ndispatch.login.error.length.name=输入的名称长度必须在 3-16 字符之间\ndispatch.logi"
  },
  {
    "path": "sorapointa-dispatch/src/test/kotlin/org/sorapointa/dispatch/AccountTest.kt",
    "chars": 3280,
    "preview": "package org.sorapointa.dispatch\n\nimport kotlinx.coroutines.joinAll\nimport kotlinx.coroutines.launch\nimport org.jetbrains"
  },
  {
    "path": "sorapointa-dispatch/src/test/kotlin/org/sorapointa/dispatch/CertTest.kt",
    "chars": 294,
    "preview": "package org.sorapointa.dispatch\n\nimport kotlinx.coroutines.runBlocking\nimport org.junit.jupiter.api.Test\nimport org.sora"
  },
  {
    "path": "sorapointa-dispatch/src/test/kotlin/org/sorapointa/dispatch/DispatchServerTest.kt",
    "chars": 619,
    "preview": "package org.sorapointa.dispatch\n\nimport io.ktor.client.request.*\nimport io.ktor.client.statement.*\nimport io.ktor.http.*"
  },
  {
    "path": "sorapointa-event/README.md",
    "chars": 5249,
    "preview": "# Event Module\n\n[简体中文](README.zh-CN.md)\n\nYou could refer a lot of examples provided in [EventPipelineTest](src/test/kotl"
  },
  {
    "path": "sorapointa-event/README.zh-CN.md",
    "chars": 4745,
    "preview": "# 事件模块\n\n[English](README.md)\n\n你可以查看 [EventPipelineTest](src/test/kotlin/org/sorapointa/event/EventPipelineTest.kt) 提供的例子"
  },
  {
    "path": "sorapointa-event/build.gradle.kts",
    "chars": 504,
    "preview": "@file:Suppress(\"GradlePackageUpdate\")\n\nplugins {\n    `sorapointa-conventions`\n    `sorapointa-publish`\n}\n\ndependencies {"
  },
  {
    "path": "sorapointa-event/src/main/kotlin/org/sorapointa/event/Event.kt",
    "chars": 2890,
    "preview": "package org.sorapointa.event\n\nimport kotlinx.atomicfu.atomic\n\n/**\n * Event Interface, if you want to implement your own "
  },
  {
    "path": "sorapointa-event/src/main/kotlin/org/sorapointa/event/EventManager.kt",
    "chars": 10553,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.event\n\nimport com.charleskorn.kaml.YamlComment\nimport kotlinx.atomicfu."
  },
  {
    "path": "sorapointa-event/src/main/kotlin/org/sorapointa/event/StateController.kt",
    "chars": 10936,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.event\n\nimport kotlinx.atomicfu.atomic\nimport kotlinx.atomicfu.update\nim"
  },
  {
    "path": "sorapointa-event/src/test/kotlin/org/sorapointa/event/EventPipelineTest.kt",
    "chars": 4488,
    "preview": "package org.sorapointa.event\n\nimport kotlinx.atomicfu.atomic\nimport kotlinx.coroutines.*\nimport org.junit.jupiter.api.Be"
  },
  {
    "path": "sorapointa-event/src/test/kotlin/org/sorapointa/event/StateControllerTest.kt",
    "chars": 6271,
    "preview": "package org.sorapointa.event\n\nimport kotlinx.atomicfu.atomic\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.j"
  },
  {
    "path": "sorapointa-i18n/build.gradle.kts",
    "chars": 326,
    "preview": "plugins {\n    `sorapointa-conventions`\n    `sorapointa-publish`\n    kotlin(\"plugin.serialization\")\n}\n\ndependencies {\n   "
  },
  {
    "path": "sorapointa-i18n/src/main/kotlin/org/sorapointa/utils/I18n.kt",
    "chars": 2258,
    "preview": "package org.sorapointa.utils\n\nimport com.charleskorn.kaml.YamlComment\nimport kotlinx.serialization.KSerializer\nimport ko"
  },
  {
    "path": "sorapointa-i18n/src/main/kotlin/org/sorapointa/utils/MessageBundle.kt",
    "chars": 4274,
    "preview": "package org.sorapointa.utils\n\nimport org.jetbrains.annotations.Nls\nimport org.jetbrains.annotations.NonNls\nimport org.je"
  },
  {
    "path": "sorapointa-i18n/src/test/kotlin/org/sorapointa/utils/I18nTest.kt",
    "chars": 1332,
    "preview": "package org.sorapointa.utils\n\nimport org.junit.jupiter.api.Test\nimport java.util.*\nimport kotlin.test.assertEquals\n\nclas"
  },
  {
    "path": "sorapointa-i18n/src/test/kotlin/org/sorapointa/utils/LocalSerializerTest.kt",
    "chars": 437,
    "preview": "package org.sorapointa.utils\n\nimport org.junit.jupiter.api.Test\nimport java.util.*\nimport kotlin.test.assertEquals\n\nclas"
  },
  {
    "path": "sorapointa-i18n/src/test/kotlin/org/sorapointa/utils/TestBundle.kt",
    "chars": 447,
    "preview": "package org.sorapointa.utils\n\nimport org.jetbrains.annotations.Nls\nimport org.jetbrains.annotations.PropertyKey\nimport j"
  },
  {
    "path": "sorapointa-i18n/src/test/resources/messages/TestBundle.properties",
    "chars": 149,
    "preview": "sora.test.simple=This is a simple test\nsora.test.english.only=This string is in default\nsora.test.parameterized=This is "
  },
  {
    "path": "sorapointa-i18n/src/test/resources/messages/TestBundle_nl.properties",
    "chars": 41,
    "preview": "sora.test.simple=Dit is een simpele test\n"
  },
  {
    "path": "sorapointa-native/.gitignore",
    "chars": 20,
    "preview": "### IDEA ###\n/.idea\n"
  },
  {
    "path": "sorapointa-native/Cargo.toml",
    "chars": 209,
    "preview": "[package]\nname = \"spnative\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[profile.release]\nopt-level = 3\n\n[lib]\ncrate_type = [\"cd"
  },
  {
    "path": "sorapointa-native/README.md",
    "chars": 1489,
    "preview": "# Native Module\n\n[简体中文](README.zh-CN.md)\n\nThis module provide Rust native library.\n\n## Build\n\nTo build this module, Rust"
  },
  {
    "path": "sorapointa-native/README.zh-CN.md",
    "chars": 998,
    "preview": "# Native 模块\n\n[English](README.md)\n\n本模块提供 Rust 原生库。\n\n## 构建\n\n构建本模块需要 Rust 工具链。推荐通过 [Rustup](https://rustup.rs/) 安装,Unix-li"
  },
  {
    "path": "sorapointa-native/build.gradle.kts",
    "chars": 158,
    "preview": "plugins {\n    alias(libs.plugins.rust.wrapper)\n}\n\nrust {\n    release.set(true)\n    command.set(\"cargo\")\n    targets {\n  "
  },
  {
    "path": "sorapointa-native/src/jnienv.rs",
    "chars": 498,
    "preview": "use jni::{descriptors::Desc, objects::JClass, JNIEnv};\n\npub trait JNIEnvExt<'a> {\n    fn throw_new_or_eprint<'c, T>(&sel"
  },
  {
    "path": "sorapointa-native/src/lib.rs",
    "chars": 24,
    "preview": "mod jnienv;\nmod logger;\n"
  },
  {
    "path": "sorapointa-native/src/logger.rs",
    "chars": 3770,
    "preview": "use anyhow::{Context, Result};\nuse jni::objects::{JClass, JStaticMethodID};\nuse jni::signature::{Primitive, ReturnType};"
  },
  {
    "path": "sorapointa-native-wrapper/README.md",
    "chars": 854,
    "preview": "# Native Wrapper Module\n\nThis module is a wrapper for [`sorapointa-native`](../sorapointa-native/README.zh-CN.md) with J"
  },
  {
    "path": "sorapointa-native-wrapper/README.zh-CN.md",
    "chars": 442,
    "preview": "# Native Wrapper 模块\n\n本模块是对 [`sorapointa-native`](../sorapointa-native/README.zh-CN.md) 的 JNI 绑定和封装。\n\n## JNI 头文件\n\n可以使用 Gr"
  },
  {
    "path": "sorapointa-native-wrapper/build.gradle.kts",
    "chars": 529,
    "preview": "plugins {\n    `sorapointa-conventions`\n    id(\"fr.stardustenterprises.rust.importer\")\n}\n\ndependencies {\n    implementati"
  },
  {
    "path": "sorapointa-native-wrapper/src/main/kotlin/org/sorapointa/rust/Setup.kt",
    "chars": 427,
    "preview": "package org.sorapointa.rust\n\nimport fr.stardustenterprises.yanl.NativeLoader\nimport java.util.concurrent.atomic.AtomicBo"
  },
  {
    "path": "sorapointa-native-wrapper/src/main/kotlin/org/sorapointa/rust/logging/RustLogger.kt",
    "chars": 1287,
    "preview": "package org.sorapointa.rust.logging\n\nimport ch.qos.logback.classic.Logger\nimport mu.KotlinLogging\nimport org.slf4j.Logge"
  },
  {
    "path": "sorapointa-native-wrapper/src/test/kotlin/org/sorapointa/rust/logging/LoggerTest.kt",
    "chars": 311,
    "preview": "package org.sorapointa.rust.logging\n\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertFailsWith\n\nobject Logger"
  },
  {
    "path": "sorapointa-proto/build.gradle.kts",
    "chars": 655,
    "preview": "@file:Suppress(\"GradlePackageUpdate\")\n\nplugins {\n    `sorapointa-conventions`\n    `sorapointa-publish`\n    id(\"com.squar"
  },
  {
    "path": "sorapointa-proto/src/main/kotlin/org/sorapointa/proto/PacketUtils.kt",
    "chars": 2106,
    "preview": "package org.sorapointa.proto\n\nimport com.squareup.moshi.JsonAdapter\nimport com.squareup.moshi.Moshi\nimport com.squareup."
  },
  {
    "path": "sorapointa-proto/src/main/kotlin/org/sorapointa/proto/ProtoInfo.kt",
    "chars": 123652,
    "preview": "@file:Suppress(\"unused\")\n\npackage org.sorapointa.proto\n\nconst val START_MAGIC: UShort = 0x4567u\nconst val END_MAGIC: USh"
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityAppliedAbility.proto",
    "chars": 332,
    "preview": "syntax = \"proto3\";\n\nimport \"AbilityScalarValueEntry.proto\";\nimport \"AbilityString.proto\";\n\noption java_package = \"org.so"
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityAppliedModifier.proto",
    "chars": 700,
    "preview": "syntax = \"proto3\";\n\nimport \"AbilityAttachedModifier.proto\";\nimport \"AbilityString.proto\";\nimport \"ModifierDurability.pro"
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityAttachedModifier.proto",
    "chars": 257,
    "preview": "syntax = \"proto3\";\n\noption java_package = \"org.sorapointa.proto\";\n\nmessage AbilityAttachedModifier {\n  bool is_invalid ="
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityControlBlock.proto",
    "chars": 180,
    "preview": "syntax = \"proto3\";\n\nimport \"AbilityEmbryo.proto\";\n\noption java_package = \"org.sorapointa.proto\";\n\nmessage AbilityControl"
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityEmbryo.proto",
    "chars": 193,
    "preview": "syntax = \"proto3\";\n\noption java_package = \"org.sorapointa.proto\";\n\nmessage AbilityEmbryo {\n  uint32 ability_id = 1;\n  fi"
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityGadgetInfo.proto",
    "chars": 181,
    "preview": "syntax = \"proto3\";\n\noption java_package = \"org.sorapointa.proto\";\n\nmessage AbilityGadgetInfo {\n  uint32 camp_id = 1;\n  u"
  },
  {
    "path": "sorapointa-proto/src/proto/AbilityMixinRecoverInfo.proto",
    "chars": 457,
    "preview": "syntax = \"proto3\";\n\nimport \"BreakoutSnapShot.proto\";\nimport \"MassivePropSyncInfo.proto\";\n\noption java_package = \"org.sor"
  }
]

// ... and 279 more files (download for full content)

About this extraction

This page contains the full source code of the Sorapointa/Sorapointa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 479 files (1.2 MB), approximately 394.1k tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!