├── .github ├── FUNDING.yml └── workflows │ ├── documentation.yml │ └── release.yml ├── .gitignore ├── Documentation~ ├── docfx.json ├── filterConfig.yml ├── manual │ └── toc.yml ├── templates │ └── darkfx │ │ ├── partials │ │ ├── affix.tmpl.partial │ │ ├── footer.tmpl.partial │ │ └── head.tmpl.partial │ │ └── styles │ │ ├── main.css │ │ └── toggle-theme.js └── toc.yml ├── Editor.meta ├── Editor ├── CharacterLimitAttributeDrawer.cs ├── CharacterLimitAttributeDrawer.cs.meta ├── DiscordAssetDrawer.cs ├── DiscordAssetDrawer.cs.meta ├── DiscordEventDrawer.cs ├── DiscordEventDrawer.cs.meta ├── DiscordManagerEditor.cs ├── DiscordManagerEditor.cs.meta ├── DiscordPartyDrawer.cs ├── DiscordPartyDrawer.cs.meta ├── DiscordSecretsDrawer.cs ├── DiscordSecretsDrawer.cs.meta ├── DiscordTimestampDrawer.cs ├── DiscordTimestampDrawer.cs.meta ├── DiscordUserDrawer.cs ├── DiscordUserDrawer.cs.meta ├── Extensions.cs ├── Extensions.cs.meta ├── NativeDllHandler.cs ├── NativeDllHandler.cs.meta ├── com.lachee.discordrpc.editor.asmdef └── com.lachee.discordrpc.editor.asmdef.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Resources.meta ├── Resources ├── discord_logo.png ├── discord_logo.png.meta ├── discord_presence.png ├── discord_presence.png.meta ├── discord_wumpus.png ├── discord_wumpus.png.meta ├── logo.png └── logo.png.meta ├── Runtime.meta ├── Runtime ├── Attributes.meta ├── Attributes │ ├── CharacterLimitAttribute.cs │ └── CharacterLimitAttribute.cs.meta ├── Control.meta ├── Control │ ├── MessageEvents.cs │ ├── MessageEvents.cs.meta │ ├── UnityLogger.cs │ ├── UnityLogger.cs.meta │ ├── UnityNamedPipe.cs │ └── UnityNamedPipe.cs.meta ├── DiscordManager.cs ├── DiscordManager.cs.meta ├── Events.meta ├── Events │ ├── Events.cs │ └── Events.cs.meta ├── Link.xml ├── Link.xml.meta ├── NamedPipeClient.meta ├── NamedPipeClient │ ├── IO.meta │ ├── IO │ │ ├── Exceptions.meta │ │ ├── Exceptions │ │ │ ├── NamedPipeConnectionException.cs │ │ │ ├── NamedPipeConnectionException.cs.meta │ │ │ ├── NamedPipeOpenException.cs │ │ │ ├── NamedPipeOpenException.cs.meta │ │ │ ├── NamedPipeReadException.cs │ │ │ ├── NamedPipeReadException.cs.meta │ │ │ ├── NamedPipeWriteException.cs │ │ │ └── NamedPipeWriteException.cs.meta │ │ ├── NamedPipeClientStream.cs │ │ └── NamedPipeClientStream.cs.meta │ ├── Plugins.meta │ ├── Plugins │ │ ├── x86_64.meta │ │ └── x86_64 │ │ │ ├── NativeNamedPipe.dll │ │ │ ├── NativeNamedPipe.dll.meta │ │ │ ├── NativeNamedPipe.so │ │ │ └── NativeNamedPipe.so.meta │ ├── ReadMe.rtf │ └── ReadMe.rtf.meta ├── Plugins.meta ├── Plugins │ ├── DiscordRPC.dll │ ├── DiscordRPC.dll.meta │ ├── DiscordRPC.pdb │ ├── DiscordRPC.pdb.meta │ ├── DiscordRPC.xml │ └── DiscordRPC.xml.meta ├── Presence.meta ├── Presence │ ├── Asset.cs │ ├── Asset.cs.meta │ ├── Button.cs │ ├── Button.cs.meta │ ├── Event.cs │ ├── Event.cs.meta │ ├── Party.cs │ ├── Party.cs.meta │ ├── Presence.cs │ ├── Presence.cs.meta │ ├── Secrets.cs │ ├── Secrets.cs.meta │ ├── Timestamp.cs │ ├── Timestamp.cs.meta │ ├── User.cs │ └── User.cs.meta ├── com.lachee.discordrpc.runtime.asmdef └── com.lachee.discordrpc.runtime.asmdef.meta ├── Samples~ ├── Rich Presence.meta └── Rich Presence │ ├── ConstantRotate.cs │ ├── ConstantRotate.cs.meta │ ├── Example_ReadyListener.cs │ ├── Example_ReadyListener.cs.meta │ ├── Example_RichPresence.cs │ ├── Example_RichPresence.cs.meta │ ├── Example_RichPresence.unity │ ├── Example_RichPresence.unity.meta │ ├── Example_SetStatusButton.cs │ ├── Example_SetStatusButton.cs.meta │ ├── Spinning Cubes.prefab │ └── Spinning Cubes.prefab.meta ├── package.json └── package.json.meta /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | ko_fi: 3 | lachee 4 | github: 5 | lachee 6 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Create Documentation 📚 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | # Build the documentation 10 | build: 11 | runs-on: windows-latest # Required by DocFX 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Install DocFX 17 | run: | 18 | choco install -y docfx 19 | docfx --version 20 | 21 | - name: Use README.md as index.md 22 | run: cp README.md Documentation~/index.md 23 | 24 | - name: Build 25 | run: docfx Documentation~/docfx.json -t default,templates/darkfx 26 | 27 | # Upload the generated documentation 28 | - name: Upload site artifact 29 | uses: actions/upload-artifact@v1 30 | with: 31 | name: _site 32 | path: _site # Must equals the 'build.dest' value on your docfx.json 33 | 34 | # Deploy the generated documentation to the gh-pages branch 35 | deploy: 36 | needs: build 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v2 41 | 42 | 43 | # Download the generated documentation 44 | - name: Download site artifact 45 | uses: actions/download-artifact@v1 46 | with: 47 | name: _site 48 | 49 | - name: Deploy 50 | uses: peaceiris/actions-gh-pages@v3 51 | with: 52 | github_token: ${{ secrets.GITHUB_TOKEN }} 53 | publish_branch: gh-pages 54 | publish_dir: _site -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 📦 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | package_path: "lachee-utilities.unitypackage" 10 | 11 | jobs: 12 | # build the packages 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-dotnet@v3 19 | with: 20 | dotnet-version: '3.1.x' 21 | 22 | # Install the packager 23 | - name: Install Unity Packager 24 | run: | 25 | git clone https://github.com/Lachee/Unity-Package-Exporter.git tools/unity-package-exporter 26 | dotnet publish -c Release -o tools tools/unity-package-exporter/UnityPackageExporter 27 | 28 | # Pack the assets 29 | - name: Package 30 | run: | 31 | echo "Creating package" 32 | dotnet tools/UnityPackageExporter.dll \ 33 | ./ \ 34 | discord-rpc-unity.unitypackage \ 35 | --sub-folder "DiscordRPC" \ 36 | --exclude "tools" \ 37 | --exclude "*~" \ 38 | --exclude ".*" \ 39 | --exclude "*.unitypackage" \ 40 | --skip-dependency-check \ 41 | -r . 42 | 43 | # Upload artifact 44 | - name: Upload Artifact 45 | uses: actions/upload-artifact@v3.0.0 46 | with: 47 | name: Unity Package 48 | path: discord-rpc-unity.unitypackage 49 | 50 | # Tag the build 51 | tag: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - uses: Klemensas/action-autotag@stable 56 | id: auto-tag 57 | with: 58 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 59 | tag_prefix: "v" 60 | outputs: 61 | tag: ${{ steps.auto-tag.outputs.tagname }} 62 | 63 | # Update the release 64 | release: 65 | runs-on: ubuntu-latest 66 | needs: [ build, tag ] 67 | if: ${{ startsWith(needs.tag.outputs.tag, 'v') }} 68 | steps: 69 | - uses: actions/checkout@v3 70 | 71 | - uses: actions/download-artifact@v3 72 | with: 73 | path: artifacts 74 | 75 | - uses: "marvinpinto/action-automatic-releases@latest" 76 | with: 77 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 78 | automatic_release_tag: ${{ needs.tag.outputs.tag }} 79 | prerelease: true 80 | title: Release ${{ needs.tag.outputs.tag }} 81 | files: artifacts/**/*.unitypackage 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ====== # 2 | # Autogenerated VS/MD solution and project files 3 | # ====== # 4 | *.csproj 5 | *.unityproj 6 | *.sln 7 | *.suo 8 | *.user 9 | *.userprefs 10 | *.pidb 11 | *.booproj 12 | 13 | # ====== # 14 | # ReSharper is a .NET coding add-in 15 | # ====== # 16 | _ReSharper*/ 17 | *.[Rr]e[Ss]harper 18 | 19 | # ====== # 20 | # DotCover is a Code Coverage Tool 21 | # ====== # 22 | *.dotCover 23 | 24 | # ====== # 25 | # NCrunch 26 | # ====== # 27 | *.ncrunch* 28 | .*crunch*.local.xml 29 | 30 | # ====== # 31 | # Unity3D Generated File On Crash Reports 32 | # ====== # 33 | sysinfo.txt 34 | 35 | # ====== # 36 | # OS Generated 37 | # ====== # 38 | .DS_Store* 39 | ._* 40 | .Spotlight-V100 41 | .Trashes 42 | ehthumbs.db 43 | Thumbs.db 44 | $RECYCLE.BIN/ 45 | Desktop.ini 46 | 47 | # ====== # 48 | # Blender backup files 49 | # ====== # 50 | *.blend[1-9] 51 | *.blend[1-9].meta 52 | 53 | # ====== # 54 | # Sourcetree backup files # 55 | # ====== # 56 | *.orig 57 | *.orig.meta 58 | 59 | # ====== # 60 | # Visual Code files 61 | # ====== # 62 | VSCode.meta 63 | VSCode/* 64 | .vscode 65 | 66 | # ====== # 67 | # Others # 68 | # ====== # 69 | .~lock.* 70 | .vs/ 71 | *_backup 72 | *_backup.meta 73 | *.~*#* 74 | /_site 75 | /tools 76 | */obj/* 77 | Documentation~/api/* 78 | Documentation~/manual/changelog.* 79 | Documentation~/index.* 80 | !.github/workflows/increment_version.sh 81 | 82 | 83 | _site.meta 84 | Documentation~/**/*.meta 85 | Documentation~/obj 86 | Documentation~/api -------------------------------------------------------------------------------- /Documentation~/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "..", 7 | "files": [ "Runtime/**/*.cs", "Editor/**/*.cs" ] 8 | } 9 | ], 10 | "globalNamespaceId": "Global", 11 | "dest": "api", 12 | "properties": { 13 | "DefineConstants": "UNITY_EDITOR=1" 14 | } 15 | } 16 | ], 17 | "build": { 18 | "globalMetadata": { 19 | "_appTitle": "Utilities", 20 | "_appFooter": "MIT by Lachee", 21 | "_enableSearch": true 22 | }, 23 | "content": [ 24 | { 25 | "files": [ 26 | "toc.yml", 27 | "index.md" 28 | ] 29 | }, 30 | { 31 | "src": "api", 32 | "files": [ 33 | "*.yml" 34 | ], 35 | "dest": "api" 36 | }, 37 | { 38 | "src": "manual", 39 | "files": [ 40 | "toc.yml", 41 | "*.md" 42 | ], 43 | "dest": "manual" 44 | } 45 | ], 46 | "overwrite": [ 47 | { 48 | "src": "..", 49 | "files": [ "Runtime/**/*.md", "Editor/**/*.md" ] 50 | } 51 | ], 52 | "resource": [ 53 | { 54 | "files": [ "resources/**/*" ] 55 | } 56 | ], 57 | "sitemap": 58 | { 59 | "baseUrl": "https://lachee.github.io/unity-utilities/", 60 | "changefreq": "yearly", 61 | "fileOptions": { 62 | "api/*": { 63 | "changefreq": "daily" 64 | } 65 | } 66 | }, 67 | "xref": [ "https://normanderwan.github.io/UnityXrefMaps/xrefmap.yml" ], 68 | "xrefService": [ "https://xref.docs.microsoft.com/query?uid={uid}" ], 69 | "dest": "../_site" 70 | } 71 | } -------------------------------------------------------------------------------- /Documentation~/filterConfig.yml: -------------------------------------------------------------------------------- 1 | apiRules: 2 | - include: 3 | uidRegex: ^Lachee 4 | type: Namespace 5 | - include: 6 | uidRegex: ^Global 7 | type: Namespace 8 | - exclude: 9 | uidRegex: .* 10 | type: Namespace -------------------------------------------------------------------------------- /Documentation~/manual/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Coniunctis nec qui 2 | href: e.md 3 | - name: Etiam nantemque exul 4 | href: etiam.md -------------------------------------------------------------------------------- /Documentation~/templates/darkfx/partials/affix.tmpl.partial: -------------------------------------------------------------------------------- 1 | {{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} 2 | 3 | 41 | -------------------------------------------------------------------------------- /Documentation~/templates/darkfx/partials/footer.tmpl.partial: -------------------------------------------------------------------------------- 1 | {{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} 2 | 3 | -------------------------------------------------------------------------------- /Documentation~/templates/darkfx/partials/head.tmpl.partial: -------------------------------------------------------------------------------- 1 | {{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} 2 | 3 | 4 | 5 | 6 | {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} 7 | 8 | 9 | 10 | {{#_description}}{{/_description}} 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{#_noindex}}{{/_noindex}} 18 | {{#_enableSearch}}{{/_enableSearch}} 19 | {{#_enableNewTab}}{{/_enableNewTab}} 20 | -------------------------------------------------------------------------------- /Documentation~/templates/darkfx/styles/main.css: -------------------------------------------------------------------------------- 1 | :root, body.dark-theme { 2 | --color-foreground: #ccd5dc; 3 | --color-navbar: #66666d; 4 | --color-breadcrumb: #999; 5 | --color-underline: #ddd; 6 | --color-toc-hover: #fff; 7 | --color-background: #2d2d30; 8 | --color-background-subnav: #333337; 9 | --color-background-dark: #1e1e1e; 10 | --color-background-table-alt: #212123; 11 | --color-background-quote: #69696e; 12 | } 13 | 14 | body.light-theme { 15 | --color-foreground: #171717; 16 | --color-breadcrumb: #4a4a4a; 17 | --color-toc-hover: #4c4c4c; 18 | --color-background: #ffffff; 19 | --color-background-subnav: #f5f5f5; 20 | --color-background-dark: #ddd; 21 | --color-background-table-alt: #f9f9f9; 22 | } 23 | 24 | body { 25 | color: var(--color-foreground); 26 | line-height: 1.5; 27 | font-size: 14px; 28 | -ms-text-size-adjust: 100%; 29 | -webkit-text-size-adjust: 100%; 30 | word-wrap: break-word; 31 | background-color: var(--color-background); 32 | } 33 | 34 | .btn.focus, .btn:focus, .btn:hover { 35 | color: var(--color-foreground); 36 | } 37 | 38 | h1 { 39 | font-weight: 600; 40 | font-size: 32px; 41 | } 42 | 43 | h2 { 44 | font-weight: 600; 45 | font-size: 24px; 46 | line-height: 1.8; 47 | } 48 | 49 | h3 { 50 | font-weight: 600; 51 | font-size: 20px; 52 | line-height: 1.8; 53 | } 54 | 55 | h5 { 56 | font-size: 14px; 57 | padding: 10px 0px; 58 | } 59 | 60 | article h1, article h2, article h3, article h4 { 61 | margin-top: 35px; 62 | margin-bottom: 15px; 63 | } 64 | 65 | article h4 { 66 | padding-bottom: 8px; 67 | border-bottom: 2px solid var(--color-underline); 68 | } 69 | 70 | .navbar-brand>img { 71 | color: var(--color-background); 72 | } 73 | 74 | .navbar { 75 | border: none; 76 | } 77 | 78 | .subnav { 79 | border-top: 1px solid var(--color-underline); 80 | background-color: var(--color-background-subnav); 81 | } 82 | 83 | .sidenav, .fixed_header, .toc { 84 | background-color: var(--color-background); 85 | } 86 | 87 | .navbar-inverse { 88 | background-color: var(--color-background-dark); 89 | z-index: 100; 90 | } 91 | 92 | .navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text { 93 | color: var(--color-navbar); 94 | background-color: var(--color-background-dark); 95 | border-bottom: 3px solid transparent; 96 | padding-bottom: 12px; 97 | } 98 | 99 | .navbar-inverse .navbar-nav>li>a:focus, .navbar-inverse .navbar-nav>li>a:hover { 100 | color: var(--color-foreground); 101 | background-color: var(--color-background-dark); 102 | border-bottom: 3px solid var(--color-background-subnav); 103 | transition: all ease 0.25s; 104 | } 105 | 106 | .navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover { 107 | color: var(--color-foreground); 108 | background-color: var(--color-background-dark); 109 | border-bottom: 3px solid var(--color-foreground); 110 | transition: all ease 0.25s; 111 | } 112 | 113 | .navbar-form .form-control { 114 | border: none; 115 | border-radius: 0; 116 | } 117 | 118 | .light-theme .navbar-brand svg { 119 | filter: brightness(20%); 120 | } 121 | 122 | .toc .level1>li { 123 | font-weight: 400; 124 | } 125 | 126 | .toc .nav>li>a { 127 | color: var(--color-foreground); 128 | } 129 | 130 | .sidefilter { 131 | background-color: var(--color-background); 132 | border-left: none; 133 | border-right: none; 134 | } 135 | 136 | .sidefilter { 137 | background-color: var(--color-background); 138 | border-left: none; 139 | border-right: none; 140 | } 141 | 142 | .toc-filter { 143 | padding: 10px; 144 | margin: 0; 145 | background-color: var(--color-background); 146 | } 147 | 148 | .toc-filter>input { 149 | border: none; 150 | border-radius: unset; 151 | background-color: var(--color-background-subnav); 152 | padding: 5px 0 5px 20px; 153 | font-size: 90% 154 | } 155 | 156 | .toc-filter>.clear-icon { 157 | position: absolute; 158 | top: 17px; 159 | right: 15px; 160 | } 161 | 162 | .toc-filter>input:focus { 163 | color: var(--color-foreground); 164 | transition: all ease 0.25s; 165 | } 166 | 167 | .toc-filter>.filter-icon { 168 | display: none; 169 | } 170 | 171 | .sidetoc>.toc { 172 | background-color: var(--color-background); 173 | overflow-x: hidden; 174 | } 175 | 176 | .sidetoc { 177 | background-color: var(--color-background); 178 | border: none; 179 | } 180 | 181 | .alert { 182 | background-color: inherit; 183 | border: none; 184 | padding: 10px 0; 185 | border-radius: 0; 186 | } 187 | 188 | .alert>p { 189 | margin-bottom: 0; 190 | padding: 5px 10px; 191 | border-bottom: 1px solid; 192 | background-color: var(--color-background-dark); 193 | } 194 | 195 | .alert>h5 { 196 | padding: 10px 15px; 197 | margin-top: 0; 198 | margin-bottom: 0; 199 | text-transform: uppercase; 200 | font-weight: bold; 201 | border-top: 2px solid; 202 | background-color: var(--color-background-dark); 203 | border-radius: none; 204 | } 205 | 206 | .alert>ul { 207 | margin-bottom: 0; 208 | padding: 5px 40px; 209 | } 210 | 211 | .alert-info { 212 | color: #1976d2; 213 | } 214 | 215 | .alert-warning { 216 | color: #f57f17; 217 | } 218 | 219 | .alert-danger { 220 | color: #d32f2f; 221 | } 222 | 223 | pre { 224 | padding: 9.5px; 225 | margin: 0 0 10px; 226 | font-size: 13px; 227 | word-break: break-all; 228 | word-wrap: break-word; 229 | background-color: var(--color-background-dark); 230 | border-radius: 0; 231 | border: none; 232 | } 233 | 234 | code { 235 | background: var(--color-background-dark) !important; 236 | border-radius: 2px; 237 | } 238 | 239 | .hljs { 240 | color: var(--color-foreground); 241 | } 242 | 243 | .toc .nav>li.active>.expand-stub::before, .toc .nav>li.in>.expand-stub::before, .toc .nav>li.in.active>.expand-stub::before, .toc .nav>li.filtered>.expand-stub::before { 244 | content: "▾"; 245 | } 246 | 247 | .toc .nav>li>.expand-stub::before, .toc .nav>li.active>.expand-stub::before { 248 | content: "▸"; 249 | } 250 | 251 | .affix ul ul>li>a:before { 252 | content: "|"; 253 | } 254 | 255 | .breadcrumb { 256 | background-color: var(--color-background-subnav); 257 | } 258 | 259 | .breadcrumb .label.label-primary { 260 | background: #444; 261 | border-radius: 0; 262 | font-weight: normal; 263 | font-size: 100%; 264 | } 265 | 266 | #breadcrumb .breadcrumb>li a { 267 | border-radius: 0; 268 | font-weight: normal; 269 | font-size: 85%; 270 | display: inline; 271 | padding: 0 .6em 0; 272 | line-height: 1; 273 | text-align: center; 274 | white-space: nowrap; 275 | vertical-align: baseline; 276 | color: var(--color-breadcrumb); 277 | } 278 | 279 | #breadcrumb .breadcrumb>li a:hover { 280 | color: var(--color-foreground); 281 | transition: all ease 0.25s; 282 | } 283 | 284 | .breadcrumb>li+li:before { 285 | content: "⯈"; 286 | font-size: 75%; 287 | color: var(--color-background-dark); 288 | padding: 0; 289 | } 290 | 291 | .light-theme .breadcrumb>li+li:before { 292 | color: var(--color-foreground) 293 | } 294 | 295 | .toc .level1>li { 296 | font-weight: 600; 297 | font-size: 130%; 298 | padding-left: 5px; 299 | } 300 | 301 | .footer { 302 | border-top: none; 303 | background-color: var(--color-background-dark); 304 | padding: 15px 0; 305 | font-size: 90%; 306 | } 307 | 308 | .toc .nav>li>a:hover, .toc .nav>li>a:focus { 309 | color: var(--color-toc-hover); 310 | transition: all ease 0.1s; 311 | } 312 | 313 | .form-control { 314 | background-color: var(--color-background-subnav); 315 | border: none; 316 | border-radius: 0; 317 | -webkit-box-shadow: none; 318 | box-shadow: none; 319 | } 320 | 321 | .form-control:focus { 322 | border-color: #66afe9; 323 | outline: 0; 324 | -webkit-box-shadow: none; 325 | box-shadow: none; 326 | } 327 | 328 | input#search-query:focus { 329 | color: var(--color-foreground); 330 | } 331 | 332 | .table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th { 333 | border: 1px solid var(--color-background-dark); 334 | } 335 | 336 | .table-striped>tbody>tr:nth-of-type(odd) { 337 | background-color: var(--color-background-table-alt); 338 | } 339 | 340 | blockquote { 341 | padding: 10px 20px; 342 | margin: 0 0 10px; 343 | font-size: 110%; 344 | border-left: 5px solid var(--color-background-quote); 345 | color: var(--color-background-quote); 346 | } 347 | 348 | .pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover { 349 | background-color: var(--color-background-subnav); 350 | border-color: var(--color-background-subnav); 351 | } 352 | 353 | .breadcrumb>li, .pagination { 354 | display: inline; 355 | } 356 | 357 | .tabGroup a[role="tab"] { 358 | border-bottom: 2px solid var(--color-background-dark); 359 | } 360 | 361 | .tabGroup a[role="tab"][aria-selected="true"] { 362 | color: var(--color-foreground); 363 | } 364 | 365 | .tabGroup section[role="tabpanel"] { 366 | border: 1px solid var(--color-background-dark); 367 | } 368 | 369 | .sideaffix > div.contribution > ul > li > a.contribution-link:hover { 370 | background-color: var(--color-background); 371 | } 372 | 373 | .switch { 374 | position: relative; 375 | display: inline-block; 376 | width: 40px; 377 | height: 20px; 378 | } 379 | 380 | .switch input { 381 | opacity: 0; 382 | width: 0; 383 | height: 0; 384 | } 385 | 386 | .slider { 387 | position: absolute; 388 | cursor: pointer; 389 | top: 0; 390 | left: 0; 391 | right: 0; 392 | bottom: 0; 393 | background-color: #ccc; 394 | -webkit-transition: .4s; 395 | transition: .4s; 396 | } 397 | 398 | .slider:before { 399 | position: absolute; 400 | content: ""; 401 | height: 14px; 402 | width: 14px; 403 | left: 4px; 404 | bottom: 3px; 405 | background-color: white; 406 | -webkit-transition: .4s; 407 | transition: .4s; 408 | } 409 | 410 | input:checked + .slider { 411 | background-color: #337ab7; 412 | } 413 | 414 | input:focus + .slider { 415 | box-shadow: 0 0 1px #337ab7; 416 | } 417 | 418 | input:checked + .slider:before { 419 | -webkit-transform: translateX(19px); 420 | -ms-transform: translateX(19px); 421 | transform: translateX(19px); 422 | } 423 | 424 | /* Rounded sliders */ 425 | .slider.round { 426 | border-radius: 20px; 427 | } 428 | 429 | .slider.round:before { 430 | border-radius: 50%; 431 | } 432 | .toggle-mode .icon { 433 | display: inline-block; 434 | } 435 | 436 | .toggle-mode .icon i { 437 | font-style: normal; 438 | font-size: 17px; 439 | display: inline-block; 440 | padding-right: 7px; 441 | padding-left: 7px; 442 | vertical-align: middle; 443 | } 444 | 445 | @media (min-width: 1600px) { 446 | .container { 447 | width: 100%; 448 | } 449 | .sidefilter { 450 | width: 18%; 451 | } 452 | .sidetoc { 453 | width: 18%; 454 | } 455 | .article.grid-right { 456 | margin-left: 19%; 457 | } 458 | .sideaffix { 459 | width: 11.5%; 460 | } 461 | .affix ul>li.active>a { 462 | white-space: initial; 463 | } 464 | .affix ul>li>a { 465 | width: 99%; 466 | overflow: hidden; 467 | white-space: nowrap; 468 | text-overflow: ellipsis; 469 | } 470 | } -------------------------------------------------------------------------------- /Documentation~/templates/darkfx/styles/toggle-theme.js: -------------------------------------------------------------------------------- 1 | const sw = document.getElementById("switch-style"), sw_mobile = document.getElementById("switch-style-m"), b = document.body; 2 | if (b) { 3 | function toggleTheme(target, dark) { 4 | target.classList.toggle("dark-theme", dark) 5 | target.classList.toggle("light-theme", !dark) 6 | } 7 | 8 | function switchEventListener() { 9 | toggleTheme(b, this.checked); 10 | if (window.localStorage) { 11 | this.checked ? localStorage.setItem("theme", "dark-theme") : localStorage.setItem("theme", "light-theme") 12 | } 13 | } 14 | 15 | var isDarkTheme = !window.localStorage || !window.localStorage.getItem("theme") || window.localStorage && localStorage.getItem("theme") === "dark-theme"; 16 | 17 | if(sw && sw_mobile){ 18 | sw.checked = isDarkTheme; 19 | sw_mobile.checked = isDarkTheme; 20 | 21 | sw.addEventListener("change", switchEventListener); 22 | sw_mobile.addEventListener("change", switchEventListener); 23 | 24 | // sync state between switches 25 | sw.addEventListener("change", function() { 26 | sw_mobile.checked = this.checked; 27 | }); 28 | 29 | sw_mobile.addEventListener("change", function() { 30 | sw.checked = this.checked; 31 | }); 32 | } 33 | 34 | toggleTheme(b, isDarkTheme); 35 | } -------------------------------------------------------------------------------- /Documentation~/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Scripting Reference 2 | href: api/ -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3ea1908ce15e5444a570ff831713141 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/CharacterLimitAttributeDrawer.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord.Attributes; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace Lachee.Discord.Editor 8 | { 9 | [CustomPropertyDrawer(typeof(CharacterLimitAttribute))] 10 | internal sealed class CharacterLimitAttributeDrawer : PropertyDrawer 11 | { 12 | // Draw the property inside the given rect 13 | public override void OnGUI(Rect pos, SerializedProperty property, GUIContent label) 14 | { 15 | // First get the attribute since it contains the range for the slider 16 | CharacterLimitAttribute range = attribute as CharacterLimitAttribute; 17 | 18 | if (property.propertyType != SerializedPropertyType.String) 19 | { 20 | EditorGUI.HelpBox(pos, "The CharLimit property is only valid on strings.", MessageType.Error); 21 | return; 22 | } 23 | 24 | //Store the size of the limit and the original colour 25 | Color original = GUI.color; 26 | 27 | //Make the box red if we are too big 28 | int remaining = range.max - property.stringValue.Length; 29 | if (remaining < 0) GUI.color = Color.red; 30 | 31 | //prepare the remaining label 32 | //string remainingLabel = property.stringValue.Length + "/" + range.max; 33 | string remainingLabel = remaining.ToString(); 34 | 35 | //Draw the label 36 | 37 | var remainingStyle = new GUIStyle() { alignment = TextAnchor.MiddleCenter }; 38 | var remainingContent = new GUIContent(remainingLabel, "Characters remaining in the text"); 39 | 40 | float remainingSize = 50; 41 | float textSize = pos.width - remainingSize - 5; 42 | 43 | Rect textRect = new Rect(pos.x, pos.y, textSize, pos.height); 44 | Rect labelRect = new Rect(pos.x + textSize + 5, pos.y, remainingSize, pos.height); 45 | 46 | //GUI.Box(textRect, GUIContent.none); 47 | GUI.Box(labelRect, GUIContent.none); 48 | 49 | //Draw the text field and the remaining contents field 50 | GUI.Label(labelRect, remainingContent, remainingStyle); 51 | EditorGUI.PropertyField(textRect, property, label); 52 | 53 | if (range.enforce && property.stringValue.Length > range.max) 54 | property.stringValue = property.stringValue.Substring(0, range.max); 55 | 56 | GUI.color = original; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Editor/CharacterLimitAttributeDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b363377b8fd3dee4699f4a9722c7f49a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordAssetDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord.Editor 5 | { 6 | [CustomPropertyDrawer(typeof(Asset))] 7 | internal sealed class DiscordAssetDrawer : PropertyDrawer 8 | { 9 | public const float keySize = 150; 10 | public const int lines = 3; 11 | 12 | public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) 13 | { 14 | SerializedProperty image = prop.FindPropertyRelative("image"); 15 | SerializedProperty tooltip = prop.FindPropertyRelative("tooltip"); 16 | SerializedProperty snowflake = prop.FindPropertyRelative("snowflake"); 17 | 18 | float h2 = pos.height / lines; 19 | EditorGUI.LabelField(new Rect(pos.x, pos.y, pos.width, EditorGUIUtility.singleLineHeight), label); 20 | 21 | if (snowflake.longValue > 0) 22 | EditorGUI.LabelField(pos, new GUIContent(" "), new GUIContent("(" + snowflake.longValue.ToString() + ")", "The unique Snowflake ID of the image. May not be accurate to the current image key.")); 23 | 24 | int indent = EditorGUI.indentLevel++; 25 | { 26 | EditorGUI.PropertyField(new Rect(pos.x, pos.y + h2 * 1, pos.width, h2), image, new GUIContent("Image Key", "The key of the image uploaded to the discord app page.")); 27 | EditorGUI.PropertyField(new Rect(pos.x, pos.y + h2 * 2 + 2, pos.width, h2), tooltip, new GUIContent("Tooltip", "The tooltip to be displayed for the image.")); 28 | } 29 | EditorGUI.indentLevel = indent; 30 | 31 | } 32 | 33 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 34 | { 35 | return (base.GetPropertyHeight(property, label) * lines) + 4; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Editor/DiscordAssetDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a4a02b1d03f5f84a82dd43f9dd91bbc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordEventDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord.Editor 5 | { 6 | [CustomPropertyDrawer(typeof(Event))] 7 | internal sealed class DiscordEventDrawer : PropertyDrawer 8 | { 9 | private bool INCLUDE_NONE = false; 10 | 11 | public const float keySize = 150; 12 | public const int lines = 3; 13 | 14 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 15 | { 16 | Event propval = (Event)property.intValue; 17 | Event newval = Event.None; 18 | 19 | Rect buttonPos; 20 | int offset = INCLUDE_NONE ? 0 : 1; 21 | float buttonWidth = (position.width - EditorGUIUtility.labelWidth) / (4 - offset); 22 | 23 | EditorGUI.LabelField(new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height), label); 24 | EditorGUI.BeginProperty(position, label, property); 25 | { 26 | 27 | if (INCLUDE_NONE) 28 | { 29 | buttonPos = new Rect(position.x + EditorGUIUtility.labelWidth + buttonWidth * 0, position.y, buttonWidth, position.height); 30 | if (GUI.Toggle(buttonPos, propval == Event.None, "None", EditorStyles.miniButtonLeft)) 31 | newval = Event.None; 32 | } 33 | 34 | buttonPos = new Rect(position.x + EditorGUIUtility.labelWidth + buttonWidth * (1 - offset), position.y, buttonWidth, position.height); 35 | if (GUI.Toggle(buttonPos, (propval & Event.Join) == Event.Join, "Join", INCLUDE_NONE ? EditorStyles.miniButtonMid : EditorStyles.miniButtonLeft)) 36 | newval |= Event.Join; 37 | 38 | buttonPos = new Rect(position.x + EditorGUIUtility.labelWidth + buttonWidth * (2 - offset), position.y, buttonWidth, position.height); 39 | if (GUI.Toggle(buttonPos, (propval & Event.Spectate) == Event.Spectate, "Spectate", EditorStyles.miniButtonMid)) 40 | newval |= Event.Spectate; 41 | 42 | buttonPos = new Rect(position.x + EditorGUIUtility.labelWidth + buttonWidth * (3 - offset), position.y, buttonWidth, position.height); 43 | if (GUI.Toggle(buttonPos, (propval & Event.JoinRequest) == Event.JoinRequest, "Invites", EditorStyles.miniButtonRight)) 44 | newval |= Event.JoinRequest; 45 | 46 | property.intValue = (int)newval; 47 | } 48 | EditorGUI.EndProperty(); 49 | 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Editor/DiscordEventDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: befe731a68d54c44190700759250750b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordManagerEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | 6 | namespace Lachee.Discord.Editor 7 | { 8 | [CustomEditor(typeof(DiscordManager))] 9 | internal sealed class DiscordManagerEditor : UnityEditor.Editor 10 | { 11 | const string BAD_COMPILE = "The current build platform is not supported by this version of Discord Rich Presence. A native library is requried from the offical Discord Rich Presence library. Future versions of DiscordRPC-Sharp will support Linux and Mac.\n\nFor convience, the properties will still be editable, but no attempt to connect to Discord will be made."; 12 | 13 | 14 | #if !(UNITY_WSA || UNITY_WSA_10_0 || UNITY_STANDALONE) 15 | public override void OnInspectorGUI() 16 | { 17 | EditorGUILayout.HelpBox(BAD_COMPILE, MessageType.Error); 18 | base.OnInspectorGUI(); 19 | } 20 | #endif 21 | } 22 | } -------------------------------------------------------------------------------- /Editor/DiscordManagerEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ef7a7ea2b7fec3e4393138572b0ede58 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordPartyDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord.Editor 5 | { 6 | [CustomPropertyDrawer(typeof(Party))] 7 | internal sealed class DiscordPartyDrawer : PropertyDrawer 8 | { 9 | public const float keySize = 150; 10 | public const int lines = 3; 11 | 12 | public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) 13 | { 14 | SerializedProperty identifer = prop.FindPropertyRelative("identifer"); 15 | SerializedProperty size = prop.FindPropertyRelative("size"); 16 | SerializedProperty sizeMax = prop.FindPropertyRelative("maxSize"); 17 | 18 | float h2 = pos.height / lines; 19 | EditorGUI.LabelField(new Rect(pos.x, pos.y, pos.width, EditorGUIUtility.singleLineHeight), label); 20 | 21 | int indent = EditorGUI.indentLevel++; 22 | { 23 | EditorGUI.PropertyField(new Rect(pos.x, pos.y + h2 * 1, pos.width, h2), identifer, new GUIContent("Identifier", "The unique ID for the party.")); 24 | EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(identifer.stringValue)); 25 | { 26 | float min = size.intValue; 27 | float max = Mathf.Max(min, sizeMax.intValue); 28 | float limitMax = Mathf.Max(max + 1, 8); 29 | 30 | float fieldSize = 22; 31 | Rect sliderRect = new Rect(pos.x, pos.y + h2 * 2 + 2, pos.width - (fieldSize + 5) * 2, h2); 32 | Rect fieldRectA = new Rect(pos.x + sliderRect.width + 5, pos.y + h2 * 2 + 2, fieldSize, h2); 33 | Rect fieldRectB = new Rect(pos.x + sliderRect.width + fieldSize + 10f, pos.y + h2 * 2 + 2, fieldSize, h2); 34 | 35 | EditorGUI.MinMaxSlider(sliderRect, new GUIContent("Size / Max Size", "The current size of the party"), ref min, ref max, 0, limitMax); 36 | size.intValue = Mathf.FloorToInt(min); 37 | sizeMax.intValue = Mathf.FloorToInt(max); 38 | 39 | EditorGUI.indentLevel = 0; 40 | size.intValue = EditorGUI.IntField(fieldRectA, GUIContent.none, size.intValue); 41 | sizeMax.intValue = EditorGUI.IntField(fieldRectB, GUIContent.none, sizeMax.intValue); 42 | 43 | } 44 | EditorGUI.EndDisabledGroup(); 45 | 46 | } 47 | EditorGUI.indentLevel = indent; 48 | 49 | } 50 | 51 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 52 | { 53 | return (base.GetPropertyHeight(property, label) * lines) + 4; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Editor/DiscordPartyDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 52bf3eaafd945bf4cb950aedc0af5702 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordSecretsDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord.Editor 5 | { 6 | [CustomPropertyDrawer(typeof(Secrets))] 7 | internal sealed class DiscordSecretsDrawer : PropertyDrawer 8 | { 9 | public const float keySize = 150; 10 | public const int lines = 3; 11 | 12 | public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) 13 | { 14 | SerializedProperty joinsecret = prop.FindPropertyRelative("joinSecret"); 15 | SerializedProperty spectatesecret = prop.FindPropertyRelative("spectateSecret"); 16 | 17 | float h2 = pos.height / lines; 18 | EditorGUI.LabelField(new Rect(pos.x, pos.y, pos.width, EditorGUIUtility.singleLineHeight), label); 19 | 20 | int indent = EditorGUI.indentLevel++; 21 | { 22 | EditorGUI.PropertyField(new Rect(pos.x, pos.y + h2 * 1 - 4, pos.width, h2), joinsecret); 23 | EditorGUI.PropertyField(new Rect(pos.x, pos.y + h2 * 2 -2 , pos.width, h2), spectatesecret); 24 | 25 | } 26 | EditorGUI.indentLevel = indent; 27 | 28 | } 29 | 30 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 31 | { 32 | return (base.GetPropertyHeight(property, label) * lines) + 10; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Editor/DiscordSecretsDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f50e8e2c5c47e884099349ce03a26024 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordTimestampDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord.Editor 5 | { 6 | [CustomPropertyDrawer(typeof(Timestamp))] 7 | internal sealed class DiscordTimestampDrawer : PropertyDrawer 8 | { 9 | public const float buttonWidth = 50f; 10 | 11 | public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) 12 | { 13 | SerializedProperty timestamp = prop.FindPropertyRelative("timestamp"); 14 | 15 | // Draw curve 16 | EditorGUI.PropertyField(new Rect(pos.x, pos.y, pos.width - buttonWidth - 5f, EditorGUIUtility.singleLineHeight), timestamp, label); 17 | if (GUI.Button(new Rect(pos.x + pos.width - buttonWidth, pos.y, buttonWidth, pos.height), new GUIContent("Now", "Sets the time to the current time"))) 18 | { 19 | timestamp.longValue = new Timestamp(Time.time).timestamp; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Editor/DiscordTimestampDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8ba5f2568dd4fd943924a5aeeac41364 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/DiscordUserDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord.Editor 5 | { 6 | [CustomPropertyDrawer(typeof(User))] 7 | internal sealed class DiscordUserDrawer : PropertyDrawer 8 | { 9 | public const float keySize = 150; 10 | public const int lines = 10; 11 | 12 | public bool debugRectangles = false; 13 | 14 | public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) 15 | { 16 | int indent = EditorGUI.indentLevel; 17 | EditorGUI.indentLevel = 0; 18 | { 19 | SerializedProperty foldout = prop.FindPropertyRelative("e_foldout"); 20 | 21 | User user = prop.GetSerializedValue() as User; 22 | if (user == null) return; 23 | 24 | string displayName = user.ToString(); 25 | 26 | Rect imageRectangle = new Rect(16, 16, 108, 108); 27 | imageRectangle.position += pos.position; 28 | 29 | Rect usernameRectangle = new Rect(imageRectangle.xMax + 10, pos.y + 16, pos.width - (imageRectangle.width + 26), 16); 30 | Rect snowflakeRectange = usernameRectangle; snowflakeRectange.y += 16; 31 | 32 | Rect cacheSizeRectangle = snowflakeRectange; cacheSizeRectangle.y += 32; 33 | Rect avatarHashRectangle = cacheSizeRectangle; avatarHashRectangle.y += 16; 34 | 35 | //Draw a rect covering everything 36 | DrawRect(pos, Color.red); 37 | 38 | //Draw the label then the left over space it gave us 39 | if (foldout.boolValue = EditorGUI.Foldout(new Rect(pos.x, pos.y, pos.width, EditorGUIUtility.singleLineHeight), foldout.boolValue, label)) 40 | { 41 | DrawAvatar(imageRectangle, user.avatar); 42 | 43 | DrawRect(usernameRectangle, Color.green); 44 | DrawRect(snowflakeRectange, Color.white); 45 | 46 | DrawRect(cacheSizeRectangle, Color.blue); 47 | DrawRect(avatarHashRectangle, Color.cyan); 48 | 49 | EditorGUI.LabelField(usernameRectangle, new GUIContent(displayName)); 50 | 51 | if (user.ID != 0) 52 | EditorGUI.LabelField(snowflakeRectange, new GUIContent("(" + user.ID.ToString() + ")")); 53 | 54 | EditorGUI.LabelField(cacheSizeRectangle, new GUIContent($"{user.cacheSize} x {user.cacheSize}, {user.cacheFormat}")); 55 | EditorGUI.LabelField(avatarHashRectangle, new GUIContent(user.avatarHash)); 56 | } 57 | } 58 | EditorGUI.indentLevel = indent; 59 | } 60 | 61 | /// Draws the avatar box 62 | private void DrawAvatar(Rect position, Texture2D avatarProperty) 63 | { 64 | //Draw the backing colour 65 | EditorGUI.HelpBox(position, "", MessageType.None); 66 | 67 | //Draw the avatar if we have one 68 | if (avatarProperty != null) 69 | EditorGUI.DrawTextureTransparent(position, avatarProperty, ScaleMode.ScaleToFit); 70 | } 71 | 72 | private void DrawRect(Rect rect, Color color) 73 | { 74 | if (!debugRectangles) return; 75 | EditorGUI.DrawRect(rect, color); 76 | } 77 | 78 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 79 | { 80 | SerializedProperty foldout = property.FindPropertyRelative("e_foldout"); 81 | float baseHeight = base.GetPropertyHeight(property, label); 82 | 83 | if (!foldout.boolValue) return baseHeight; 84 | return baseHeight + 108 + 6; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Editor/DiscordUserDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bda8c967ea4725941beb86087f4c0f90 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Lachee.Discord.Editor 9 | { 10 | public static class SerializedPropertyExtensions 11 | { 12 | /// 13 | /// Gets the type of the underlying field the SerializedProperty is of. 14 | /// 15 | /// The property to get the type from 16 | /// 17 | public static System.Type GetSerializedType(this SerializedProperty property) 18 | { 19 | return property.GetSerializedFieldInfo()?.FieldType; 20 | } 21 | 22 | /// 23 | /// Gets the FieldInfo of the underlying field 24 | /// 25 | /// The property to get the FieldInfo off 26 | /// 27 | public static FieldInfo GetSerializedFieldInfo(this SerializedProperty property) 28 | { 29 | System.Type parentType = property.serializedObject.targetObject.GetType(); 30 | return parentType.GetFieldInfoFromPath(property.propertyPath); 31 | } 32 | 33 | /// 34 | /// Gets the underlying value this property represents 35 | /// 36 | /// 37 | /// 38 | public static object GetSerializedValue(this SerializedProperty property) 39 | { 40 | #if !DISABLE_FAST_SERIALIZED_VALUE_LOOKUP 41 | switch (property.propertyType) 42 | { 43 | default: // If we cant find anything, we should just use the raw .ToString of the value 44 | case SerializedPropertyType.Enum: // Its easier to just lookup the enum properties than recreating it 45 | break; 46 | 47 | // Manually get a bunch because its more efficient than looking up serialized values 48 | case SerializedPropertyType.ObjectReference: 49 | return property.objectReferenceValue ? property.objectReferenceValue : null; 50 | case SerializedPropertyType.Boolean: 51 | return property.boolValue; 52 | case SerializedPropertyType.Integer: 53 | return property.intValue; 54 | case SerializedPropertyType.Float: 55 | return property.floatValue; 56 | case SerializedPropertyType.String: 57 | return property.stringValue; 58 | case SerializedPropertyType.Color: 59 | return property.colorValue; 60 | case SerializedPropertyType.Vector2: 61 | return property.vector2Value; 62 | case SerializedPropertyType.Vector3: 63 | return property.vector3Value; 64 | case SerializedPropertyType.Vector4: 65 | return property.vector4Value; 66 | case SerializedPropertyType.Vector2Int: 67 | return property.vector2IntValue; 68 | case SerializedPropertyType.Vector3Int: 69 | return property.vector3IntValue; 70 | case SerializedPropertyType.Quaternion: 71 | return property.quaternionValue; 72 | case SerializedPropertyType.Bounds: 73 | return property.boundsValue; 74 | case SerializedPropertyType.BoundsInt: 75 | return property.boundsIntValue; 76 | case SerializedPropertyType.Rect: 77 | return property.rectValue; 78 | case SerializedPropertyType.RectInt: 79 | return property.rectIntValue; 80 | } 81 | #endif 82 | // Lookup the property path and pull teh value directly 83 | System.Type parentType = property.serializedObject.targetObject.GetType(); 84 | return parentType.GetValueFromPath(property.serializedObject.targetObject, property.propertyPath); 85 | } 86 | 87 | /// 88 | /// Finds the field info for the given type at the given path. 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// Does not work with arrays yet as they would return a PropertyInfo instead 94 | /// 95 | public static FieldInfo GetFieldInfoFromPath(this System.Type type, string path, BindingFlags flag = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField) 96 | { 97 | System.Type parentType = type; 98 | FieldInfo fi = type.GetField(path, flag); 99 | if (fi != null) return fi; 100 | 101 | string[] perDot = path.Split('.'); 102 | foreach (string fieldName in perDot) 103 | { 104 | fi = parentType.GetField(fieldName, flag); 105 | if (fi != null) 106 | parentType = fi.FieldType; 107 | else 108 | return null; 109 | } 110 | if (fi != null) 111 | return fi; 112 | else return null; 113 | } 114 | 115 | /// 116 | /// Gets the field values from the given path 117 | /// 118 | /// The type of the root object 119 | /// The root object to get the value from 120 | /// The SerializedProperty formatted path 121 | /// The flag used to search fields. 122 | /// 123 | public static object GetValueFromPath(this System.Type type, object context, string path, BindingFlags flag = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField) 124 | { 125 | object result = context; 126 | System.Type resultType = type; 127 | 128 | // We need to delve deeper until we hit the final result. 129 | string[] segments = path.Split('.'); 130 | for (int i = 0; i < segments.Length; i++) 131 | { 132 | // If the field name is an array we need to break apart the next segment to extract its index. 133 | // Once we have the index we can then use the `this` property arrays have to get the appropriate item and 134 | // continue our search through the list of paths. 135 | string fieldName = segments[i]; 136 | if (fieldName == "Array") 137 | { 138 | // parse the index 139 | string arrIndexPath = segments[++i]; 140 | string arrIndexStr = arrIndexPath.Substring(5, arrIndexPath.Length - 1 - 5); 141 | int arrIndex = int.Parse(arrIndexStr); 142 | 143 | // get the property 144 | var thisProperty = resultType.GetProperty("Item", new System.Type[] { arrIndex.GetType() }); 145 | var thisGetter = thisProperty.GetMethod; 146 | 147 | // Update the current state 148 | result = thisGetter.Invoke(result, new object[] { arrIndex }); 149 | resultType = result.GetType(); 150 | } 151 | else 152 | { 153 | var fi = resultType.GetField(fieldName, flag); 154 | if (fi == null) return null; 155 | 156 | resultType = fi.FieldType; 157 | result = fi.GetValue(result); 158 | } 159 | } 160 | 161 | return result; 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /Editor/Extensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: baf9bc97a5ec9ec4dab4b2d8ea32abb1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/NativeDllHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Lachee.Discord.Editor 9 | { 10 | [InitializeOnLoad] 11 | internal sealed class DiscordNativeInstall 12 | { 13 | const bool ENABLED = false; 14 | 15 | const string PLUGIN_PATH_86_64 = "Discord RPC/Plugins/x86_64"; 16 | const string PLUGIN_PATH_86 = "Discord RPC/Plugins/x86"; 17 | const string PLUGIN_NAME = "DiscordRPC.Native.dll"; 18 | 19 | static DiscordNativeInstall() 20 | { 21 | if (PlayerSettings.GetApiCompatibilityLevel(BuildTargetGroup.Standalone) == ApiCompatibilityLevel.NET_2_0_Subset) 22 | { 23 | var result = EditorUtility.DisplayDialog("Incompatible API Level", "You are currently using the .NET 2.0 Subset in this project. Discord RPC is incompatible with this version and requires the full version.\r\n\r\n" + 24 | "Failure to change to the full version of .NET 2.0 may break builds and hard crash the game.\r\n\r\n" + 25 | "Would you like to upgrade the project to .NET 2.0 now?", "Yes", "No"); 26 | 27 | if (result) 28 | { 29 | PlayerSettings.SetApiCompatibilityLevel(BuildTargetGroup.Standalone, ApiCompatibilityLevel.NET_2_0); 30 | Debug.Log("Converted project to .NET 2.0 successfully"); 31 | } 32 | else 33 | { 34 | Debug.LogError("Discord RPC is unable to work in a .NET 2.0 SUBSET enviroment. Builds may not work and may hardcrash if not fixed. Please manually fix by changing player settings."); 35 | } 36 | } 37 | 38 | #if !UNITY_2019_OR_NEWER 39 | FixLinkerSettings(); 40 | #endif 41 | } 42 | 43 | 44 | static void FixLinkerSettings() 45 | { 46 | string linkPath = "Packages/com.lachee.discordrpc/Runtime/DiscordRPC.xml"; 47 | string cscPath = "Packages/com.lachee.discordrpc/Runtime/csc.rsp"; 48 | bool needsRefresh = false; 49 | 50 | if (!File.Exists(linkPath)) { 51 | string linkContent = ""; 52 | File.WriteAllText(linkPath, linkContent); 53 | needsRefresh = true; 54 | } 55 | 56 | if (needsRefresh || !File.Exists(cscPath)) 57 | { 58 | string cscContent = $"/res:{linkPath}"; 59 | File.WriteAllText(cscPath, cscContent); 60 | needsRefresh = true; 61 | } 62 | 63 | if (needsRefresh) 64 | AssetDatabase.Refresh(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Editor/NativeDllHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 65bf06ff29d07db4f862043e34b2f868 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/com.lachee.discordrpc.editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.lachee.discordrpc.editor", 3 | "references": [ 4 | "com.lachee.discordrpc.runtime" 5 | ], 6 | "optionalUnityReferences": [], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false 12 | } -------------------------------------------------------------------------------- /Editor/com.lachee.discordrpc.editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 70fe66498821d1740869338aca9ead84 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lachee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1f91186609d13cd45a3b9a856ecd18c2 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 18 | 19 |
5 | 6 | 8 |

Discord RPC Unity

9 |

10 | 11 | GitHub package.json version 12 | 13 |
14 | This package provides a wrapper for lachee/discord-rpc-csharp and 15 | a better experience when intergrating with Unity3D, as well as solving some tricky annoyances such as named pipes and mono. 16 |

17 |
20 | 21 | # Usage 22 | Add the package to your project and look at the sample code. For more documentation about the RPC, check the 23 | [discord-rpc-csharp](https://github.com/lachee/discord-rpc-csharp) documentation 24 | 25 | Check out the documentation at [https://lachee.github.io/discord-rpc-unity/](https://lachee.github.io/discord-rpc-unity/) 26 | 27 | # Dependencies 28 | 29 | - At _least_ Unity 2018, however: 30 | - Support is **only given down to** [Unity 2018.4.36f1](https://unity3d.com/unity/qa/lts-releases?version=2018.4) LTS 31 | - Support is **only given up to** the latest LTS 32 | 33 | - Newtonsoft.JSON 13 34 | - This is provided by [com.unity.nuget.newtonsoft-json](https://docs.unity3d.com/Packages/com.unity.nuget.newtonsoft-json@3.0/manual/index.html) 35 | - _this determines the 2018.4 min spec. You can go lower, but you need to supply your own newtonsoft.json 13.0 binary._ 36 | 37 | 38 | # Installation 39 | #### OpenUPM 40 | The [openupm registry](https://openupm.com) is a open source package manager for Unity and provides the [openupm-cli](https://github.com/openupm/openupm-cli) to manage your dependencies. 41 | ``` 42 | openupm add com.lachee.discordrpc 43 | ``` 44 | 45 | #### Manual UPM GitHub package.json version 46 | Use the Unity Package Manager to add a git package. Adding the git to your UPM will limit updates as Unity will not track versioning on git projects (even though they totally could with tags). 47 | 1. Open the Unity Package Manager and `Add Package by git URL...` 48 | 2. `https://github.com/Lachee/discord-rpc-unity.git ` 49 | 50 | For local editable versions, manually clone the repo into your package folder. Note the exact spelling on destination name. 51 | 1. `git clone https://github.com/Lachee/discord-rpc-unity.git Packages/com.lachee.discordrpc` 52 | 53 | #### Unity Package 54 | Go old school and download the Unity Package and import it into your project. 55 | 1. Download the `.unitypackage` from the [Releases](releases) or via the last run `Create Release` action. 56 | 2. Import that package into your Unity3D 57 | 58 | # Logging 59 | 60 | By default, the DiscordManager will log to the Unity Console while in the Editor. 61 | To enable logging in builds, create a [Development Build](https://docs.unity3d.com/Manual/BuildSettings.html) and a new discordrpc.log file will be generated with your app when it runs. 62 | 63 | # Licensing 64 | 65 | The license is MIT so do what you want; 66 | 67 | However, i do appriciate attributations where possible and a link. Also if you plan to "fix" the library and sell it, please contribute back to this project with your fixes so others can benifit too. 68 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ed0c046e615465941bb83ddda6d4c83a 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6717e8f35d614e847ac2e1c8d65ac82c 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Resources/discord_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Resources/discord_logo.png -------------------------------------------------------------------------------- /Resources/discord_logo.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e82c8e5c734e424d827caf275b12cb9 3 | TextureImporter: 4 | fileIDToRecycleName: 5 | 2186277476908879412: ImportLogs 6 | externalObjects: {} 7 | serializedVersion: 5 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | sRGBTexture: 1 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapsPreserveCoverage: 0 16 | alphaTestReferenceValue: 0.5 17 | mipMapFadeDistanceStart: 1 18 | mipMapFadeDistanceEnd: 3 19 | bumpmap: 20 | convertToNormalMap: 0 21 | externalNormalMap: 0 22 | heightScale: 0.25 23 | normalMapFilter: 0 24 | isReadable: 0 25 | grayScaleToAlpha: 0 26 | generateCubemap: 6 27 | cubemapConvolution: 0 28 | seamlessCubemap: 0 29 | textureFormat: 1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | serializedVersion: 2 33 | filterMode: 2 34 | aniso: -1 35 | mipBias: -1 36 | wrapU: 1 37 | wrapV: 1 38 | wrapW: -1 39 | nPOTScale: 0 40 | lightmap: 0 41 | compressionQuality: 50 42 | spriteMode: 1 43 | spriteExtrude: 1 44 | spriteMeshType: 1 45 | alignment: 0 46 | spritePivot: {x: 0.5, y: 0.5} 47 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 48 | spritePixelsToUnits: 100 49 | alphaUsage: 1 50 | alphaIsTransparency: 1 51 | spriteTessellationDetail: -1 52 | textureType: 8 53 | textureShape: 1 54 | singleChannelComponent: 0 55 | maxTextureSizeSet: 0 56 | compressionQualitySet: 0 57 | textureFormatSet: 0 58 | platformSettings: 59 | - serializedVersion: 2 60 | buildTarget: DefaultTexturePlatform 61 | maxTextureSize: 2048 62 | resizeAlgorithm: 0 63 | textureFormat: -1 64 | textureCompression: 0 65 | compressionQuality: 50 66 | crunchedCompression: 0 67 | allowsAlphaSplitting: 0 68 | overridden: 0 69 | androidETC2FallbackOverride: 0 70 | - serializedVersion: 2 71 | buildTarget: Standalone 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 0 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | androidETC2FallbackOverride: 0 81 | spriteSheet: 82 | serializedVersion: 2 83 | sprites: [] 84 | outline: [] 85 | physicsShape: [] 86 | bones: [] 87 | spriteID: cf9d870bf415949439b4a4907ce72e6f 88 | vertices: [] 89 | indices: 90 | edges: [] 91 | weights: [] 92 | spritePackingTag: 93 | userData: 94 | assetBundleName: 95 | assetBundleVariant: 96 | -------------------------------------------------------------------------------- /Resources/discord_presence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Resources/discord_presence.png -------------------------------------------------------------------------------- /Resources/discord_presence.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 776a7bd6dde298048840a64474480461 3 | timeCreated: 1521361092 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 4 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | sRGBTexture: 0 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 6 25 | cubemapConvolution: 0 26 | seamlessCubemap: 0 27 | textureFormat: 1 28 | maxTextureSize: 2048 29 | textureSettings: 30 | filterMode: -1 31 | aniso: 1 32 | mipBias: -1 33 | wrapMode: 1 34 | nPOTScale: 0 35 | lightmap: 0 36 | compressionQuality: 50 37 | spriteMode: 0 38 | spriteExtrude: 1 39 | spriteMeshType: 1 40 | alignment: 0 41 | spritePivot: {x: 0.5, y: 0.5} 42 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 43 | spritePixelsToUnits: 100 44 | alphaUsage: 1 45 | alphaIsTransparency: 1 46 | spriteTessellationDetail: -1 47 | textureType: 2 48 | textureShape: 1 49 | maxTextureSizeSet: 0 50 | compressionQualitySet: 0 51 | textureFormatSet: 0 52 | platformSettings: 53 | - buildTarget: DefaultTexturePlatform 54 | maxTextureSize: 2048 55 | textureFormat: -1 56 | textureCompression: 1 57 | compressionQuality: 50 58 | crunchedCompression: 0 59 | allowsAlphaSplitting: 0 60 | overridden: 0 61 | - buildTarget: Standalone 62 | maxTextureSize: 2048 63 | textureFormat: -1 64 | textureCompression: 1 65 | compressionQuality: 50 66 | crunchedCompression: 0 67 | allowsAlphaSplitting: 0 68 | overridden: 0 69 | spriteSheet: 70 | serializedVersion: 2 71 | sprites: [] 72 | outline: [] 73 | spritePackingTag: 74 | userData: 75 | assetBundleName: 76 | assetBundleVariant: 77 | -------------------------------------------------------------------------------- /Resources/discord_wumpus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Resources/discord_wumpus.png -------------------------------------------------------------------------------- /Resources/discord_wumpus.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e8e47700789ac34c9e419fd91fb84ff 3 | TextureImporter: 4 | fileIDToRecycleName: 5 | 2186277476908879412: ImportLogs 6 | externalObjects: {} 7 | serializedVersion: 5 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 0 11 | sRGBTexture: 1 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapsPreserveCoverage: 0 16 | alphaTestReferenceValue: 0.5 17 | mipMapFadeDistanceStart: 1 18 | mipMapFadeDistanceEnd: 3 19 | bumpmap: 20 | convertToNormalMap: 0 21 | externalNormalMap: 0 22 | heightScale: 0.25 23 | normalMapFilter: 0 24 | isReadable: 0 25 | grayScaleToAlpha: 0 26 | generateCubemap: 6 27 | cubemapConvolution: 0 28 | seamlessCubemap: 0 29 | textureFormat: 1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | serializedVersion: 2 33 | filterMode: 2 34 | aniso: -1 35 | mipBias: -1 36 | wrapU: 1 37 | wrapV: 1 38 | wrapW: -1 39 | nPOTScale: 0 40 | lightmap: 0 41 | compressionQuality: 50 42 | spriteMode: 1 43 | spriteExtrude: 9 44 | spriteMeshType: 1 45 | alignment: 0 46 | spritePivot: {x: 0.5, y: 0.5} 47 | spritePixelsToUnits: 100 48 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 49 | spriteGenerateFallbackPhysicsShape: 1 50 | alphaUsage: 2 51 | alphaIsTransparency: 1 52 | spriteTessellationDetail: -1 53 | textureType: 8 54 | textureShape: 1 55 | singleChannelComponent: 0 56 | maxTextureSizeSet: 0 57 | compressionQualitySet: 0 58 | textureFormatSet: 0 59 | platformSettings: 60 | - serializedVersion: 2 61 | buildTarget: DefaultTexturePlatform 62 | maxTextureSize: 2048 63 | resizeAlgorithm: 1 64 | textureFormat: -1 65 | textureCompression: 0 66 | compressionQuality: 50 67 | crunchedCompression: 0 68 | allowsAlphaSplitting: 0 69 | overridden: 0 70 | androidETC2FallbackOverride: 0 71 | - serializedVersion: 2 72 | buildTarget: Standalone 73 | maxTextureSize: 2048 74 | resizeAlgorithm: 1 75 | textureFormat: -1 76 | textureCompression: 0 77 | compressionQuality: 50 78 | crunchedCompression: 0 79 | allowsAlphaSplitting: 0 80 | overridden: 0 81 | androidETC2FallbackOverride: 0 82 | spriteSheet: 83 | serializedVersion: 2 84 | sprites: [] 85 | outline: [] 86 | physicsShape: [] 87 | bones: [] 88 | spriteID: 9ad9a18e0fe29454293a7fa245ecd109 89 | vertices: [] 90 | indices: 91 | edges: [] 92 | weights: [] 93 | spritePackingTag: 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Resources/logo.png -------------------------------------------------------------------------------- /Resources/logo.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d5afae97af0a5c428d09c1e51cfa04c 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 1 38 | mipBias: 0 39 | wrapU: 0 40 | wrapV: 0 41 | wrapW: 0 42 | nPOTScale: 1 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 0 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 0 55 | spriteTessellationDetail: -1 56 | textureType: 0 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 0 66 | platformSettings: 67 | - serializedVersion: 3 68 | buildTarget: DefaultTexturePlatform 69 | maxTextureSize: 2048 70 | resizeAlgorithm: 0 71 | textureFormat: -1 72 | textureCompression: 1 73 | compressionQuality: 50 74 | crunchedCompression: 0 75 | allowsAlphaSplitting: 0 76 | overridden: 0 77 | androidETC2FallbackOverride: 0 78 | forceMaximumCompressionQuality_BC6H_BC7: 0 79 | spriteSheet: 80 | serializedVersion: 2 81 | sprites: [] 82 | outline: [] 83 | physicsShape: [] 84 | bones: [] 85 | spriteID: 86 | internalID: 0 87 | vertices: [] 88 | indices: 89 | edges: [] 90 | weights: [] 91 | secondaryTextures: [] 92 | nameFileIdTable: {} 93 | spritePackingTag: 94 | pSDRemoveMatte: 0 95 | pSDShowRemoveMatteOption: 0 96 | userData: 97 | assetBundleName: 98 | assetBundleVariant: 99 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2cb646afe6b9434d98cab3fc62ff4ec 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Attributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eee91a11d8111e542912260226300ee0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Attributes/CharacterLimitAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Lachee.Discord.Attributes 4 | { 5 | public class CharacterLimitAttribute : PropertyAttribute 6 | { 7 | public int max = 32; 8 | public bool enforce = false; 9 | 10 | public CharacterLimitAttribute(int max) 11 | { 12 | this.max = max; 13 | this.enforce = false; 14 | } 15 | 16 | public CharacterLimitAttribute(int max, bool enforce) 17 | { 18 | this.max = max; 19 | this.enforce = enforce; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Runtime/Attributes/CharacterLimitAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5817f51f41457e479f8fc1e43bf363f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Control.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e952ad0a6c46a6d4e98f7e3c2000c337 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Control/MessageEvents.cs: -------------------------------------------------------------------------------- 1 | using DiscordRPC; 2 | using DiscordRPC.Message; 3 | using System; 4 | using UnityEngine; 5 | using UnityEngine.Events; 6 | 7 | namespace Lachee.Discord.Control 8 | { 9 | [Serializable] 10 | public sealed class MessageEvents 11 | { 12 | [Serializable] 13 | public sealed class ReadyMessageEvent : UnityEvent { } 14 | 15 | [Serializable] 16 | public sealed class CloseMessageEvent : UnityEvent { } 17 | 18 | [Serializable] 19 | public sealed class ErrorMessageEvent : UnityEvent { } 20 | 21 | [Serializable] 22 | public sealed class PresenceMessageEvent : UnityEvent { } 23 | 24 | [Serializable] 25 | public sealed class SubscribeMessageEvent : UnityEvent { } 26 | 27 | [Serializable] 28 | public sealed class UnsubscribeMessageEvent : UnityEvent { } 29 | 30 | [Serializable] 31 | public sealed class JoinMessageEvent : UnityEvent { } 32 | 33 | [Serializable] 34 | public sealed class SpectateMessageEvent : UnityEvent { } 35 | 36 | [Serializable] 37 | public sealed class JoinRequestMessageEvent : UnityEvent { } 38 | 39 | [Serializable] 40 | public sealed class ConnectionEstablishedMessageEvent : UnityEvent { } 41 | 42 | [Serializable] 43 | public sealed class ConnectionFailedMessageEvent : UnityEvent { } 44 | 45 | public ReadyMessageEvent OnReady = new ReadyMessageEvent(); 46 | public CloseMessageEvent OnClose = new CloseMessageEvent(); 47 | public ErrorMessageEvent OnError = new ErrorMessageEvent(); 48 | public PresenceMessageEvent OnPresenceUpdate = new PresenceMessageEvent(); 49 | public SubscribeMessageEvent OnSubscribe = new SubscribeMessageEvent(); 50 | public UnsubscribeMessageEvent OnUnsubscribe = new UnsubscribeMessageEvent(); 51 | public JoinMessageEvent OnJoin = new JoinMessageEvent(); 52 | public SpectateMessageEvent OnSpectate = new SpectateMessageEvent(); 53 | public JoinRequestMessageEvent OnJoinRequest = new JoinRequestMessageEvent(); 54 | public ConnectionEstablishedMessageEvent OnConnectionEstablished = new ConnectionEstablishedMessageEvent(); 55 | public ConnectionFailedMessageEvent OnConnectionFailed = new ConnectionFailedMessageEvent(); 56 | 57 | public void RegisterEvents(DiscordRpcClient client) 58 | { 59 | client.OnReady += (s, args) => OnReady.Invoke(args); 60 | client.OnClose += (s, args) => OnClose.Invoke(args); 61 | client.OnError += (s, args) => OnError.Invoke(args); 62 | 63 | client.OnPresenceUpdate += (s, args) => OnPresenceUpdate.Invoke(args); 64 | client.OnSubscribe += (s, args) => OnSubscribe.Invoke(args); 65 | client.OnUnsubscribe += (s, args) => OnUnsubscribe.Invoke(args); 66 | 67 | client.OnJoin += (s, args) => OnJoin.Invoke(args); 68 | client.OnSpectate += (s, args) => OnSpectate.Invoke(args); 69 | client.OnJoinRequested += (s, args) => OnJoinRequest.Invoke(args); 70 | 71 | client.OnConnectionEstablished += (s, args) => OnConnectionEstablished.Invoke(args); 72 | client.OnConnectionFailed += (s, args) => OnConnectionFailed.Invoke(args); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Runtime/Control/MessageEvents.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a4ca9910408e96945acec366a9bbf00b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Control/UnityLogger.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using DiscordRPC.Logging; 3 | 4 | namespace Lachee.Discord.Control 5 | { 6 | /// 7 | /// This is a bridge between the Discord IPC logging and Unity Logging. Useful for debugging errors within the pipe. 8 | /// 9 | public sealed class UnityLogger : DiscordRPC.Logging.ILogger 10 | { 11 | public LogLevel Level { get; set; } 12 | 13 | public void Trace(string message, params object[] args) 14 | { 15 | if (Level > LogLevel.Trace) return; 16 | Debug.Log(" " + (args.Length > 0 ? string.Format(message, args) : message)); 17 | } 18 | 19 | public void Info(string message, params object[] args) 20 | { 21 | if (Level > LogLevel.Info) return; 22 | Debug.Log(" " + (args.Length > 0 ? string.Format(message, args) : message)); 23 | } 24 | 25 | public void Warning(string message, params object[] args) 26 | { 27 | if (Level > LogLevel.Warning) return; 28 | Debug.LogWarning(" " + (args.Length > 0 ? string.Format(message, args) : message)); 29 | } 30 | 31 | public void Error(string message, params object[] args) 32 | { 33 | if (Level > LogLevel.Error) return; 34 | Debug.LogError(" " + (args.Length > 0 ? string.Format(message, args) : message)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Runtime/Control/UnityLogger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 272944aa76fcab94795bac60822c3a13 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: 776a7bd6dde298048840a64474480461, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Control/UnityNamedPipe.cs: -------------------------------------------------------------------------------- 1 | #if (UNITY_WSA || UNITY_WSA_10_0 || UNITY_STANDALONE) && !DISABLE_DISCORD 2 | using DiscordRPC.IO; 3 | using DiscordRPC.Logging; 4 | using System; 5 | using Lachee.IO; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | using System.Threading; 9 | 10 | namespace Lachee.Discord.Control 11 | { 12 | /// 13 | /// Pipe Client used to communicate with Discord. 14 | /// 15 | public class UnityNamedPipe : INamedPipeClient 16 | { 17 | const string PIPE_NAME = @"discord-ipc-{0}"; 18 | 19 | private NamedPipeClientStream _stream; 20 | private byte[] _buffer = new byte[PipeFrame.MAX_SIZE]; 21 | 22 | public ILogger Logger { get; set; } 23 | public bool IsConnected { get { return _stream != null && _stream.IsConnected; } } 24 | public int ConnectedPipe { get; private set; } 25 | 26 | private volatile bool _isDisposed = false; 27 | 28 | public bool Connect(int pipe) 29 | { 30 | if (_isDisposed) 31 | throw new ObjectDisposedException("NamedPipe"); 32 | 33 | if (pipe > 9) 34 | throw new ArgumentOutOfRangeException("pipe", "Argument cannot be greater than 9"); 35 | 36 | if (pipe < 0) 37 | { 38 | //If we have -1, then we need to iterate over every single pipe until we get it 39 | //Iterate until we connect to a pipe 40 | for (int i = 0; i < 10; i++) 41 | { 42 | if (AttemptConnection(i) || AttemptConnection(i, true)) 43 | return true; 44 | } 45 | 46 | //We failed everythign else 47 | return false; 48 | } 49 | else 50 | { 51 | //We have a set one so we should just straight up try to connect to it 52 | return AttemptConnection(pipe) || AttemptConnection(pipe, true); 53 | } 54 | } 55 | 56 | private bool AttemptConnection(int pipe, bool doSandbox = false) 57 | { 58 | //Make sure the stream is null 59 | if (_stream != null) 60 | { 61 | Logger.Error("Attempted to create a new stream while one already exists!"); 62 | return false; 63 | } 64 | 65 | //Make sure we are disconnected 66 | if (IsConnected) 67 | { 68 | Logger.Error("Attempted to create a new connection while one already exists!"); 69 | return false; 70 | } 71 | 72 | try 73 | { 74 | //Prepare the sandbox 75 | string sandbox = doSandbox ? GetPipeSandbox() : ""; 76 | if (doSandbox && sandbox == null) 77 | { 78 | Logger.Trace("Skipping sandbox because this platform does not support it."); 79 | return false; 80 | } 81 | 82 | //Prepare the name 83 | string pipename = GetPipeName(pipe); 84 | 85 | //Attempt to connect 86 | Logger.Info("Connecting to " + pipename + " (" + sandbox +")"); 87 | ConnectedPipe = pipe; 88 | _stream = new NamedPipeClientStream(".", pipename); 89 | _stream.Connect(); 90 | 91 | Logger.Info("Connected"); 92 | return true; 93 | } 94 | catch(Exception e) 95 | { 96 | Logger.Error("Failed: " + e.GetType().FullName + ", " + e.Message); 97 | ConnectedPipe = -1; 98 | Close(); 99 | return false; 100 | } 101 | } 102 | 103 | public void Close() 104 | { 105 | if (_stream != null) 106 | { 107 | Logger.Trace("Closing stream"); 108 | _stream.Dispose(); 109 | _stream = null; 110 | } 111 | } 112 | 113 | public void Dispose() 114 | { 115 | if (_isDisposed) return; 116 | Logger.Trace("Disposing Stream"); 117 | _isDisposed = true; 118 | Close(); 119 | } 120 | 121 | public bool ReadFrame(out PipeFrame frame) 122 | { 123 | if (_isDisposed) 124 | throw new ObjectDisposedException("_stream"); 125 | 126 | //We are not connected so we cannot read! 127 | if (!IsConnected) 128 | { 129 | frame = default(PipeFrame); 130 | return false; 131 | } 132 | 133 | //Try and read a frame 134 | int length = _stream.Read(_buffer, 0, _buffer.Length); 135 | Logger.Trace("Read {0} bytes", length); 136 | 137 | if (length == 0) 138 | { 139 | frame = default(PipeFrame); 140 | return false; 141 | } 142 | 143 | //Read the stream now 144 | using (MemoryStream memory = new MemoryStream(_buffer, 0, length)) 145 | { 146 | frame = new PipeFrame(); 147 | if (!frame.ReadStream(memory)) 148 | { 149 | Logger.Error("Failed to read a frame! {0}", frame.Opcode); 150 | return false; 151 | } 152 | else 153 | { 154 | Logger.Trace("Read pipe frame!"); 155 | return true; 156 | } 157 | } 158 | } 159 | 160 | public bool WriteFrame(PipeFrame frame) 161 | { 162 | if (_isDisposed) 163 | throw new ObjectDisposedException("_stream"); 164 | 165 | //Write the frame. We are assuming proper duplex connection here 166 | if (!IsConnected) 167 | { 168 | Logger.Error("Failed to write frame because the stream is closed"); 169 | return false; 170 | } 171 | 172 | try 173 | { 174 | //Write the pipe 175 | //This can only happen on the main thread so it should be fine. 176 | Logger.Trace("Writing frame"); 177 | frame.WriteStream(_stream); 178 | return true; 179 | } 180 | catch (IOException io) 181 | { 182 | Logger.Error("Failed to write frame because of a IO Exception: {0}", io.Message); 183 | } 184 | catch (ObjectDisposedException) 185 | { 186 | Logger.Warning("Failed to write frame as the stream was already disposed"); 187 | } 188 | catch (InvalidOperationException) 189 | { 190 | Logger.Warning("Failed to write frame because of a invalid operation"); 191 | } 192 | 193 | //We must have failed the try catch 194 | return false; 195 | } 196 | 197 | private string GetPipeName(int pipe, string sandbox = "") 198 | { 199 | switch (Environment.OSVersion.Platform) 200 | { 201 | default: 202 | 203 | #if !UNITY_EDITOR_OSX 204 | case PlatformID.Win32NT: 205 | case PlatformID.Win32S: 206 | case PlatformID.Win32Windows: 207 | case PlatformID.WinCE: 208 | Logger.Trace("PIPE WIN"); 209 | return sandbox + string.Format(PIPE_NAME, pipe); 210 | #endif 211 | 212 | case PlatformID.Unix: 213 | case PlatformID.MacOSX: 214 | Logger.Trace("PIPE UNIX / MACOSX"); 215 | return Path.Combine(GetEnviromentTemp(), sandbox + string.Format(PIPE_NAME, pipe)); 216 | } 217 | } 218 | 219 | private string GetPipeSandbox() 220 | { 221 | switch (Environment.OSVersion.Platform) 222 | { 223 | default: 224 | return null; 225 | case PlatformID.Unix: 226 | return "snap.discord/"; 227 | } 228 | } 229 | 230 | private string GetEnviromentTemp() 231 | { 232 | string temp = null; 233 | temp = temp ?? Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); 234 | temp = temp ?? Environment.GetEnvironmentVariable("TMPDIR"); 235 | temp = temp ?? Environment.GetEnvironmentVariable("TMP"); 236 | temp = temp ?? Environment.GetEnvironmentVariable("TEMP"); 237 | temp = temp ?? "/tmp"; 238 | return temp; 239 | } 240 | } 241 | } 242 | #endif -------------------------------------------------------------------------------- /Runtime/Control/UnityNamedPipe.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e34a1d8c83cf20b49b86cd17cf315175 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/DiscordManager.cs: -------------------------------------------------------------------------------- 1 | using DiscordRPC.Message; 2 | using Lachee.Discord.Events; 3 | using System.Collections; 4 | using UnityEngine; 5 | using UnityEngine.Events; 6 | 7 | namespace Lachee.Discord 8 | { 9 | /// 10 | /// A wrapper for the Discord Sharp Client, providing useful utilities in a Unity-Friendly form. 11 | /// 12 | [ExecuteInEditMode] 13 | public sealed class DiscordManager : MonoBehaviour 14 | { 15 | public const string EXAMPLE_APPLICATION = "424087019149328395"; 16 | 17 | /// 18 | /// The current instance of the Discord Manager 19 | /// 20 | public static DiscordManager current { get { return _instance; } } 21 | private static DiscordManager _instance; 22 | 23 | #region Properties and Configurations 24 | [Header("Properties")] 25 | [Tooltip("The ID of the Discord Application. Visit the Discord API to create a new application if nessary.")] 26 | public string applicationID = EXAMPLE_APPLICATION; 27 | 28 | [Tooltip("The Steam App ID. This is a optional field used to launch your game through steam instead of the executable.")] 29 | public string steamID = ""; 30 | 31 | [Tooltip("The pipe discord is located on. Useful for testing multiple clients.")] 32 | public Pipe targetPipe = Pipe.FirstAvailable; 33 | 34 | /// 35 | /// All possible pipes discord can be found on. 36 | /// 37 | public enum Pipe 38 | { 39 | FirstAvailable = -1, 40 | Pipe0 = 0, 41 | Pipe1 = 1, 42 | Pipe2 = 2, 43 | Pipe3 = 3, 44 | Pipe4 = 4, 45 | Pipe5 = 5, 46 | Pipe6 = 6, 47 | Pipe7 = 7, 48 | Pipe8 = 8, 49 | Pipe9 = 9 50 | } 51 | 52 | [Tooltip("Logging level of the Discord IPC connection.")] 53 | public DiscordRPC.Logging.LogLevel logLevel = DiscordRPC.Logging.LogLevel.Warning; 54 | [Tooltip("The file to write the logs too in a build. If empty, then the console logger will be used.")] 55 | public string logFile = "discord.log"; 56 | 57 | [Tooltip("Registers a custom URI scheme for your game. This is required for the Join / Specate features to work.")] 58 | public bool registerUriScheme = false; 59 | 60 | [SerializeField] 61 | [Tooltip("The enabled state of the IPC connection")] 62 | private bool active = true; 63 | 64 | /// 65 | /// The current Discord user. This does not get set until the first Ready event. 66 | /// 67 | public User CurrentUser { get { return _currentUser; } } 68 | [Tooltip("The current Discord user. This does not get set until the first Ready event.")] 69 | 70 | [Header("State")] 71 | [SerializeField] private User _currentUser; 72 | 73 | /// 74 | /// The current event subscription flag. 75 | /// 76 | public Event CurrentSubscription { get { return _currentSubscription; } } 77 | [Tooltip("The current subscription flag")] 78 | [SerializeField] private Event _currentSubscription = Event.None; 79 | 80 | /// 81 | /// The current presence displayed on the Discord Client. 82 | /// 83 | public Presence CurrentPresence { get { return _currentPresence; } } 84 | [Tooltip("The current Rich Presence displayed on the Discord Client.")] 85 | [SerializeField] private Presence _currentPresence; 86 | 87 | #endregion 88 | 89 | [Header("Handlers and Events")] 90 | #if UNITY_2019_OR_NEWER 91 | public UnityEvent OnReady; 92 | public UnityEvent OnClose; 93 | public UnityEvent OnPresence; 94 | public UnityEvent OnJoin; 95 | [HideInInspector] public UnityEvent OnSubscribe; 96 | [HideInInspector] public UnityEvent OnUnsubscribe; 97 | [HideInInspector] public UnityEvent OnError; 98 | [HideInInspector] public UnityEvent OnSpectate; 99 | [HideInInspector] public UnityEvent OnJoinRequest; 100 | [HideInInspector] public UnityEvent OnConnectionEstablished; 101 | [HideInInspector] public UnityEvent OnConnectionFailed; 102 | #else 103 | #pragma warning disable CS0618 // Type or member is obsolete 104 | public UnityReadyEvent OnReady; 105 | public UnityCloseEvent OnClose; 106 | public UnityPresenceEvent OnPresence; 107 | public UnityJoinEvent OnJoin; 108 | [HideInInspector] public UnitySubscribeEvent OnSubscribe; 109 | [HideInInspector] public UnityUnsubscribeEvent OnUnsubscribe; 110 | [HideInInspector] public UnityErrorEvent OnError; 111 | [HideInInspector] public UnitySpectateEvent OnSpectate; 112 | [HideInInspector] public UnityJoinRequestEvent OnJoinRequest; 113 | [HideInInspector] public UnityConnectionEstablishedEvent OnConnectionEstablished; 114 | [HideInInspector] public UnityConnectionFailedEvent OnConnectionFailed; 115 | #pragma warning restore CS0618 // Type or member is obsolete 116 | #endif 117 | 118 | /// 119 | /// The current Discord Client. 120 | /// 121 | public DiscordRPC.DiscordRpcClient client { get { return _client; } } 122 | private DiscordRPC.DiscordRpcClient _client = null; 123 | 124 | public bool isInitialized { get { return _client != null && _client.IsInitialized; } } 125 | 126 | #region Unity Events 127 | 128 | private void OnDisable() { Deinitialize(); } //Try to dispose the client when we are disabled 129 | private void OnDestroy() { Deinitialize(); } 130 | 131 | #if (UNITY_WSA || UNITY_WSA_10_0 || UNITY_STANDALONE) && !DISABLE_DISCORD 132 | 133 | private void OnEnable() 134 | { //Try to initialize the client when we are enabled. 135 | if (gameObject.activeSelf && !isInitialized) 136 | Initialize(); 137 | } 138 | 139 | private void Awake() 140 | { 141 | SetupSingleton(); 142 | } 143 | 144 | //Try to initialize the client when we start. This is useful for moments where we are spawned in 145 | private void Start() 146 | { 147 | if (!isInitialized) 148 | { 149 | if (_client != null) 150 | { 151 | Debug.LogWarning("Client already exists! Disposing Early."); 152 | Deinitialize(); 153 | } 154 | 155 | Initialize(); 156 | } 157 | } 158 | 159 | private void FixedUpdate() 160 | { 161 | if (client == null) return; 162 | 163 | //Update the client log level 164 | client.Logger.Level = logLevel; 165 | 166 | //Invoke the client events 167 | client.Invoke(); 168 | } 169 | 170 | #endif 171 | 172 | #if UNITY_EDITOR 173 | [UnityEditor.MenuItem("GameObject/Discord Manager", priority = 10)] 174 | private static void CreateNewManager() 175 | { 176 | var prev = FindObjectOfType(); 177 | if (prev == null) 178 | { 179 | var go = new GameObject("Discord Manager"); 180 | prev = go.AddComponent(); 181 | } 182 | else 183 | { 184 | Debug.LogWarning("Cannot create new Discord Manager because one already exists."); 185 | } 186 | 187 | UnityEditor.Selection.activeObject = prev; 188 | } 189 | #endif 190 | 191 | #endregion 192 | 193 | private void SetupSingleton() 194 | { 195 | //This has a instance already that isn't us 196 | if (_instance != null && _instance != this) 197 | { 198 | Debug.LogWarning("[DAPI] Multiple DiscordManagers exist already. Destroying self.", _instance); 199 | Destroy(this); 200 | return; 201 | } 202 | 203 | //Make sure the client doesnt already exit 204 | if (_client != null) 205 | { 206 | Debug.LogError("[DAPI] Cannot initialize a new client when one is already initialized."); 207 | return; 208 | } 209 | 210 | //Assign the instance 211 | _instance = this; 212 | 213 | if (Application.isPlaying) 214 | DontDestroyOnLoad(this); 215 | } 216 | 217 | /// 218 | /// Initializes the discord client if able to. Wont initialize if is false, we are not in playmode, we already have a instance or we already have a client. 219 | /// This function is empty unless UNITY_WSA || UNITY_WSA_10_0 || UNITY_STANDALONE) && !DISABLE_DISCORD is meet. 220 | /// 221 | public void Initialize() 222 | { 223 | #if (UNITY_WSA || UNITY_WSA_10_0 || UNITY_STANDALONE) && !DISABLE_DISCORD 224 | 225 | if (!active) return; //Are we allowed to be active? 226 | if (!Application.isPlaying) return; //We are not allowed to initialize while in the editor. 227 | 228 | SetupSingleton(); 229 | 230 | //Prepare the logger 231 | DiscordRPC.Logging.ILogger logger = null; 232 | 233 | //Update the logger to the unity logger 234 | if (Application.isEditor) 235 | { 236 | logger = new Control.UnityLogger() { Level = logLevel }; 237 | } 238 | else 239 | { 240 | logger = new DiscordRPC.Logging.FileLogger(logFile) { Level = logLevel }; 241 | } 242 | 243 | //We are starting the client. Below is a break down of the parameters. 244 | Debug.Log("[DRP] Starting Discord Rich Presence"); 245 | _client = new DiscordRPC.DiscordRpcClient( 246 | applicationID, //The Discord Application ID 247 | pipe: (int)targetPipe, //The target pipe to connect too 248 | logger: logger, //The logger, 249 | autoEvents: false, //WE will manually invoke events 250 | client: new Control.UnityNamedPipe() //The client for the pipe to use. Unity MUST use a NativeNamedPipeClient since its managed client is broken. 251 | ); 252 | 253 | if (registerUriScheme) 254 | client.RegisterUriScheme(steamID); 255 | 256 | //Subscribe to some initial events 257 | #region Event Registration 258 | client.OnError += (s, args) => Debug.LogError("[DRP] Error Occured within the Discord IPC: (" + args.Code + ") " + args.Message); 259 | client.OnJoinRequested += (s, args) => Debug.Log("[DRP] Join Requested"); 260 | 261 | client.OnReady += (s, args) => 262 | { 263 | //We have connected to the Discord IPC. We should send our rich presence just incase it lost it. 264 | Debug.Log("[DRP] Connection established and received READY from Discord IPC. Sending our previous Rich Presence and Subscription."); 265 | 266 | //Set the user and cache their avatars 267 | _currentUser = args.User; 268 | _currentUser.GetAvatar(DiscordAvatarSize.x128); 269 | }; 270 | client.OnPresenceUpdate += (s, args) => 271 | { 272 | Debug.Log("[DRP] Our Rich Presence has been updated. Applied changes to local store."); 273 | Debug.Log(args.Presence.State); 274 | _currentPresence = (Presence)args.Presence; 275 | }; 276 | client.OnSubscribe += (s, a) => 277 | { 278 | Debug.Log("[DRP] New Subscription. Updating local store."); 279 | _currentSubscription = client.Subscription.ToUnity(); 280 | }; 281 | client.OnUnsubscribe += (s, a) => 282 | { 283 | Debug.Log("[DRP] Removed Subscription. Updating local store."); 284 | _currentSubscription = client.Subscription.ToUnity(); 285 | }; 286 | 287 | //Register the unity events 288 | client.OnReady += (s, args) => OnReady?.Invoke(new ReadyEvent(args)); 289 | client.OnClose += (s, args) => OnClose?.Invoke(args); 290 | client.OnError += (s, args) => OnError?.Invoke(args); 291 | 292 | client.OnPresenceUpdate += (s, args) => OnPresence?.Invoke(new PresenceEvent(args)); 293 | client.OnSubscribe += (s, args) => OnSubscribe?.Invoke(args); 294 | client.OnUnsubscribe += (s, args) => OnUnsubscribe?.Invoke(args); 295 | 296 | client.OnJoin += (s, args) => OnJoin?.Invoke(args); 297 | client.OnSpectate += (s, args) => OnSpectate?.Invoke(args); 298 | client.OnJoinRequested += (s, args) => OnJoinRequest?.Invoke(new JoinRequestEvent(args)); 299 | 300 | client.OnConnectionEstablished += (s, args) => OnConnectionEstablished.Invoke(args); 301 | client.OnConnectionFailed += (s, args) => OnConnectionFailed.Invoke(args); 302 | #endregion 303 | 304 | //Set initial presence and sub. (This will enqueue it) 305 | SetSubscription(_currentSubscription); 306 | SetPresence(_currentPresence); 307 | 308 | //Start the client 309 | _client.Initialize(); 310 | Debug.Log("[DRP] Discord Rich Presence intialized and connecting..."); 311 | 312 | #endif 313 | } 314 | 315 | /// 316 | /// If not already disposed, it will dispose and deinitialize the discord client. 317 | /// 318 | public void Deinitialize() 319 | { 320 | //We dispose outside the scripting symbols as we always want to be able to dispose (just in case). 321 | if (_client != null) 322 | { 323 | Debug.Log("[DRP] Disposing Discord IPC Client..."); 324 | _client.Dispose(); 325 | _client = null; 326 | Debug.Log("[DRP] Finished Disconnecting"); 327 | } 328 | } 329 | 330 | /// 331 | /// Sets the Rich Presence of the Discord Client through the pipe connection. 332 | /// This will log a error if the client is null or not yet initiated. 333 | /// 334 | /// The Rich Presence to be shown to the client 335 | public void SetPresence(Presence presence) 336 | { 337 | if (client == null) 338 | { 339 | Debug.LogError("[DRP] Attempted to send a presence update but no client exists!"); 340 | return; 341 | } 342 | 343 | if (!client.IsInitialized) 344 | { 345 | //Debug.LogWarning("[DRP] Attempted to send a presence update to a client that is not initialized! The messages will be enqueued instead!"); 346 | } 347 | 348 | //Just do some validation 349 | if (!presence.secrets.IsEmpty() && _currentSubscription == Event.None) 350 | { 351 | Debug.LogWarning("[DRP] Sending a secret, however we are not actually subscribed to any events. This will cause the messages to be ignored!"); 352 | } 353 | 354 | //Set the presence 355 | _currentPresence = presence; 356 | client.SetPresence(presence != null ? presence.ToRichPresence() : null); 357 | } 358 | 359 | /// 360 | /// Resends the current Rich Presence to the Discord Client via the pipe connectoin. 361 | /// 362 | [ContextMenu("Resend Presence")] 363 | public void ResetPresence() 364 | { 365 | SetPresence(_currentPresence); 366 | } 367 | 368 | /// 369 | /// Sets the subscription flag, unsubscribing and then subscribing to the nessary events. Used for Join / Spectate feature. If you have not registered your application, this feature is unavailable. 370 | /// This will log a error if the client is null or not yet initiated. 371 | /// 372 | /// The events to subscribe too 373 | public void SetSubscription(Event evt) 374 | { 375 | if (client == null) 376 | { 377 | Debug.LogError("[DRP] Attempted to send a presence update but no client exists!"); 378 | return; 379 | } 380 | 381 | //if (!client.IsInitialized) 382 | //{ 383 | // Debug.LogError("[DRP] Attempted to send a presence update to a client that is not initialized!"); 384 | // return; 385 | //} 386 | 387 | this._currentSubscription = evt; 388 | client.SetSubscription(evt.ToDiscordRPC()); 389 | } 390 | 391 | #region Single Components Sets 392 | public Presence UpdateDetails(string details) 393 | { 394 | if (_client == null) return null; 395 | return (Presence)_client.UpdateDetails(details); 396 | } 397 | 398 | public Presence UpdateState(string state) 399 | { 400 | if (_client == null) return null; 401 | return (Presence)_client.UpdateState(state); 402 | } 403 | 404 | public Presence UpdateParty(Party party) 405 | { 406 | if (_client == null) return null; 407 | if (party == null) return (Presence)_client.UpdateParty(null); 408 | return (Presence)_client.UpdateParty(party.ToRichParty()); 409 | } 410 | public Presence UpdatePartySize(int size, int max) 411 | { 412 | if (_client == null) return null; 413 | return (Presence)_client.UpdatePartySize(size, max); 414 | } 415 | 416 | public Presence UpdateLargeAsset(Asset asset) 417 | { 418 | if (_client == null) return null; 419 | if (asset == null) return (Presence)_client.UpdateLargeAsset("", ""); 420 | return (Presence)_client.UpdateLargeAsset(asset.image, asset.tooltip); 421 | } 422 | public Presence UpdateSmallAsset(Asset asset) 423 | { 424 | if (_client == null) return null; 425 | if (asset == null) return (Presence)_client.UpdateSmallAsset("", ""); 426 | return (Presence)_client.UpdateSmallAsset(asset.image, asset.tooltip); 427 | } 428 | 429 | public Presence UpdateSecrets(Secrets secrets) 430 | { 431 | if (_client == null) return null; 432 | return (Presence)_client.UpdateSecrets(secrets.ToRichSecrets()); 433 | } 434 | 435 | public Presence UpdateStartTime() 436 | { 437 | if (_client == null) return null; 438 | return (Presence)_client.UpdateStartTime(); 439 | } 440 | public Presence UpdateStartTime(Timestamp timestamp) 441 | { 442 | if (_client == null) return null; 443 | return (Presence)_client.UpdateStartTime(timestamp.GetDateTime()); 444 | } 445 | public Presence UpdateEndTime() 446 | { 447 | if (_client == null) return null; 448 | return (Presence)_client.UpdateEndTime(); 449 | } 450 | public Presence UpdateEndTime(Timestamp timestamp) 451 | { 452 | if (_client == null) return null; 453 | return (Presence)_client.UpdateEndTime(timestamp.GetDateTime()); 454 | } 455 | public Presence UpdateClearTime(Timestamp timestamp) 456 | { 457 | if (_client == null) return null; 458 | return (Presence)_client.UpdateClearTime(); 459 | } 460 | #endregion 461 | 462 | 463 | /// 464 | /// Resonds to a Join Request. 465 | /// 466 | /// The request being responded too 467 | /// The result of the request. True to accept the request. 468 | public void Respond(JoinRequestMessage request, bool acceptRequest) 469 | { 470 | if (client == null) 471 | { 472 | Debug.LogError("[DRP] Attempted to send a presence update but no client exists!"); 473 | return; 474 | } 475 | 476 | if (!client.IsInitialized) 477 | { 478 | Debug.LogError("[DRP] Attempted to send a presence update to a client that is not initialized!"); 479 | return; 480 | } 481 | 482 | 483 | client.Respond(request, acceptRequest); 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /Runtime/DiscordManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b1c0662f7bb58e7439856b9fa8414d6c 3 | timeCreated: 1521360783 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {fileID: 2800000, guid: 0e82c8e5c734e424d827caf275b12cb9, type: 3} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Runtime/Events.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92f7f8d3f3cb974489194c82ff3d7ac4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Events/Events.cs: -------------------------------------------------------------------------------- 1 | using DiscordRPC; 2 | using DiscordRPC.Message; 3 | using System; 4 | using UnityEngine; 5 | using UnityEngine.Events; 6 | 7 | namespace Lachee.Discord.Events 8 | { 9 | public sealed class ReadyEvent 10 | { 11 | public User user { get; } 12 | public Configuration configuration { get; } 13 | public int version { get; } 14 | 15 | internal ReadyEvent(ReadyMessage message) 16 | { 17 | user = new User(message.User); 18 | configuration = message.Configuration; 19 | version = message.Version; 20 | } 21 | } 22 | 23 | public sealed class PresenceEvent 24 | { 25 | public Presence presence { get; } 26 | public string name { get; } 27 | public string applicationID { get; } 28 | internal PresenceEvent(PresenceMessage message) 29 | { 30 | presence = new Presence(message.Presence); 31 | name = message.Name; 32 | applicationID = message.ApplicationID; 33 | } 34 | } 35 | 36 | public sealed class JoinRequestEvent 37 | { 38 | public User user { get; } 39 | internal JoinRequestEvent(JoinRequestMessage message) 40 | { 41 | user = new User(message.User); 42 | } 43 | } 44 | 45 | 46 | // By Unity 2020, you shouldn't be using these anymore 47 | #if !UNITY_2020_OR_NEWER 48 | [Serializable] 49 | [System.Obsolete("This is a wrapper for Unity 2018")] 50 | public class UnityReadyEvent : UnityEvent { } 51 | 52 | [Serializable] 53 | [System.Obsolete("This is a wrapper for Unity 2018")] 54 | public class UnityCloseEvent : UnityEvent { } 55 | 56 | [Serializable] 57 | [System.Obsolete("This is a wrapper for Unity 2018")] 58 | public class UnityErrorEvent : UnityEvent { } 59 | 60 | [Serializable] 61 | [System.Obsolete("This is a wrapper for Unity 2018")] 62 | public class UnityPresenceEvent : UnityEvent { } 63 | 64 | [Serializable] 65 | [System.Obsolete("This is a wrapper for Unity 2018")] 66 | public class UnitySubscribeEvent : UnityEvent { } 67 | 68 | [Serializable] 69 | [System.Obsolete("This is a wrapper for Unity 2018")] 70 | public class UnityUnsubscribeEvent : UnityEvent { } 71 | 72 | [Serializable] 73 | [System.Obsolete("This is a wrapper for Unity 2018")] 74 | public class UnityJoinEvent : UnityEvent { } 75 | 76 | [Serializable] 77 | [System.Obsolete("This is a wrapper for Unity 2018")] 78 | public class UnitySpectateEvent : UnityEvent { } 79 | 80 | [Serializable] 81 | [System.Obsolete("This is a wrapper for Unity 2018")] 82 | public class UnityJoinRequestEvent : UnityEvent { } 83 | 84 | [Serializable] 85 | [System.Obsolete("This is a wrapper for Unity 2018")] 86 | public class UnityConnectionEstablishedEvent : UnityEvent { } 87 | 88 | [Serializable][System.Obsolete("This is a wrapper for Unity 2018")] 89 | public class UnityConnectionFailedEvent : UnityEvent { } 90 | #endif 91 | } -------------------------------------------------------------------------------- /Runtime/Events/Events.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eac5fab1ecfe2484797c1b1f260aae76 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Link.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Runtime/Link.xml.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a228e4a5dcb1b448877e9e5692c4425 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b1aef380728eae1459ca601833d41f4e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5b69ff6be5f109f41bd912d42a8db1d3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba58dc8e6978d674ebef739c0260b54b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeConnectionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Lachee.IO.Exceptions 4 | { 5 | public class NamedPipeConnectionException : Exception 6 | { 7 | internal NamedPipeConnectionException(string message) : base(message) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeConnectionException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f1d001c44226e5d4f98cddad90dbf94e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeOpenException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Lachee.IO.Exceptions 4 | { 5 | public class NamedPipeOpenException : Exception 6 | { 7 | public int ErrorCode { get; private set; } 8 | internal NamedPipeOpenException(int err) : base("An exception has occured while trying to open the pipe. Error Code: " + err) 9 | { 10 | ErrorCode = err; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeOpenException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb6c26b3e8a8d614d8184df301dee1e6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeReadException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Lachee.IO.Exceptions 4 | { 5 | public class NamedPipeReadException : Exception 6 | { 7 | public int ErrorCode { get; private set; } 8 | internal NamedPipeReadException(int err) : base("An exception occured while reading from the pipe. Error Code: " + err) 9 | { 10 | ErrorCode = err; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeReadException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 51431e2170eb34441b9d558a6d90a29c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeWriteException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Lachee.IO.Exceptions 4 | { 5 | public class NamedPipeWriteException : Exception 6 | { 7 | public int ErrorCode { get; private set; } 8 | internal NamedPipeWriteException(int err) : base("An exception occured while reading from the pipe. Error Code: " + err) 9 | { 10 | ErrorCode = err; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/Exceptions/NamedPipeWriteException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 32a12cea90d19f041ac631369ba6816f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/NamedPipeClientStream.cs: -------------------------------------------------------------------------------- 1 | using Lachee.IO.Exceptions; 2 | using System; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Lachee.IO 7 | { 8 | public class NamedPipeClientStream : System.IO.Stream 9 | { 10 | private IntPtr ptr; 11 | private bool _isDisposed; 12 | 13 | /// 14 | /// Can the stream read? Always returns true. 15 | /// 16 | public override bool CanRead { get { return true; } } 17 | 18 | /// 19 | /// Can the stream seek? Always returns false. 20 | /// 21 | public override bool CanSeek { get { return false; } } 22 | 23 | /// 24 | /// Can the stream write? Always returns true. 25 | /// 26 | public override bool CanWrite { get { return true; } } 27 | 28 | /// 29 | /// The length of the stream. Always 0. 30 | /// 31 | public override long Length { get { return 0; } } 32 | 33 | /// 34 | /// The current position of the stream. Always 0. 35 | /// 36 | public override long Position { get { return 0; } set { } } 37 | 38 | /// 39 | /// Checks if the current pipe is connected and running. 40 | /// 41 | public bool IsConnected { get { return Native.IsConnected(ptr); } } 42 | 43 | /// 44 | /// The pipe name for this client. 45 | /// 46 | public string PipeName { get; private set; } 47 | 48 | #region Constructors 49 | /// 50 | /// Creates a new instance of a NamedPipeClient 51 | /// 52 | /// The remote to connect too 53 | /// The name of the pipe that will be connected too. 54 | public NamedPipeClientStream(string server, string pipeName) 55 | { 56 | ptr = Native.CreateClient(); 57 | PipeName = FormatPipe(server, pipeName); 58 | Console.WriteLine("Created new NamedPipeClientStream '{0}' => '{1}'", pipeName, PipeName); 59 | } 60 | 61 | ~NamedPipeClientStream() 62 | { 63 | Dispose(false); 64 | } 65 | 66 | protected override void Dispose(bool disposing) 67 | { 68 | base.Dispose(disposing); 69 | if (!_isDisposed) 70 | { 71 | Disconnect(); 72 | Native.DestroyClient(ptr); 73 | _isDisposed = true; 74 | } 75 | } 76 | 77 | private static string FormatPipe(string server, string pipeName) 78 | { 79 | switch (Environment.OSVersion.Platform) 80 | { 81 | default: 82 | case PlatformID.Win32NT: 83 | return string.Format(@"\\{0}\pipe\{1}", server, pipeName); 84 | 85 | case PlatformID.Unix: 86 | if (server != ".") 87 | throw new PlatformNotSupportedException("Remote pipes are not supported on this platform."); 88 | return pipeName; 89 | } 90 | } 91 | 92 | #endregion 93 | 94 | #region Open Close 95 | /// 96 | /// Attempts to open a named pipe. 97 | /// 98 | /// The name of the pipe 99 | public void Connect() 100 | { 101 | int code = Native.Open(ptr, PipeName); 102 | if (!IsConnected) throw new NamedPipeOpenException(code); 103 | 104 | } 105 | 106 | /// 107 | /// Closes the named pipe already opened. 108 | /// 109 | public void Disconnect() 110 | { 111 | Native.Close(ptr); 112 | } 113 | #endregion 114 | 115 | /// 116 | /// Reads a block of bytes from a stream and writes the data to a specified buffer. Will not block if there is no data available to read. 117 | /// 118 | /// When this method returns, contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. 119 | /// The byte offset in the buffer array at which the bytes that are read will be placed. 120 | /// The maximum number of bytes to read. 121 | /// The total number of bytes that are read into buffer. This might be less than the number of bytes requested if that number of bytes is not currently available. If the value is less than 0, then a error has occured. 122 | public override int Read(byte[] buffer, int offset, int count) 123 | { 124 | //Make sure we are connected 125 | if (!IsConnected) 126 | throw new NamedPipeConnectionException("Cannot read stream as pipe is not connected"); 127 | 128 | if (offset + count > buffer.Length) 129 | throw new ArgumentOutOfRangeException("count", "Cannot read as the count exceeds the buffer size"); 130 | 131 | //Prepare bytes read and a buffer of memory to read into 132 | int bytesRead = 0; 133 | int size = Marshal.SizeOf(buffer[0]) * count; 134 | IntPtr buffptr = Marshal.AllocHGlobal(size); 135 | 136 | try 137 | { 138 | //Read the bytes 139 | bytesRead = Native.ReadFrame(ptr, buffptr, count); 140 | if (bytesRead <= 0) 141 | { 142 | //A error actively occured. If it is 0 we just read no bytes. 143 | if (bytesRead < 0) 144 | { 145 | //We have a pretty bad error, we will log it for prosperity. 146 | throw new NamedPipeReadException(bytesRead); 147 | } 148 | 149 | //Return a empty frame and return false (read failure). 150 | return 0; 151 | } 152 | else 153 | { 154 | //WE have read a valid amount of bytes, so copy the marshaled bytes over to the buffer 155 | Marshal.Copy(buffptr, buffer, offset, bytesRead); 156 | return bytesRead; 157 | } 158 | } 159 | finally 160 | { 161 | //Finally, before we exit this try block, free the pointer we allocated. 162 | Marshal.FreeHGlobal(buffptr); 163 | } 164 | } 165 | 166 | /// 167 | /// Writes a block of bytes to the current stream using data from a buffer 168 | /// 169 | /// The buffer that contains data to write to the pipe. 170 | /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. 171 | /// The maximum number of bytes to write to the current stream. 172 | public override void Write(byte[] buffer, int offset, int count) 173 | { 174 | //Make sure we are connected 175 | if (!IsConnected) 176 | throw new NamedPipeConnectionException("Cannot write stream as pipe is not connected"); 177 | 178 | //Copy the bytes into a new marshaled block 179 | int size = Marshal.SizeOf(buffer[0]) * count; 180 | IntPtr buffptr = Marshal.AllocHGlobal(size); 181 | 182 | try 183 | { 184 | //Copy the block of memory over 185 | Marshal.Copy(buffer, offset, buffptr, count); 186 | 187 | //Send the block 188 | int result = Native.WriteFrame(ptr, buffptr, count); 189 | if (result < 0) throw new NamedPipeWriteException(result); 190 | } 191 | finally 192 | { 193 | //Finally, before exiting the try catch, free the memory we assigned. 194 | Marshal.FreeHGlobal(buffptr); 195 | } 196 | } 197 | 198 | #region unsupported 199 | 200 | /// 201 | /// Flushes the stream. Not supported by NamedPipeClient. 202 | /// 203 | public override void Flush() 204 | { 205 | throw new NotSupportedException(); 206 | } 207 | 208 | /// 209 | /// Seeks to the given posisiton. Not supported by NamedPipeClient 210 | /// 211 | /// 212 | /// 213 | /// 214 | public override long Seek(long offset, SeekOrigin origin) 215 | { 216 | throw new NotSupportedException(); 217 | } 218 | 219 | /// 220 | /// Sets the length of the stream. Not supported by NamedPipeClient 221 | /// 222 | /// 223 | public override void SetLength(long value) 224 | { 225 | throw new NotSupportedException(); 226 | } 227 | 228 | #endregion 229 | 230 | private static class Native 231 | { 232 | const string LIBRARY_NAME = "NativeNamedPipe"; 233 | 234 | #region Creation and Destruction 235 | [DllImport(LIBRARY_NAME, EntryPoint = "createClient", CallingConvention = CallingConvention.Cdecl, ExactSpelling = false)] 236 | public static extern IntPtr CreateClient(); 237 | 238 | [DllImport(LIBRARY_NAME, EntryPoint = "destroyClient", CallingConvention = CallingConvention.Cdecl)] 239 | public static extern void DestroyClient(IntPtr client); 240 | #endregion 241 | 242 | #region State Control 243 | 244 | [DllImport(LIBRARY_NAME, EntryPoint = "isConnected", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 245 | public static extern bool IsConnected([MarshalAs(UnmanagedType.SysInt)] IntPtr client); 246 | 247 | [DllImport(LIBRARY_NAME, EntryPoint = "open", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 248 | public static extern int Open(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string pipename); 249 | 250 | [DllImport(LIBRARY_NAME, EntryPoint = "close", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] 251 | public static extern void Close(IntPtr client); 252 | 253 | #endregion 254 | 255 | #region IO 256 | 257 | [DllImport(LIBRARY_NAME, EntryPoint = "readFrame", CallingConvention = CallingConvention.Cdecl)] 258 | public static extern int ReadFrame(IntPtr client, IntPtr buffer, int length); 259 | 260 | [DllImport(LIBRARY_NAME, EntryPoint = "writeFrame", CallingConvention = CallingConvention.Cdecl)] 261 | public static extern int WriteFrame(IntPtr client, IntPtr buffer, int length); 262 | 263 | #endregion 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/IO/NamedPipeClientStream.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 41be55c1bc70ab3488e70590dca4653b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e7cf9ef4dddb0143adf5c8b62721b7a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/Plugins/x86_64.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2aea989833f9064cbbccef4bf9bc108 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/Plugins/x86_64/NativeNamedPipe.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Runtime/NamedPipeClient/Plugins/x86_64/NativeNamedPipe.dll -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/Plugins/x86_64/NativeNamedPipe.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c997a7f967dc5854e894fcb413191317 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | isPreloaded: 0 9 | isOverridable: 0 10 | platformData: 11 | - first: 12 | '': Any 13 | second: 14 | enabled: 0 15 | settings: 16 | Exclude Editor: 0 17 | Exclude Linux: 0 18 | Exclude Linux64: 0 19 | Exclude LinuxUniversal: 0 20 | Exclude OSXUniversal: 0 21 | Exclude Win: 1 22 | Exclude Win64: 0 23 | - first: 24 | '': OSXIntel 25 | second: 26 | enabled: 0 27 | settings: 28 | CPU: None 29 | - first: 30 | '': OSXIntel64 31 | second: 32 | enabled: 1 33 | settings: 34 | CPU: AnyCPU 35 | - first: 36 | Any: 37 | second: 38 | enabled: 1 39 | settings: {} 40 | - first: 41 | Editor: Editor 42 | second: 43 | enabled: 1 44 | settings: 45 | CPU: x86_64 46 | DefaultValueInitialized: true 47 | OS: Windows 48 | - first: 49 | Facebook: Win 50 | second: 51 | enabled: 0 52 | settings: 53 | CPU: None 54 | - first: 55 | Facebook: Win64 56 | second: 57 | enabled: 1 58 | settings: 59 | CPU: AnyCPU 60 | - first: 61 | Standalone: Linux 62 | second: 63 | enabled: 1 64 | settings: 65 | CPU: None 66 | - first: 67 | Standalone: Linux64 68 | second: 69 | enabled: 1 70 | settings: 71 | CPU: x86_64 72 | - first: 73 | Standalone: LinuxUniversal 74 | second: 75 | enabled: 1 76 | settings: 77 | CPU: AnyCPU 78 | - first: 79 | Standalone: OSXUniversal 80 | second: 81 | enabled: 1 82 | settings: 83 | CPU: x86_64 84 | - first: 85 | Standalone: Win 86 | second: 87 | enabled: 0 88 | settings: 89 | CPU: None 90 | - first: 91 | Standalone: Win64 92 | second: 93 | enabled: 1 94 | settings: 95 | CPU: AnyCPU 96 | userData: 97 | assetBundleName: 98 | assetBundleVariant: 99 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/Plugins/x86_64/NativeNamedPipe.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Runtime/NamedPipeClient/Plugins/x86_64/NativeNamedPipe.so -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/Plugins/x86_64/NativeNamedPipe.so.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: afdd54d5193832a46a03889bcfbd790c 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | isPreloaded: 0 9 | isOverridable: 0 10 | platformData: 11 | - first: 12 | '': Any 13 | second: 14 | enabled: 0 15 | settings: 16 | Exclude Editor: 0 17 | Exclude Linux: 1 18 | Exclude Linux64: 0 19 | Exclude LinuxUniversal: 0 20 | Exclude OSXUniversal: 0 21 | Exclude Win: 0 22 | Exclude Win64: 0 23 | - first: 24 | '': OSXIntel 25 | second: 26 | enabled: 0 27 | settings: 28 | CPU: None 29 | - first: 30 | '': OSXIntel64 31 | second: 32 | enabled: 1 33 | settings: 34 | CPU: AnyCPU 35 | - first: 36 | Any: 37 | second: 38 | enabled: 1 39 | settings: {} 40 | - first: 41 | Editor: Editor 42 | second: 43 | enabled: 1 44 | settings: 45 | CPU: x86_64 46 | DefaultValueInitialized: true 47 | OS: AnyOS 48 | - first: 49 | Facebook: Win 50 | second: 51 | enabled: 0 52 | settings: 53 | CPU: None 54 | - first: 55 | Facebook: Win64 56 | second: 57 | enabled: 1 58 | settings: 59 | CPU: AnyCPU 60 | - first: 61 | Standalone: Linux 62 | second: 63 | enabled: 0 64 | settings: 65 | CPU: None 66 | - first: 67 | Standalone: Linux64 68 | second: 69 | enabled: 1 70 | settings: 71 | CPU: x86_64 72 | - first: 73 | Standalone: LinuxUniversal 74 | second: 75 | enabled: 1 76 | settings: 77 | CPU: x86_64 78 | - first: 79 | Standalone: OSXUniversal 80 | second: 81 | enabled: 1 82 | settings: 83 | CPU: x86_64 84 | - first: 85 | Standalone: Win 86 | second: 87 | enabled: 1 88 | settings: 89 | CPU: None 90 | - first: 91 | Standalone: Win64 92 | second: 93 | enabled: 1 94 | settings: 95 | CPU: AnyCPU 96 | userData: 97 | assetBundleName: 98 | assetBundleVariant: 99 | -------------------------------------------------------------------------------- /Runtime/NamedPipeClient/ReadMe.rtf.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95ea1e8a74bdfd1429d606aa37c3cad8 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20f1344c265ee8e41b65471ea362cfd3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Plugins/DiscordRPC.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Runtime/Plugins/DiscordRPC.dll -------------------------------------------------------------------------------- /Runtime/Plugins/DiscordRPC.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95439ed501e6ee64b88375393b71fe09 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 1 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | Any: 16 | second: 17 | enabled: 1 18 | settings: {} 19 | - first: 20 | Editor: Editor 21 | second: 22 | enabled: 0 23 | settings: 24 | DefaultValueInitialized: true 25 | - first: 26 | Windows Store Apps: WindowsStoreApps 27 | second: 28 | enabled: 0 29 | settings: 30 | CPU: AnyCPU 31 | userData: 32 | assetBundleName: 33 | assetBundleVariant: 34 | -------------------------------------------------------------------------------- /Runtime/Plugins/DiscordRPC.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lachee/discord-rpc-unity/7498674d0192bb8bb68f417c13b57884b50144e3/Runtime/Plugins/DiscordRPC.pdb -------------------------------------------------------------------------------- /Runtime/Plugins/DiscordRPC.pdb.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d1c3fadb7f94457448ba636689abb6ea 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Plugins/DiscordRPC.xml.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 40060c82d7c647f439872eb6ed484fbb 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Presence.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82a89a20c498d824d970515bcf229cec 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Presence/Asset.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord.Attributes; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord 5 | { 6 | [System.Obsolete("The word Discord has been removed from types", true)] 7 | public sealed class DiscordAsset { } 8 | 9 | [System.Serializable] 10 | public sealed class Asset 11 | { 12 | /// 13 | /// The key of the image to be displayed. 14 | /// Max 32 Bytes. 15 | /// 16 | [CharacterLimit(256, enforce = true)] 17 | [Tooltip("The key or URL of the image to be displayed in the large square.")] 18 | public string image; 19 | 20 | /// 21 | /// The tooltip of the image. 22 | /// Max 128 Bytes. 23 | /// 24 | [CharacterLimit(128, enforce = true)] 25 | [Tooltip("The tooltip of the image.")] 26 | public string tooltip; 27 | 28 | [Tooltip("Snowflake ID of the image.")] 29 | public ulong snowflake; 30 | 31 | /// 32 | /// Is the asset object empty? 33 | /// 34 | /// 35 | public bool IsEmpty() 36 | { 37 | return string.IsNullOrEmpty(image) && string.IsNullOrEmpty(tooltip); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Runtime/Presence/Asset.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0323152162a0a9c43a683c36ac6086cf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/Button.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Lachee.Discord 4 | { 5 | [System.Obsolete("The word Discord has been removed from types", true)] 6 | public sealed class DiscordButton { } 7 | 8 | [System.Serializable] 9 | public sealed class Button 10 | { 11 | /// 12 | /// The text on the button to be displayed 13 | /// 14 | [Tooltip("The label on the button to be displayed")] 15 | public string label; 16 | 17 | /// 18 | /// The tooltip of the image. 19 | /// ] 20 | [Tooltip("The URL on the button to be displayed")] 21 | public string url; 22 | 23 | /// 24 | /// Is the asset object empty? 25 | /// 26 | /// 27 | public bool IsEmpty() 28 | { 29 | return string.IsNullOrEmpty(label) && string.IsNullOrEmpty(url); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Runtime/Presence/Button.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 180aa9f983e1fde4986427b689918038 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/Event.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Lachee.Discord 4 | { 5 | /// 6 | /// Events to receive from Discord 7 | /// 8 | [System.Flags] 9 | public enum Event 10 | { 11 | /// 12 | /// No Events 13 | /// 14 | None = 0, 15 | 16 | /// 17 | /// Listen to Spectate Events 18 | /// 19 | Spectate = 1, 20 | 21 | /// 22 | /// Listen to Join Events 23 | /// 24 | Join = 2, 25 | 26 | /// 27 | /// Listen for Join Requests 28 | /// 29 | JoinRequest = 4 30 | } 31 | 32 | public static class EventExtension 33 | { 34 | public static DiscordRPC.EventType ToDiscordRPC(this Event ev) 35 | { 36 | return (DiscordRPC.EventType)((int)ev); 37 | } 38 | 39 | public static Event ToUnity(this DiscordRPC.EventType type) 40 | { 41 | return (Event)((int)type); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Runtime/Presence/Event.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 089832a7aac7b8f43bb9192a403658c0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/Party.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord.Attributes; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord 5 | { 6 | [System.Obsolete("The word Discord has been removed from types", true)] 7 | public sealed class DiscordParty { } 8 | 9 | [System.Serializable] 10 | public sealed class Party 11 | { 12 | /// 13 | /// A unique ID for the player's current party / lobby / group. If this is not supplied, they player will not be in a party and the rest of the information will not be sent. 14 | /// Max 128 Bytes 15 | /// 16 | [CharacterLimit(128)] 17 | [Tooltip("The unique ID of the party. Leave empty for no party.")] 18 | public string identifer; 19 | 20 | /// 21 | /// The size of the party. 22 | /// 23 | [Tooltip("The current size of the party.")] 24 | public int size; 25 | 26 | /// 27 | /// The max size of the party. Cannot be smaller than size. 28 | /// 29 | [Tooltip("The max size of the party. Cannot be smaller than size.")] 30 | public int maxSize; 31 | 32 | /// 33 | /// Creates a new instance of the party 34 | /// 35 | /// ID of the party 36 | /// Size of the party 37 | /// Max Size of the party 38 | public Party(string id, int size, int max) 39 | { 40 | this.identifer = id; 41 | this.size = size; 42 | this.maxSize = max; 43 | } 44 | 45 | /// 46 | /// Creates a new empty instance of the party 47 | /// 48 | public Party() { } 49 | 50 | /// 51 | /// Returns true if the party is not valid and has no ID. 52 | /// 53 | /// 54 | public bool IsEmpty() 55 | { 56 | return size <= 0 || maxSize <= 0 || string.IsNullOrEmpty(identifer); 57 | } 58 | 59 | /// 60 | /// Creates new instances of the party, using the as the base. 61 | /// 62 | /// The base to use the values from 63 | public Party(DiscordRPC.Party party) 64 | { 65 | this.identifer = party.ID; 66 | this.size = party.Size; 67 | this.maxSize = party.Max; 68 | } 69 | 70 | /// 71 | /// Converts this object into the DiscordRPC equivilent. 72 | /// 73 | /// 74 | public DiscordRPC.Party ToRichParty() 75 | { 76 | //We are not a valid party 77 | if (string.IsNullOrEmpty(identifer)) 78 | return null; 79 | 80 | //return the party 81 | return new DiscordRPC.Party() 82 | { 83 | ID = identifer, 84 | Max = maxSize, 85 | Size = size 86 | }; 87 | } 88 | 89 | /// 90 | /// Generates a random party identifier 91 | /// 92 | /// 93 | public static string GenerateRandomIdentifer() 94 | { 95 | const string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 96 | const int chunkSize = 16; 97 | 98 | var builder = new System.Text.StringBuilder(); 99 | for (int i = 0; i < 128; i++) 100 | { 101 | if (i > 0 && i % chunkSize == 0) 102 | { 103 | builder.Append("-"); 104 | } 105 | else 106 | { 107 | char c = valid[Random.Range(0, valid.Length)]; 108 | builder.Append(c); 109 | } 110 | } 111 | 112 | return builder.ToString(); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /Runtime/Presence/Party.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fad1ad43cdf18904fa5a85e3bf3447fe 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/Presence.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord.Attributes; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord 5 | { 6 | [System.Obsolete("The word Discord has been removed from types", true)] 7 | public sealed class DiscordPresence { } 8 | 9 | [System.Serializable] 10 | public sealed class Presence 11 | { 12 | [Header("Basic Details")] 13 | 14 | /// 15 | /// The details about the game. Appears underneath the game name 16 | /// 17 | [CharacterLimit(128)] 18 | [Tooltip("The details about the game")] 19 | public string details = "Playing a game"; 20 | 21 | /// 22 | /// The current state of the game (In Game, In Menu etc). Appears next to the party size 23 | /// 24 | [CharacterLimit(128)] 25 | [Tooltip("The current state of the game (In Game, In Menu). It appears next to the party size.")] 26 | public string state = "In Game"; 27 | 28 | [Header("Time Details")] 29 | 30 | /// 31 | /// The time the game started. 0 if the game hasn't started 32 | /// 33 | [Tooltip("The time the game started. Leave as 0 if the game has not yet started.")] 34 | public Timestamp startTime = 0; 35 | 36 | /// 37 | /// The time the game will end in. 0 to ignore endtime. 38 | /// 39 | [Tooltip("Time the game will end. Leave as 0 to ignore it.")] 40 | public Timestamp endTime = 0; 41 | 42 | [Header("Presentation Details")] 43 | 44 | /// 45 | /// The images used for the presence. 46 | /// 47 | [Tooltip("The images used for the presence")] 48 | public Asset smallAsset; 49 | public Asset largeAsset; 50 | 51 | [Header("Button Details")] 52 | 53 | /// 54 | /// The buttons used for the presence. 55 | /// 56 | [Tooltip("The buttons used for the presence")] 57 | public Button[] buttons; 58 | 59 | [Header("Party Details")] 60 | 61 | /// 62 | /// The current party 63 | /// 64 | [Tooltip("The current party. Identifier must not be empty")] 65 | public Party party = new Party("", 0, 0); 66 | 67 | /// 68 | /// The current secrets for the join / spectate feature. 69 | /// 70 | [Tooltip("The current secrets for the join / spectate feature.")] 71 | public Secrets secrets = new Secrets(); 72 | 73 | /// 74 | /// Creates a new Presence object 75 | /// 76 | public Presence() { } 77 | 78 | /// 79 | /// Creats a new Presence object, copying values of the Rich Presence 80 | /// 81 | /// The rich presence, often received by discord. 82 | public Presence(DiscordRPC.BaseRichPresence presence) 83 | { 84 | if (presence != null) 85 | { 86 | this.state = presence.State; 87 | this.details = presence.Details; 88 | 89 | this.party = presence.HasParty() ? new Party(presence.Party) : new Party(); 90 | this.secrets = presence.HasSecrets() ? new Secrets(presence.Secrets) : new Secrets(); 91 | 92 | if (presence.HasAssets()) 93 | { 94 | this.smallAsset = new Asset() 95 | { 96 | image = presence.Assets.SmallImageKey, 97 | tooltip = presence.Assets.SmallImageText, 98 | snowflake = presence.Assets.SmallImageID.GetValueOrDefault(0) 99 | }; 100 | 101 | 102 | this.largeAsset = new Asset() 103 | { 104 | image = presence.Assets.LargeImageKey, 105 | tooltip = presence.Assets.LargeImageText, 106 | snowflake = presence.Assets.LargeImageID.GetValueOrDefault(0) 107 | }; 108 | } 109 | else 110 | { 111 | this.smallAsset = new Asset(); 112 | this.largeAsset = new Asset(); 113 | } 114 | 115 | if (presence.HasTimestamps()) 116 | { 117 | //This could probably be made simpler 118 | this.startTime = presence.Timestamps.Start.HasValue ? new Timestamp((long)presence.Timestamps.StartUnixMilliseconds.Value) : Timestamp.Invalid; 119 | this.endTime = presence.Timestamps.End.HasValue ? new Timestamp((long)presence.Timestamps.EndUnixMilliseconds.Value) : Timestamp.Invalid; 120 | } 121 | } 122 | else 123 | { 124 | this.state = ""; 125 | this.details = ""; 126 | this.party = new Party(); 127 | this.secrets = new Secrets(); 128 | this.smallAsset = new Asset(); 129 | this.largeAsset = new Asset(); 130 | this.startTime = Timestamp.Invalid; 131 | this.endTime = Timestamp.Invalid; 132 | } 133 | 134 | this.buttons = new Button[0]; 135 | } 136 | 137 | public Presence(DiscordRPC.RichPresence presence) 138 | : this((DiscordRPC.BaseRichPresence)presence) 139 | { 140 | if (presence != null) 141 | { 142 | if (presence.HasButtons()) 143 | { 144 | this.buttons = new Button[presence.Buttons.Length]; 145 | 146 | for (int i = 0; i < presence.Buttons.Length; i++) 147 | { 148 | this.buttons[i] = new Button() 149 | { 150 | label = presence.Buttons[i].Label, 151 | url = presence.Buttons[i].Url 152 | }; 153 | } 154 | } 155 | else 156 | { 157 | this.buttons = new Button[0]; 158 | } 159 | } 160 | } 161 | 162 | /// 163 | /// Converts this object into a new instance of a rich presence, ready to be sent to the discord client. 164 | /// 165 | /// A new instance of a rich presence, ready to be sent to the discord client. 166 | public DiscordRPC.RichPresence ToRichPresence() 167 | { 168 | var presence = new DiscordRPC.RichPresence(); 169 | presence.State = this.state; 170 | presence.Details = this.details; 171 | 172 | presence.Party = !this.party.IsEmpty() ? this.party.ToRichParty() : null; 173 | presence.Secrets = !this.secrets.IsEmpty() ? this.secrets.ToRichSecrets() : null; 174 | 175 | if ((smallAsset != null && !smallAsset.IsEmpty()) || (largeAsset != null && !largeAsset.IsEmpty())) 176 | { 177 | presence.Assets = new DiscordRPC.Assets() 178 | { 179 | SmallImageKey = smallAsset.image, 180 | SmallImageText = smallAsset.tooltip, 181 | 182 | LargeImageKey = largeAsset.image, 183 | LargeImageText = largeAsset.tooltip 184 | }; 185 | } 186 | 187 | if (startTime.IsValid() || endTime.IsValid()) 188 | { 189 | presence.Timestamps = new DiscordRPC.Timestamps(); 190 | if (startTime.IsValid()) presence.Timestamps.Start = startTime.GetDateTime(); 191 | if (endTime.IsValid()) presence.Timestamps.End = endTime.GetDateTime(); 192 | } 193 | 194 | if (buttons.Length > 0) 195 | { 196 | presence.Buttons = new DiscordRPC.Button[buttons.Length]; 197 | 198 | for (int i = 0; i < buttons.Length; i++) 199 | { 200 | presence.Buttons[i] = new DiscordRPC.Button 201 | { 202 | Label = buttons[i].label, 203 | Url = buttons[i].url 204 | }; 205 | } 206 | } 207 | 208 | return presence; 209 | } 210 | 211 | public static explicit operator DiscordRPC.RichPresence(Presence presence) 212 | { 213 | return presence.ToRichPresence(); 214 | } 215 | 216 | public static explicit operator Presence(DiscordRPC.RichPresence presence) 217 | { 218 | return new Presence(presence); 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /Runtime/Presence/Presence.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: af7f245f8d5b833478fd90246d8d7168 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/Secrets.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord.Attributes; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord 5 | { 6 | [System.Obsolete("The word Discord has been removed from types", true)] 7 | public struct DiscordSecrets { } 8 | 9 | [System.Serializable] 10 | public struct Secrets 11 | { 12 | /// 13 | /// The secret data that will tell the client how to connect to the game to play. This could be a unique identifier for a fancy match maker or player id, lobby id, etc. 14 | /// It is recommended to encrypt this information so its hard for people to replicate it. 15 | /// Do NOT just use the IP address in this. That is a bad practice and can leave your players vulnerable! 16 | /// 17 | /// Max Length of 128 Bytes 18 | /// 19 | [CharacterLimit(128)] 20 | [Tooltip("The secret data that will tell the client how to connect to the game to play. This could be a unique identifier for a fancy match maker or player id, lobby id, etc.")] 21 | public string joinSecret; 22 | 23 | 24 | /// 25 | /// The secret data that will tell the client how to connect to the game to spectate. This could be a unique identifier for a fancy match maker or player id, lobby id, etc. 26 | /// It is recommended to encrypt this information so its hard for people to replicate it. 27 | /// Do NOT just use the IP address in this. That is a bad practice and can leave your players vulnerable! 28 | /// 29 | /// Max Length of 128 Bytes 30 | /// 31 | [CharacterLimit(128)] 32 | [Tooltip("The secret data that will tell the client how to connect to the game to spectate. This could be a unique identifier for a fancy match maker or player id, lobby id, etc.")] 33 | public string spectateSecret; 34 | 35 | /// 36 | /// Creates new instances of the secrets, using the as the base. 37 | /// 38 | /// The base to use the values from 39 | public Secrets(DiscordRPC.Secrets secrets) 40 | { 41 | this.joinSecret = secrets.JoinSecret; 42 | this.spectateSecret = secrets.SpectateSecret; 43 | } 44 | 45 | 46 | /// 47 | /// Is the secret object empty? 48 | /// 49 | /// 50 | public bool IsEmpty() 51 | { 52 | return string.IsNullOrEmpty(joinSecret) && string.IsNullOrEmpty(spectateSecret); 53 | } 54 | 55 | /// 56 | /// Converts this object into the DiscordRPC equivilent. 57 | /// 58 | /// 59 | public DiscordRPC.Secrets ToRichSecrets() 60 | { 61 | if (IsEmpty()) return null; 62 | return new DiscordRPC.Secrets() 63 | { 64 | JoinSecret = joinSecret, 65 | SpectateSecret = spectateSecret 66 | }; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Runtime/Presence/Secrets.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fbd2a77a49969eb4b8328cd569b4ff3f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/Timestamp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Lachee.Discord 5 | { 6 | [System.Obsolete("The word Discord has been removed from types", true)] 7 | public sealed class DiscordTimestamp { } 8 | 9 | /// 10 | /// A special time class that can convert all manners of time into timestamps. 11 | /// 12 | [System.Serializable] 13 | public sealed class Timestamp 14 | { 15 | /// 16 | /// Representation of a invalid timestamp (unix epoch of 0 seconds). 17 | /// 18 | public static readonly Timestamp Invalid = new Timestamp(0L); 19 | 20 | /// 21 | /// The linux epoch of the timestamp. Use conversion methods such as to convert the time into unity relative times. 22 | /// This is used for implicit casting into a 23 | /// 24 | [Tooltip("Unix Epoch Timestamp")] 25 | public long timestamp = 0; 26 | 27 | /// 28 | /// Creates a new stamp of the current time. 29 | /// 30 | public Timestamp() 31 | : this(DateTime.UtcNow) { } 32 | 33 | /// 34 | /// Creates a new stamp with the supplied datetime 35 | /// 36 | /// The DateTime 37 | public Timestamp(DateTime time) 38 | : this(ToUnixMilliseconds(DateTime.UtcNow)) { } 39 | 40 | /// 41 | /// Creates a new stamp with the specified unix epoch 42 | /// 43 | /// The time in unix epoch milliseconds 44 | public Timestamp(long timestamp) 45 | { 46 | this.timestamp = timestamp; 47 | } 48 | 49 | /// 50 | /// Creates a new stamp that is relative to the Unity Startup time, where "now" is equal too . 51 | /// 52 | /// The time relative to 53 | public Timestamp(float time) 54 | { 55 | //Calculate the difference 56 | float diff = time - UnityEngine.Time.realtimeSinceStartup; 57 | 58 | //Convert to timespan and then to unix epoch 59 | TimeSpan timespan = TimeSpan.FromSeconds(diff); //Convert the difference to a TimeSpan for easier maths 60 | timestamp = ToUnixMilliseconds(DateTime.UtcNow + timespan); //Add the difference to the current time 61 | } 62 | 63 | /// 64 | /// Converts the timestamp into a 65 | /// This is used for implicit conversion into a 66 | /// 67 | /// 68 | public DateTime GetDateTime() 69 | { 70 | return FromUnixMilliseconds(timestamp); 71 | } 72 | 73 | /// 74 | /// Converts the timestamp into the number of seconds since the startup of the game (Unity relative), where "now" is equal too . 75 | /// This is used for implicit convertion into a . 76 | /// 77 | /// 78 | public float GetTime() 79 | { 80 | DateTime time = GetDateTime(); 81 | TimeSpan timespan = time - DateTime.UtcNow; 82 | return UnityEngine.Time.realtimeSinceStartup + (float)timespan.TotalSeconds; 83 | } 84 | 85 | /// 86 | /// Checks if the timestamp is valid (above 0 seconds relative to unix epoch). 87 | /// 88 | /// Returns true if the timestamp is a non-zero epoch. 89 | public bool IsValid() { return this.timestamp > 0; } 90 | 91 | /// 92 | /// Adds seconds onto the timestamp. 93 | /// 94 | /// The number of seconds to add 95 | /// Returns the same timestamp object. 96 | public Timestamp AddSeconds(int seconds) 97 | { 98 | timestamp += seconds; 99 | return this; 100 | } 101 | 102 | /// 103 | /// Adds minutes onto the timestamp. 104 | /// 105 | /// The number of minutes to add 106 | /// Returns the same timestamp object. 107 | public Timestamp AddMinutes(int minutes) 108 | { 109 | return AddSeconds(minutes * 60); 110 | } 111 | 112 | /// 113 | /// Adds minutes onto the timestamp, rounding off to the nearest second. 114 | /// 115 | /// The fraction of minutes to add 116 | /// Returns the same timestamp object. 117 | public Timestamp AddMinutes(float minutes) 118 | { 119 | //Convert the time into a integer form 120 | int mins = Mathf.FloorToInt(minutes); 121 | int secs = Mathf.RoundToInt((minutes - mins) * 60); 122 | 123 | return AddMinutes(mins).AddSeconds(secs); 124 | } 125 | 126 | /// 127 | /// Adds hours onto the timestamp. 128 | /// 129 | /// The number of hours to add 130 | /// Returns the same timestamp object 131 | public Timestamp AddHours(int hours) 132 | { 133 | return AddMinutes(hours * 60); 134 | } 135 | 136 | /// 137 | /// Adds hours onto the timestamp, rounding off to the nearest second. 138 | /// 139 | /// The fraction of hours to add 140 | /// Returns the same timestamp object. 141 | public Timestamp AddHours(float hours) 142 | { 143 | int h = Mathf.FloorToInt(hours); 144 | float m = (hours - h) * 60f; 145 | return AddHours(h).AddMinutes(m); 146 | } 147 | 148 | 149 | #region Value Conversions 150 | /// 151 | /// Casts the timestamp into a unix epoch count of seconds. 152 | /// 153 | /// The timestamp 154 | public static implicit operator long(Timestamp stamp) 155 | { 156 | return stamp.timestamp; 157 | } 158 | /// 159 | /// Converts the timestamp into a unity epoch (start of the game time as origin) count of seconds, where is now. 160 | /// 161 | /// The timestamp 162 | public static implicit operator float(Timestamp stamp) 163 | { 164 | return stamp.GetTime(); 165 | } 166 | /// 167 | /// Converts the timestamp into a representation. 168 | /// 169 | /// The timestamp 170 | public static implicit operator DateTime(Timestamp stamp) 171 | { 172 | return stamp.GetDateTime(); 173 | } 174 | #endregion 175 | 176 | #region Stamp Conversions 177 | /// 178 | /// Casts a unixh epoch count of seconds into a timestamp 179 | /// 180 | /// The time in milliseconds 181 | public static implicit operator Timestamp(long time) 182 | { 183 | return new Timestamp(time); 184 | } 185 | 186 | /// 187 | /// Converts the into a timestamp 188 | /// 189 | /// The time 190 | public static implicit operator Timestamp(DateTime time) 191 | { 192 | return new Timestamp(time); 193 | } 194 | /// 195 | /// Converts a unity epoch (start of game time as origin) count of seconds (where is now) into a timestamp. 196 | /// 197 | /// The time 198 | public static implicit operator Timestamp(float time) 199 | { 200 | return new Timestamp(time); 201 | } 202 | #endregion 203 | 204 | /// 205 | /// Converts a Unix Epoch time into a . 206 | /// 207 | /// The time in milliseconds since 1970 / 01 / 01 208 | /// 209 | public static DateTime FromUnixMilliseconds(long unixTime) 210 | { 211 | var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 212 | return epoch.AddMilliseconds(Convert.ToDouble(unixTime)); 213 | } 214 | 215 | /// 216 | /// Converts a into a Unix Epoch time (in milliseconds). 217 | /// 218 | /// The datetime to convert 219 | /// 220 | public static long ToUnixMilliseconds(DateTime date) 221 | { 222 | var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 223 | return Convert.ToInt64((date - epoch).TotalMilliseconds); 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /Runtime/Presence/Timestamp.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 22be56012353e8c4db5592836fcecb6f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Presence/User.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections; 4 | using System.IO; 5 | using UnityEngine; 6 | 7 | #if UNITY_2017_4_OR_NEWER 8 | using UnityEngine.Networking; 9 | #endif 10 | 11 | namespace Lachee.Discord 12 | { 13 | [System.Serializable] 14 | public sealed class User 15 | { 16 | /// 17 | /// Caching Level. This is a flag. 18 | /// 19 | [System.Flags] 20 | public enum CacheLevelFlag 21 | { 22 | /// Disable all caching 23 | None = 0, 24 | 25 | /// Caches avatars by user id (required for caching to work). 26 | UserId = 1, 27 | 28 | /// Caches the avatars by avatar hash. 29 | Hash = 3, //UserId | 2, 30 | 31 | /// Caches the avatars by size. If off, only the largest size is stored. 32 | Size = 5, //UserId | 4 33 | } 34 | 35 | /// 36 | /// The current location of the avatar caches 37 | /// 38 | public static string CacheDirectory = null; 39 | 40 | /// 41 | /// The caching level used by the avatar functions. Note that the cache is never cleared. The cache level will help mitigate exessive file counts. 42 | /// will cause no images to be cached and will be downloaded everytime they are fetched. 43 | /// will cache images based of their hash. Without this, the avatar will likely stay the same forever. 44 | /// will cache images based of their size. Useful, but may result in multiples of the same file. Disabling this will cause all files to be x512. 45 | /// 46 | public static CacheLevelFlag CacheLevel = CacheLevelFlag.None; 47 | 48 | /// 49 | /// The format to download and cache avatars in. By default, PNG is used. 50 | /// 51 | public static DiscordAvatarFormat AvatarFormat { get; set; } 52 | 53 | 54 | private DiscordRPC.User _user; 55 | 56 | /// 57 | /// The username of the Discord user 58 | /// 59 | public string username => _user?.Username; 60 | 61 | /// 62 | /// The display name of the user 63 | /// 64 | /// This will be empty if the user has not set a global display name. 65 | public string displayName => _user?.DisplayName; 66 | 67 | /// 68 | /// The discriminator of the user. 69 | /// 70 | /// If the user has migrated to unique a , the discriminator will always be 0. 71 | [Obsolete("Discord no longer uses discriminators.")] 72 | public int discriminator => (_user?.Discriminator).GetValueOrDefault(); 73 | 74 | /// 75 | /// The discriminator in a nicely formatted string. 76 | /// 77 | [Obsolete("Discord no longer uses discriminators.")] 78 | public string discrim { get { return "#" + discriminator.ToString("D4"); } } 79 | 80 | /// 81 | /// The unique snowflake ID of the Discord user 82 | /// 83 | public ulong ID => (_user?.ID).GetValueOrDefault(); 84 | 85 | /// 86 | /// The hash of the users avatar. Used to generate the URL's 87 | /// 88 | public string avatarHash => (_user?.Avatar); 89 | 90 | /// 91 | /// The current avatar cache. Will return null until is called. 92 | /// 93 | public Texture2D avatar { get; private set; } 94 | 95 | /// 96 | /// The size of the currently cached avatar 97 | /// 98 | public DiscordAvatarSize cacheSize { get; private set; } 99 | 100 | /// 101 | /// The format of the currently cached avatar 102 | /// 103 | public DiscordAvatarFormat cacheFormat { get; private set; } 104 | 105 | /// 106 | /// The current URL for the discord avatars 107 | /// 108 | private string cdnEndpoint => _user?.CdnEndpoint; 109 | 110 | #if UNITY_EDITOR 111 | #pragma warning disable 0414 112 | [HideInInspector] 113 | [SerializeField] 114 | private bool e_foldout = true; 115 | #pragma warning restore 0414 116 | #endif 117 | 118 | public User(DiscordRPC.User user) 119 | { 120 | _user = user; 121 | } 122 | 123 | /// 124 | /// An event that is triggered when the avatar finishes downloading. 125 | /// 126 | /// The user the avatar belongs too 127 | /// The avatar that was downloaded 128 | public delegate void AvatarDownloadCallback(User user, Texture2D avatar); 129 | 130 | /// 131 | /// Gets the user avatar as a Texture2D and starts it with the supplied monobehaviour. It will first check the cache if the image exists, if it does it will return the image. Otherwise it will download the image from Discord and store it in the cache, calling the callback once done. 132 | /// 133 | /// The target size of the avatar. Default is 128x128 134 | /// The callback for when the texture completes. Default is no-callback, but its highly recommended to use a callback 135 | /// 136 | public void GetAvatar(DiscordAvatarSize size = DiscordAvatarSize.x128, AvatarDownloadCallback callback = null) 137 | { 138 | DiscordManager.current.StartCoroutine(GetAvatarCoroutine(size, callback)); 139 | } 140 | 141 | /// 142 | /// Gets the user avatar as a Texture2D as a enumerator. It will first check the cache if the image exists, if it does it will return the image. Otherwise it will download the image from Discord and store it in the cache, calling the callback once done. 143 | /// If has set, then the size will be ignored and will be used instead. 144 | /// If is , then no files will be written for cache. 145 | /// 146 | /// The target size of the avatar. Default is 128x128 147 | /// The callback for when the texture completes. Default is no-callback, but its highly recommended to use a callback 148 | /// 149 | public IEnumerator GetAvatarCoroutine(DiscordAvatarSize size = DiscordAvatarSize.x128, AvatarDownloadCallback callback = null) 150 | { 151 | if (avatar != null) 152 | { 153 | //Execute the callback (if any) 154 | if (callback != null) 155 | callback.Invoke(this, avatar); 156 | 157 | //Stop here, we did all we need to do 158 | yield break; 159 | } 160 | 161 | if (string.IsNullOrEmpty(avatarHash)) 162 | { 163 | yield return GetDefaultAvatarCoroutine(size, callback); 164 | } 165 | else 166 | { 167 | //Prepare the cache path 168 | string path = null; 169 | 170 | //Build the formatting 171 | if (CacheLevel != CacheLevelFlag.None) 172 | { 173 | //Update the default cache just incase its null 174 | SetupDefaultCacheDirectory(); 175 | 176 | string format = "{0}"; 177 | if ((CacheLevel & CacheLevelFlag.Hash) == CacheLevelFlag.Hash) format += "-{1}"; 178 | if ((CacheLevel & CacheLevelFlag.Size) == CacheLevelFlag.Size) format += "{2}"; else size = DiscordAvatarSize.x512; 179 | 180 | //Generate the path name 181 | string filename = string.Format(format + ".{3}", ID, avatarHash, size.ToString(), User.AvatarFormat.ToString().ToLowerInvariant()); 182 | path = Path.Combine(CacheDirectory, filename); 183 | Debug.Log("Cache: " + path); 184 | } 185 | 186 | //The holder texture is null, so we should create new one 187 | Texture2D avatarTexture = new Texture2D((int)size, (int)size, TextureFormat.RGBA32, false); 188 | 189 | //Check if the file exists and we have caching enabled 190 | if (CacheLevel != CacheLevelFlag.None && File.Exists(path)) 191 | { 192 | //Load the image 193 | var bytes = File.ReadAllBytes(path); 194 | avatarTexture.LoadImage(bytes); 195 | } 196 | else 197 | { 198 | #if UNITY_2017_4_OR_NEWER 199 | using (UnityWebRequest req = UnityWebRequestTexture.GetTexture(GetAvatarURL(User.AvatarFormat, size))) 200 | { 201 | //Download the texture 202 | yield return req.SendWebRequest(); 203 | 204 | #if UNITY_2020_1_OR_NEWER 205 | if (req.result == UnityWebRequest.Result.ConnectionError || req.result == UnityWebRequest.Result.ProtocolError) 206 | #else 207 | if (req.isNetworkError || req.isHttpError) 208 | #endif 209 | { 210 | //Report errors 211 | Debug.LogError("Failed to download user avatar: " + req.error); 212 | } 213 | else 214 | { 215 | //Update the avatar 216 | avatarTexture = DownloadHandlerTexture.GetContent(req); 217 | } 218 | } 219 | #else 220 | using (WWW www = new WWW(GetAvatarURL(DiscordUser.AvatarFormat, size))) 221 | { 222 | //Download the texture 223 | yield return www; 224 | 225 | //Update the holder 226 | www.LoadImageIntoTexture(avatarTexture); 227 | } 228 | #endif 229 | } 230 | 231 | //Apply our avatar and update our cache 232 | if (avatarTexture != null) 233 | { 234 | CacheAvatarTexture(avatarTexture, path); 235 | avatar = avatarTexture; 236 | cacheFormat = User.AvatarFormat; 237 | cacheSize = size; 238 | 239 | //Execute the callback (if any) 240 | if (callback != null) 241 | callback.Invoke(this, avatarTexture); 242 | } 243 | } 244 | } 245 | 246 | /// Caches the avatar 247 | private void CacheAvatarTexture(Texture2D texture, string path) 248 | { 249 | //Encode and cache the files 250 | if (CacheLevel != CacheLevelFlag.None) 251 | { 252 | //Create the directory if it doesnt already exist 253 | if (!Directory.Exists(CacheDirectory)) 254 | Directory.CreateDirectory(CacheDirectory); 255 | 256 | //Encode the image 257 | byte[] bytes; 258 | switch (User.AvatarFormat) 259 | { 260 | default: 261 | case DiscordAvatarFormat.PNG: 262 | bytes = texture.EncodeToPNG(); 263 | break; 264 | 265 | case DiscordAvatarFormat.JPEG: 266 | bytes = texture.EncodeToJPG(); 267 | break; 268 | } 269 | 270 | //Save the image 271 | File.WriteAllBytes(path, bytes); 272 | } 273 | } 274 | 275 | 276 | /// 277 | /// Gets the default avatar for the given user. Will check the cache first, and if none are available it will then download the default from discord. 278 | /// If has set, then the size will be ignored and will be used instead. 279 | /// If is , then no files will be written for cache. 280 | /// 281 | /// The size of the target avatar 282 | /// The callback that will be made when the picture finishes downloading. 283 | /// 284 | /// 285 | public IEnumerator GetDefaultAvatarCoroutine(DiscordAvatarSize size = DiscordAvatarSize.x128, AvatarDownloadCallback callback = null) 286 | { 287 | //Calculate the discrim number and prepare the cache path 288 | string path = null; 289 | int index = (int)((ID >> 22) % 6); 290 | 291 | #pragma warning disable CS0618 // Disable the obsolete warning as we know the discriminator is obsolete and we are validating it here. 292 | if (discriminator > 0) 293 | index = discriminator % 5; 294 | #pragma warning restore CS0618 295 | 296 | //Update the default cache just incase its null 297 | if (CacheLevel != CacheLevelFlag.None) 298 | { 299 | //Setup the dir 300 | SetupDefaultCacheDirectory(); 301 | 302 | //should we cache the size? 303 | bool cacheSize = (CacheLevel & CacheLevelFlag.Size) == CacheLevelFlag.Size; 304 | if (!cacheSize) size = DiscordAvatarSize.x512; 305 | 306 | string filename = string.Format("default-{0}{1}.png", index, cacheSize ? size.ToString() : ""); 307 | path = Path.Combine(CacheDirectory, filename); 308 | } 309 | 310 | //The holder texture is null, so we should create new one 311 | Texture2D avatarTexture = new Texture2D((int)size, (int)size, TextureFormat.RGBA32, false); 312 | 313 | //Check if the file exists 314 | if (CacheLevel != CacheLevelFlag.None && File.Exists(path)) 315 | { 316 | //Load the image 317 | byte[] bytes = File.ReadAllBytes(path); 318 | avatarTexture.LoadImage(bytes); 319 | } 320 | else 321 | { 322 | string url = string.Format("https://{0}/embed/avatars/{1}.png?size={2}", cdnEndpoint, index, (int)size); 323 | 324 | #if UNITY_2017_4_OR_NEWER 325 | using (UnityWebRequest req = UnityWebRequestTexture.GetTexture(url)) 326 | { 327 | //Download the texture 328 | yield return req.SendWebRequest(); 329 | #if UNITY_2020_1_OR_NEWER 330 | if (req.result == UnityWebRequest.Result.ConnectionError || req.result == UnityWebRequest.Result.ProtocolError) 331 | #else 332 | if (req.isNetworkError || req.isHttpError) 333 | #endif 334 | { 335 | //Report errors 336 | Debug.LogError("Failed to download default avatar: " + req.error); 337 | } 338 | else 339 | { 340 | //Update the avatar 341 | avatarTexture = DownloadHandlerTexture.GetContent(req); 342 | } 343 | } 344 | #else 345 | using (WWW www = new WWW(url)) 346 | { 347 | //Download the texture 348 | yield return www; 349 | 350 | //Update the holder 351 | www.LoadImageIntoTexture(avatarTexture); 352 | } 353 | #endif 354 | //We have been told to cache, so do so. 355 | if (CacheLevel != CacheLevelFlag.None) 356 | { 357 | //Create the directory if it doesnt already exist 358 | if (!Directory.Exists(CacheDirectory)) 359 | Directory.CreateDirectory(CacheDirectory); 360 | 361 | byte[] bytes = avatarTexture.EncodeToPNG(); 362 | File.WriteAllBytes(path, bytes); 363 | } 364 | } 365 | 366 | //Apply our avatar and update our cache 367 | avatar = avatarTexture; 368 | cacheFormat = DiscordAvatarFormat.PNG; 369 | cacheSize = size; 370 | 371 | //Execute the callback (if any) 372 | if (callback != null) 373 | callback.Invoke(this, avatar); 374 | } 375 | 376 | /// 377 | /// Updates the default directory for the cache 378 | /// 379 | private static void SetupDefaultCacheDirectory() 380 | { 381 | if (CacheDirectory == null) 382 | CacheDirectory = Application.dataPath + "/Discord Rpc/Cache"; 383 | } 384 | 385 | /// 386 | /// Gets a URL that can be used to download the user's avatar. If the user has not yet set their avatar, it will return the default one that discord is using. The default avatar only supports the format. 387 | /// 388 | /// The format of the target avatar 389 | /// The optional size of the avatar you wish for. 390 | /// URL to the discord CDN for the particular avatar 391 | private string GetAvatarURL(DiscordAvatarFormat format, DiscordAvatarSize size) 392 | { 393 | return _user.GetAvatarURL(format == DiscordAvatarFormat.PNG ? DiscordRPC.User.AvatarFormat.PNG : DiscordRPC.User.AvatarFormat.JPEG, (DiscordRPC.User.AvatarSize)size); 394 | } 395 | 396 | /// 397 | /// Formats the user into a displayable format. If the user has a , then this will be used. 398 | /// If the user still has a discriminator, then this will return the form of `Username#Discriminator`. 399 | /// 400 | /// String of the user that can be used for display. 401 | public override string ToString() 402 | { 403 | if (_user == null) return "N/A"; 404 | return _user.ToString(); 405 | } 406 | 407 | /// 408 | /// Implicit casting from a DiscordRPC.User to a DiscordUser 409 | /// 410 | /// 411 | public static implicit operator User(DiscordRPC.User user) { return new User(user); } 412 | public static implicit operator DiscordRPC.User(User user) { return user._user; } 413 | 414 | public override int GetHashCode() 415 | { 416 | return this.ID.GetHashCode() ^ 7; 417 | } 418 | 419 | public override bool Equals(object obj) 420 | { 421 | if (obj is User) 422 | return this.ID == ((User)obj).ID; 423 | 424 | return false; 425 | } 426 | } 427 | 428 | /// 429 | /// The format of the discord avatars in the cache 430 | /// 431 | public enum DiscordAvatarFormat 432 | { 433 | /// 434 | /// Portable Network Graphics format (.png) 435 | /// Losses format that supports transparent avatars. Most recommended for stationary formats with wide support from many libraries. 436 | /// 437 | PNG, 438 | 439 | /// 440 | /// Joint Photographic Experts Group format (.jpeg) 441 | /// The format most cameras use. Lossy and does not support transparent avatars. 442 | /// 443 | JPEG 444 | } 445 | 446 | /// 447 | /// Possible square sizes of avatars. 448 | /// 449 | public enum DiscordAvatarSize 450 | { 451 | /// 16 x 16 pixels. 452 | x16 = 16, 453 | /// 32 x 32 pixels. 454 | x32 = 32, 455 | /// 64 x 64 pixels. 456 | x64 = 64, 457 | /// 128 x 128 pixels. 458 | x128 = 128, 459 | /// 256 x 256 pixels. 460 | x256 = 256, 461 | /// 512 x 512 pixels. 462 | x512 = 512, 463 | /// 1024 x 1024 pixels. 464 | x1024 = 1024, 465 | /// 2048 x 2048 pixels. 466 | x2048 = 2048 467 | } 468 | 469 | /// 470 | /// Collection of extensions to the class. 471 | /// 472 | public static class DiscordUserExtension 473 | { 474 | /// 475 | /// Gets the user avatar as a Texture2D and starts it with the supplied monobehaviour. It will first check the cache if the image exists, if it does it will return the image. Otherwise it will download the image from Discord and store it in the cache, calling the callback once done. 476 | /// An alias of and will return the new instance. 477 | /// 478 | /// The target size of the avatar. Default is 128x128 479 | /// The callback for when the texture completes. Default is no-callback, but its highly recommended to use a callback 480 | /// Returns the generated for this object. 481 | public static User GetAvatar(this DiscordRPC.User user,DiscordAvatarSize size = DiscordAvatarSize.x128, User.AvatarDownloadCallback callback = null) 482 | { 483 | var du = new User(user); 484 | du.GetAvatar(size, callback); 485 | return du; 486 | } 487 | 488 | /// 489 | /// Gets the user avatar as a Texture2D as a enumerator. It will first check the cache if the image exists, if it does it will return the image. Otherwise it will download the image from Discord and store it in the cache, calling the callback once done. 490 | /// An alias of and will return the new instance in the callback. 491 | /// 492 | /// The target size of the avatar. Default is 128x128 493 | /// The callback for when the texture completes. Default is no-callback, but its highly recommended to use a callback 494 | /// 495 | public static IEnumerator GetAvatarCoroutine(this DiscordRPC.User user, DiscordAvatarSize size = DiscordAvatarSize.x128, User.AvatarDownloadCallback callback = null) 496 | { 497 | var du = new User(user); 498 | return du.GetDefaultAvatarCoroutine(size, callback); 499 | } 500 | 501 | /// 502 | /// Gets the default avatar for the given user. Will check the cache first, and if none are available it will then download the default from discord. 503 | /// An alias of and will return the new instance in the callback. 504 | /// 505 | /// The size of the target avatar 506 | /// The callback that will be made when the picture finishes downloading. 507 | /// 508 | public static IEnumerator GetDefaultAvatarCoroutine(this DiscordRPC.User user, DiscordAvatarSize size = DiscordAvatarSize.x128, User.AvatarDownloadCallback callback = null) 509 | { 510 | var du = new User(user); 511 | return du.GetDefaultAvatarCoroutine(size, callback); 512 | } 513 | } 514 | } -------------------------------------------------------------------------------- /Runtime/Presence/User.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3adb9074488ff7d4b986add5005df8fc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/com.lachee.discordrpc.runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.lachee.discordrpc.runtime", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false 8 | } -------------------------------------------------------------------------------- /Runtime/com.lachee.discordrpc.runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dbe5ccece09868a4db171d8cd0086c18 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Samples~/Rich Presence.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 29b8d30d888719a4ca2fd2c5ec2fb761 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/ConstantRotate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace DiscordRPC.Examples 6 | { 7 | public class ConstantRotate : MonoBehaviour 8 | { 9 | 10 | public Vector3 rotation; 11 | 12 | // Update is called once per frame 13 | void Update() 14 | { 15 | transform.Rotate(rotation); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/ConstantRotate.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 835ec75000b58cf4aba592fb846a629b 3 | timeCreated: 1521367966 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_ReadyListener.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord; 2 | using Lachee.Discord.Events; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | 7 | namespace DiscordRPC.Examples.RichPresence 8 | { 9 | public class Example_ReadyListener : MonoBehaviour 10 | { 11 | void Start() 12 | { 13 | // This method is only required for 2018 or below. 14 | // Since 2019, Unity can serialize generics 15 | DiscordManager.current.OnReady.AddListener(OnReady); 16 | } 17 | 18 | public void OnReady(ReadyEvent evt) 19 | { 20 | Debug.Log("Received Ready!"); 21 | evt.user.GetAvatar(DiscordAvatarSize.x1024, (user, texture) => 22 | { 23 | var renderer = GetComponent(); 24 | renderer.material.mainTexture = texture; 25 | }); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_ReadyListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a6886d548845d24468b7160b582ef670 3 | timeCreated: 1535178489 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_RichPresence.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEngine.UI; 6 | 7 | namespace DiscordRPC.Examples.RichPresence 8 | { 9 | public class Example_RichPresence : MonoBehaviour 10 | { 11 | 12 | [Header("Details")] 13 | public InputField inputDetails, inputState; 14 | 15 | [Header("Time")] 16 | public Toggle inputStartTime; 17 | public InputField inputEndTime; 18 | 19 | [Header("Images")] 20 | public InputField inputLargeKey; 21 | public InputField inputLargeTooltip; 22 | public InputField inputSmallKey; 23 | public InputField inputSmallTooltip; 24 | 25 | 26 | public Presence presence; 27 | 28 | private void Start() 29 | { 30 | //Update the values 31 | UpdateFields(presence); 32 | 33 | //Register to a presence change 34 | DiscordManager.current.OnPresence.AddListener((message) => 35 | { 36 | Debug.Log("Received a new presence! Current App: " + message.applicationID + ", " + message.name); 37 | UpdateFields(presence); 38 | }); 39 | } 40 | 41 | public void SendPresence() 42 | { 43 | UpdatePresence(); 44 | DiscordManager.current.SetPresence(presence); 45 | } 46 | 47 | public void UpdateFields(Presence presence) 48 | { 49 | if (presence == null) 50 | { 51 | return; 52 | } 53 | 54 | this.presence = presence; 55 | inputState.text = presence.state; 56 | inputDetails.text = presence.details; 57 | 58 | 59 | inputLargeTooltip.text = presence.largeAsset.tooltip; 60 | inputLargeKey.text = presence.largeAsset.image; 61 | 62 | inputSmallTooltip.text = presence.smallAsset.tooltip; 63 | inputSmallKey.text = presence.smallAsset.image; 64 | } 65 | 66 | public void UpdatePresence() 67 | { 68 | presence.state = inputState.text; 69 | presence.details = inputDetails.text; 70 | presence.startTime = inputStartTime.isOn ? new Timestamp(Time.realtimeSinceStartup) : Timestamp.Invalid; 71 | 72 | presence.largeAsset = new Asset() 73 | { 74 | image = inputLargeKey.text, 75 | tooltip = inputLargeTooltip.text 76 | }; 77 | presence.smallAsset = new Asset() 78 | { 79 | image = inputSmallKey.text, 80 | tooltip = inputSmallTooltip.text 81 | }; 82 | 83 | float duration = float.Parse(inputEndTime.text); 84 | presence.endTime = duration > 0 ? new Timestamp(Time.realtimeSinceStartup + duration) : Timestamp.Invalid; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_RichPresence.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ee9b415880f902147837a1db9af2150b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_RichPresence.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 799086f5f8ec62d4f9426712b2e14809 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_SetStatusButton.cs: -------------------------------------------------------------------------------- 1 | using Lachee.Discord; 2 | using Lachee.Discord.Events; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | 7 | namespace DiscordRPC.Examples.RichPresence 8 | { 9 | public class Example_SetStatusButton : MonoBehaviour 10 | { 11 | public UnityEngine.UI.Button button; 12 | public UnityEngine.UI.InputField input; 13 | 14 | void Start() 15 | { 16 | // This method is only required for 2018 or below. 17 | // Since 2019, Unity can serialize generics 18 | DiscordManager.current.OnReady.AddListener(OnReady); 19 | button.onClick.AddListener(OnClick); 20 | button.interactable = false; 21 | } 22 | 23 | public void OnReady(ReadyEvent evt) 24 | { 25 | button.interactable = true; 26 | } 27 | 28 | public void OnClick() 29 | { 30 | var presence = DiscordManager.current.CurrentPresence; 31 | presence.details = input.text; 32 | DiscordManager.current.SetPresence(presence); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Samples~/Rich Presence/Example_SetStatusButton.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 69e3792da7f6c8640a8e82dbfd081e47 3 | timeCreated: 1535178489 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/Spinning Cubes.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1001 &100100000 4 | Prefab: 5 | m_ObjectHideFlags: 1 6 | serializedVersion: 2 7 | m_Modification: 8 | m_TransformParent: {fileID: 0} 9 | m_Modifications: [] 10 | m_RemovedComponents: [] 11 | m_SourcePrefab: {fileID: 0} 12 | m_RootGameObject: {fileID: 1073555440304004} 13 | m_IsPrefabAsset: 1 14 | --- !u!1 &1041719462949384 15 | GameObject: 16 | m_ObjectHideFlags: 0 17 | m_CorrespondingSourceObject: {fileID: 0} 18 | m_PrefabInternal: {fileID: 100100000} 19 | serializedVersion: 6 20 | m_Component: 21 | - component: {fileID: 4729002408311522} 22 | - component: {fileID: 33564632248098672} 23 | - component: {fileID: 65047797416528598} 24 | - component: {fileID: 23054393140330284} 25 | - component: {fileID: 114115697624970392} 26 | m_Layer: 0 27 | m_Name: Baby Cube 28 | m_TagString: Untagged 29 | m_Icon: {fileID: 0} 30 | m_NavMeshLayer: 0 31 | m_StaticEditorFlags: 0 32 | m_IsActive: 1 33 | --- !u!1 &1073555440304004 34 | GameObject: 35 | m_ObjectHideFlags: 0 36 | m_CorrespondingSourceObject: {fileID: 0} 37 | m_PrefabInternal: {fileID: 100100000} 38 | serializedVersion: 6 39 | m_Component: 40 | - component: {fileID: 4162741165016952} 41 | m_Layer: 0 42 | m_Name: Spinning Cubes 43 | m_TagString: Untagged 44 | m_Icon: {fileID: 0} 45 | m_NavMeshLayer: 0 46 | m_StaticEditorFlags: 0 47 | m_IsActive: 1 48 | --- !u!1 &1680671491621748 49 | GameObject: 50 | m_ObjectHideFlags: 0 51 | m_CorrespondingSourceObject: {fileID: 0} 52 | m_PrefabInternal: {fileID: 100100000} 53 | serializedVersion: 6 54 | m_Component: 55 | - component: {fileID: 4545879213793466} 56 | - component: {fileID: 33966280038937740} 57 | - component: {fileID: 65423381411825540} 58 | - component: {fileID: 23317795868008346} 59 | - component: {fileID: 114491102716508916} 60 | m_Layer: 0 61 | m_Name: Baby Cube 62 | m_TagString: Untagged 63 | m_Icon: {fileID: 0} 64 | m_NavMeshLayer: 0 65 | m_StaticEditorFlags: 0 66 | m_IsActive: 1 67 | --- !u!1 &1750859589963992 68 | GameObject: 69 | m_ObjectHideFlags: 0 70 | m_CorrespondingSourceObject: {fileID: 0} 71 | m_PrefabInternal: {fileID: 100100000} 72 | serializedVersion: 6 73 | m_Component: 74 | - component: {fileID: 4782709639625668} 75 | - component: {fileID: 33051478306752160} 76 | - component: {fileID: 65227327944083028} 77 | - component: {fileID: 23165215184582192} 78 | - component: {fileID: 114194695389752790} 79 | - component: {fileID: 114780491621147760} 80 | m_Layer: 0 81 | m_Name: Daddy Cube 82 | m_TagString: Untagged 83 | m_Icon: {fileID: 0} 84 | m_NavMeshLayer: 0 85 | m_StaticEditorFlags: 0 86 | m_IsActive: 1 87 | --- !u!1 &1821574291237272 88 | GameObject: 89 | m_ObjectHideFlags: 0 90 | m_CorrespondingSourceObject: {fileID: 0} 91 | m_PrefabInternal: {fileID: 100100000} 92 | serializedVersion: 6 93 | m_Component: 94 | - component: {fileID: 4964883829626994} 95 | - component: {fileID: 33934235896704876} 96 | - component: {fileID: 65262898154187970} 97 | - component: {fileID: 23360437394164212} 98 | - component: {fileID: 114111543808520302} 99 | m_Layer: 0 100 | m_Name: Baby Cube 101 | m_TagString: Untagged 102 | m_Icon: {fileID: 0} 103 | m_NavMeshLayer: 0 104 | m_StaticEditorFlags: 0 105 | m_IsActive: 1 106 | --- !u!1 &1948494897459636 107 | GameObject: 108 | m_ObjectHideFlags: 0 109 | m_CorrespondingSourceObject: {fileID: 0} 110 | m_PrefabInternal: {fileID: 100100000} 111 | serializedVersion: 6 112 | m_Component: 113 | - component: {fileID: 4690729713261028} 114 | - component: {fileID: 33710475515543736} 115 | - component: {fileID: 65510122804828914} 116 | - component: {fileID: 23512684803758586} 117 | - component: {fileID: 114733796681027892} 118 | m_Layer: 0 119 | m_Name: Baby Cube 120 | m_TagString: Untagged 121 | m_Icon: {fileID: 0} 122 | m_NavMeshLayer: 0 123 | m_StaticEditorFlags: 0 124 | m_IsActive: 1 125 | --- !u!4 &4162741165016952 126 | Transform: 127 | m_ObjectHideFlags: 1 128 | m_CorrespondingSourceObject: {fileID: 0} 129 | m_PrefabInternal: {fileID: 100100000} 130 | m_GameObject: {fileID: 1073555440304004} 131 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 132 | m_LocalPosition: {x: 0, y: 0, z: 0} 133 | m_LocalScale: {x: 1, y: 1, z: 1} 134 | m_Children: 135 | - {fileID: 4782709639625668} 136 | - {fileID: 4690729713261028} 137 | - {fileID: 4729002408311522} 138 | - {fileID: 4545879213793466} 139 | - {fileID: 4964883829626994} 140 | m_Father: {fileID: 0} 141 | m_RootOrder: 0 142 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 143 | --- !u!4 &4545879213793466 144 | Transform: 145 | m_ObjectHideFlags: 1 146 | m_CorrespondingSourceObject: {fileID: 0} 147 | m_PrefabInternal: {fileID: 100100000} 148 | m_GameObject: {fileID: 1680671491621748} 149 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 150 | m_LocalPosition: {x: 3.334, y: -1.97, z: 0} 151 | m_LocalScale: {x: 0.79621184, y: 0.7962116, z: 0.7962116} 152 | m_Children: [] 153 | m_Father: {fileID: 4162741165016952} 154 | m_RootOrder: 3 155 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 156 | --- !u!4 &4690729713261028 157 | Transform: 158 | m_ObjectHideFlags: 1 159 | m_CorrespondingSourceObject: {fileID: 0} 160 | m_PrefabInternal: {fileID: 100100000} 161 | m_GameObject: {fileID: 1948494897459636} 162 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 163 | m_LocalPosition: {x: -2.81, y: 2.37, z: 0} 164 | m_LocalScale: {x: 0.79621184, y: 0.7962116, z: 0.7962116} 165 | m_Children: [] 166 | m_Father: {fileID: 4162741165016952} 167 | m_RootOrder: 1 168 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 169 | --- !u!4 &4729002408311522 170 | Transform: 171 | m_ObjectHideFlags: 1 172 | m_CorrespondingSourceObject: {fileID: 0} 173 | m_PrefabInternal: {fileID: 100100000} 174 | m_GameObject: {fileID: 1041719462949384} 175 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 176 | m_LocalPosition: {x: 3.334, y: 2.37, z: 0} 177 | m_LocalScale: {x: 0.79621184, y: 0.7962116, z: 0.7962116} 178 | m_Children: [] 179 | m_Father: {fileID: 4162741165016952} 180 | m_RootOrder: 2 181 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 182 | --- !u!4 &4782709639625668 183 | Transform: 184 | m_ObjectHideFlags: 1 185 | m_CorrespondingSourceObject: {fileID: 0} 186 | m_PrefabInternal: {fileID: 100100000} 187 | m_GameObject: {fileID: 1750859589963992} 188 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 189 | m_LocalPosition: {x: 0, y: 0, z: 0} 190 | m_LocalScale: {x: 1.8866842, y: 1.886684, z: 1.886684} 191 | m_Children: [] 192 | m_Father: {fileID: 4162741165016952} 193 | m_RootOrder: 0 194 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 195 | --- !u!4 &4964883829626994 196 | Transform: 197 | m_ObjectHideFlags: 1 198 | m_CorrespondingSourceObject: {fileID: 0} 199 | m_PrefabInternal: {fileID: 100100000} 200 | m_GameObject: {fileID: 1821574291237272} 201 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 202 | m_LocalPosition: {x: -3.51, y: -1.97, z: 0} 203 | m_LocalScale: {x: 0.79621184, y: 0.7962116, z: 0.7962116} 204 | m_Children: [] 205 | m_Father: {fileID: 4162741165016952} 206 | m_RootOrder: 4 207 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 208 | --- !u!23 &23054393140330284 209 | MeshRenderer: 210 | m_ObjectHideFlags: 1 211 | m_CorrespondingSourceObject: {fileID: 0} 212 | m_PrefabInternal: {fileID: 100100000} 213 | m_GameObject: {fileID: 1041719462949384} 214 | m_Enabled: 1 215 | m_CastShadows: 1 216 | m_ReceiveShadows: 1 217 | m_DynamicOccludee: 1 218 | m_MotionVectors: 1 219 | m_LightProbeUsage: 1 220 | m_ReflectionProbeUsage: 1 221 | m_RenderingLayerMask: 4294967295 222 | m_Materials: 223 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 224 | m_StaticBatchInfo: 225 | firstSubMesh: 0 226 | subMeshCount: 0 227 | m_StaticBatchRoot: {fileID: 0} 228 | m_ProbeAnchor: {fileID: 0} 229 | m_LightProbeVolumeOverride: {fileID: 0} 230 | m_ScaleInLightmap: 1 231 | m_PreserveUVs: 1 232 | m_IgnoreNormalsForChartDetection: 0 233 | m_ImportantGI: 0 234 | m_StitchLightmapSeams: 0 235 | m_SelectedEditorRenderState: 3 236 | m_MinimumChartSize: 4 237 | m_AutoUVMaxDistance: 0.5 238 | m_AutoUVMaxAngle: 89 239 | m_LightmapParameters: {fileID: 0} 240 | m_SortingLayerID: 0 241 | m_SortingLayer: 0 242 | m_SortingOrder: 0 243 | --- !u!23 &23165215184582192 244 | MeshRenderer: 245 | m_ObjectHideFlags: 1 246 | m_CorrespondingSourceObject: {fileID: 0} 247 | m_PrefabInternal: {fileID: 100100000} 248 | m_GameObject: {fileID: 1750859589963992} 249 | m_Enabled: 1 250 | m_CastShadows: 1 251 | m_ReceiveShadows: 1 252 | m_DynamicOccludee: 1 253 | m_MotionVectors: 1 254 | m_LightProbeUsage: 1 255 | m_ReflectionProbeUsage: 1 256 | m_RenderingLayerMask: 4294967295 257 | m_Materials: 258 | - {fileID: 2100000, guid: c8fac7cd63ac11b47aaa9eeefec804a8, type: 2} 259 | m_StaticBatchInfo: 260 | firstSubMesh: 0 261 | subMeshCount: 0 262 | m_StaticBatchRoot: {fileID: 0} 263 | m_ProbeAnchor: {fileID: 0} 264 | m_LightProbeVolumeOverride: {fileID: 0} 265 | m_ScaleInLightmap: 1 266 | m_PreserveUVs: 1 267 | m_IgnoreNormalsForChartDetection: 0 268 | m_ImportantGI: 0 269 | m_StitchLightmapSeams: 0 270 | m_SelectedEditorRenderState: 3 271 | m_MinimumChartSize: 4 272 | m_AutoUVMaxDistance: 0.5 273 | m_AutoUVMaxAngle: 89 274 | m_LightmapParameters: {fileID: 0} 275 | m_SortingLayerID: 0 276 | m_SortingLayer: 0 277 | m_SortingOrder: 0 278 | --- !u!23 &23317795868008346 279 | MeshRenderer: 280 | m_ObjectHideFlags: 1 281 | m_CorrespondingSourceObject: {fileID: 0} 282 | m_PrefabInternal: {fileID: 100100000} 283 | m_GameObject: {fileID: 1680671491621748} 284 | m_Enabled: 1 285 | m_CastShadows: 1 286 | m_ReceiveShadows: 1 287 | m_DynamicOccludee: 1 288 | m_MotionVectors: 1 289 | m_LightProbeUsage: 1 290 | m_ReflectionProbeUsage: 1 291 | m_RenderingLayerMask: 4294967295 292 | m_Materials: 293 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 294 | m_StaticBatchInfo: 295 | firstSubMesh: 0 296 | subMeshCount: 0 297 | m_StaticBatchRoot: {fileID: 0} 298 | m_ProbeAnchor: {fileID: 0} 299 | m_LightProbeVolumeOverride: {fileID: 0} 300 | m_ScaleInLightmap: 1 301 | m_PreserveUVs: 1 302 | m_IgnoreNormalsForChartDetection: 0 303 | m_ImportantGI: 0 304 | m_StitchLightmapSeams: 0 305 | m_SelectedEditorRenderState: 3 306 | m_MinimumChartSize: 4 307 | m_AutoUVMaxDistance: 0.5 308 | m_AutoUVMaxAngle: 89 309 | m_LightmapParameters: {fileID: 0} 310 | m_SortingLayerID: 0 311 | m_SortingLayer: 0 312 | m_SortingOrder: 0 313 | --- !u!23 &23360437394164212 314 | MeshRenderer: 315 | m_ObjectHideFlags: 1 316 | m_CorrespondingSourceObject: {fileID: 0} 317 | m_PrefabInternal: {fileID: 100100000} 318 | m_GameObject: {fileID: 1821574291237272} 319 | m_Enabled: 1 320 | m_CastShadows: 1 321 | m_ReceiveShadows: 1 322 | m_DynamicOccludee: 1 323 | m_MotionVectors: 1 324 | m_LightProbeUsage: 1 325 | m_ReflectionProbeUsage: 1 326 | m_RenderingLayerMask: 4294967295 327 | m_Materials: 328 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 329 | m_StaticBatchInfo: 330 | firstSubMesh: 0 331 | subMeshCount: 0 332 | m_StaticBatchRoot: {fileID: 0} 333 | m_ProbeAnchor: {fileID: 0} 334 | m_LightProbeVolumeOverride: {fileID: 0} 335 | m_ScaleInLightmap: 1 336 | m_PreserveUVs: 1 337 | m_IgnoreNormalsForChartDetection: 0 338 | m_ImportantGI: 0 339 | m_StitchLightmapSeams: 0 340 | m_SelectedEditorRenderState: 3 341 | m_MinimumChartSize: 4 342 | m_AutoUVMaxDistance: 0.5 343 | m_AutoUVMaxAngle: 89 344 | m_LightmapParameters: {fileID: 0} 345 | m_SortingLayerID: 0 346 | m_SortingLayer: 0 347 | m_SortingOrder: 0 348 | --- !u!23 &23512684803758586 349 | MeshRenderer: 350 | m_ObjectHideFlags: 1 351 | m_CorrespondingSourceObject: {fileID: 0} 352 | m_PrefabInternal: {fileID: 100100000} 353 | m_GameObject: {fileID: 1948494897459636} 354 | m_Enabled: 1 355 | m_CastShadows: 1 356 | m_ReceiveShadows: 1 357 | m_DynamicOccludee: 1 358 | m_MotionVectors: 1 359 | m_LightProbeUsage: 1 360 | m_ReflectionProbeUsage: 1 361 | m_RenderingLayerMask: 4294967295 362 | m_Materials: 363 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 364 | m_StaticBatchInfo: 365 | firstSubMesh: 0 366 | subMeshCount: 0 367 | m_StaticBatchRoot: {fileID: 0} 368 | m_ProbeAnchor: {fileID: 0} 369 | m_LightProbeVolumeOverride: {fileID: 0} 370 | m_ScaleInLightmap: 1 371 | m_PreserveUVs: 1 372 | m_IgnoreNormalsForChartDetection: 0 373 | m_ImportantGI: 0 374 | m_StitchLightmapSeams: 0 375 | m_SelectedEditorRenderState: 3 376 | m_MinimumChartSize: 4 377 | m_AutoUVMaxDistance: 0.5 378 | m_AutoUVMaxAngle: 89 379 | m_LightmapParameters: {fileID: 0} 380 | m_SortingLayerID: 0 381 | m_SortingLayer: 0 382 | m_SortingOrder: 0 383 | --- !u!33 &33051478306752160 384 | MeshFilter: 385 | m_ObjectHideFlags: 1 386 | m_CorrespondingSourceObject: {fileID: 0} 387 | m_PrefabInternal: {fileID: 100100000} 388 | m_GameObject: {fileID: 1750859589963992} 389 | m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} 390 | --- !u!33 &33564632248098672 391 | MeshFilter: 392 | m_ObjectHideFlags: 1 393 | m_CorrespondingSourceObject: {fileID: 0} 394 | m_PrefabInternal: {fileID: 100100000} 395 | m_GameObject: {fileID: 1041719462949384} 396 | m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} 397 | --- !u!33 &33710475515543736 398 | MeshFilter: 399 | m_ObjectHideFlags: 1 400 | m_CorrespondingSourceObject: {fileID: 0} 401 | m_PrefabInternal: {fileID: 100100000} 402 | m_GameObject: {fileID: 1948494897459636} 403 | m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} 404 | --- !u!33 &33934235896704876 405 | MeshFilter: 406 | m_ObjectHideFlags: 1 407 | m_CorrespondingSourceObject: {fileID: 0} 408 | m_PrefabInternal: {fileID: 100100000} 409 | m_GameObject: {fileID: 1821574291237272} 410 | m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} 411 | --- !u!33 &33966280038937740 412 | MeshFilter: 413 | m_ObjectHideFlags: 1 414 | m_CorrespondingSourceObject: {fileID: 0} 415 | m_PrefabInternal: {fileID: 100100000} 416 | m_GameObject: {fileID: 1680671491621748} 417 | m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} 418 | --- !u!65 &65047797416528598 419 | BoxCollider: 420 | m_ObjectHideFlags: 1 421 | m_CorrespondingSourceObject: {fileID: 0} 422 | m_PrefabInternal: {fileID: 100100000} 423 | m_GameObject: {fileID: 1041719462949384} 424 | m_Material: {fileID: 0} 425 | m_IsTrigger: 0 426 | m_Enabled: 1 427 | serializedVersion: 2 428 | m_Size: {x: 1, y: 1, z: 1} 429 | m_Center: {x: 0, y: 0, z: 0} 430 | --- !u!65 &65227327944083028 431 | BoxCollider: 432 | m_ObjectHideFlags: 1 433 | m_CorrespondingSourceObject: {fileID: 0} 434 | m_PrefabInternal: {fileID: 100100000} 435 | m_GameObject: {fileID: 1750859589963992} 436 | m_Material: {fileID: 0} 437 | m_IsTrigger: 0 438 | m_Enabled: 1 439 | serializedVersion: 2 440 | m_Size: {x: 1, y: 1, z: 1} 441 | m_Center: {x: 0, y: 0, z: 0} 442 | --- !u!65 &65262898154187970 443 | BoxCollider: 444 | m_ObjectHideFlags: 1 445 | m_CorrespondingSourceObject: {fileID: 0} 446 | m_PrefabInternal: {fileID: 100100000} 447 | m_GameObject: {fileID: 1821574291237272} 448 | m_Material: {fileID: 0} 449 | m_IsTrigger: 0 450 | m_Enabled: 1 451 | serializedVersion: 2 452 | m_Size: {x: 1, y: 1, z: 1} 453 | m_Center: {x: 0, y: 0, z: 0} 454 | --- !u!65 &65423381411825540 455 | BoxCollider: 456 | m_ObjectHideFlags: 1 457 | m_CorrespondingSourceObject: {fileID: 0} 458 | m_PrefabInternal: {fileID: 100100000} 459 | m_GameObject: {fileID: 1680671491621748} 460 | m_Material: {fileID: 0} 461 | m_IsTrigger: 0 462 | m_Enabled: 1 463 | serializedVersion: 2 464 | m_Size: {x: 1, y: 1, z: 1} 465 | m_Center: {x: 0, y: 0, z: 0} 466 | --- !u!65 &65510122804828914 467 | BoxCollider: 468 | m_ObjectHideFlags: 1 469 | m_CorrespondingSourceObject: {fileID: 0} 470 | m_PrefabInternal: {fileID: 100100000} 471 | m_GameObject: {fileID: 1948494897459636} 472 | m_Material: {fileID: 0} 473 | m_IsTrigger: 0 474 | m_Enabled: 1 475 | serializedVersion: 2 476 | m_Size: {x: 1, y: 1, z: 1} 477 | m_Center: {x: 0, y: 0, z: 0} 478 | --- !u!114 &114111543808520302 479 | MonoBehaviour: 480 | m_ObjectHideFlags: 1 481 | m_CorrespondingSourceObject: {fileID: 0} 482 | m_PrefabInternal: {fileID: 100100000} 483 | m_GameObject: {fileID: 1821574291237272} 484 | m_Enabled: 1 485 | m_EditorHideFlags: 0 486 | m_Script: {fileID: 11500000, guid: 835ec75000b58cf4aba592fb846a629b, type: 3} 487 | m_Name: 488 | m_EditorClassIdentifier: 489 | rotation: {x: 0, y: 0, z: 0} 490 | --- !u!114 &114115697624970392 491 | MonoBehaviour: 492 | m_ObjectHideFlags: 1 493 | m_CorrespondingSourceObject: {fileID: 0} 494 | m_PrefabInternal: {fileID: 100100000} 495 | m_GameObject: {fileID: 1041719462949384} 496 | m_Enabled: 1 497 | m_EditorHideFlags: 0 498 | m_Script: {fileID: 11500000, guid: 835ec75000b58cf4aba592fb846a629b, type: 3} 499 | m_Name: 500 | m_EditorClassIdentifier: 501 | rotation: {x: 0, y: 0, z: 0} 502 | --- !u!114 &114194695389752790 503 | MonoBehaviour: 504 | m_ObjectHideFlags: 1 505 | m_CorrespondingSourceObject: {fileID: 0} 506 | m_PrefabInternal: {fileID: 100100000} 507 | m_GameObject: {fileID: 1750859589963992} 508 | m_Enabled: 1 509 | m_EditorHideFlags: 0 510 | m_Script: {fileID: 11500000, guid: 835ec75000b58cf4aba592fb846a629b, type: 3} 511 | m_Name: 512 | m_EditorClassIdentifier: 513 | rotation: {x: 0, y: 0, z: 0} 514 | --- !u!114 &114491102716508916 515 | MonoBehaviour: 516 | m_ObjectHideFlags: 1 517 | m_CorrespondingSourceObject: {fileID: 0} 518 | m_PrefabInternal: {fileID: 100100000} 519 | m_GameObject: {fileID: 1680671491621748} 520 | m_Enabled: 1 521 | m_EditorHideFlags: 0 522 | m_Script: {fileID: 11500000, guid: 835ec75000b58cf4aba592fb846a629b, type: 3} 523 | m_Name: 524 | m_EditorClassIdentifier: 525 | rotation: {x: 0, y: 0, z: 0} 526 | --- !u!114 &114733796681027892 527 | MonoBehaviour: 528 | m_ObjectHideFlags: 1 529 | m_CorrespondingSourceObject: {fileID: 0} 530 | m_PrefabInternal: {fileID: 100100000} 531 | m_GameObject: {fileID: 1948494897459636} 532 | m_Enabled: 1 533 | m_EditorHideFlags: 0 534 | m_Script: {fileID: 11500000, guid: 835ec75000b58cf4aba592fb846a629b, type: 3} 535 | m_Name: 536 | m_EditorClassIdentifier: 537 | rotation: {x: 0, y: 0, z: 0} 538 | --- !u!114 &114780491621147760 539 | MonoBehaviour: 540 | m_ObjectHideFlags: 1 541 | m_CorrespondingSourceObject: {fileID: 0} 542 | m_PrefabInternal: {fileID: 100100000} 543 | m_GameObject: {fileID: 1750859589963992} 544 | m_Enabled: 1 545 | m_EditorHideFlags: 0 546 | m_Script: {fileID: 11500000, guid: a6886d548845d24468b7160b582ef670, type: 3} 547 | m_Name: 548 | m_EditorClassIdentifier: 549 | -------------------------------------------------------------------------------- /Samples~/Rich Presence/Spinning Cubes.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ea9c4ccd537827b4582636e312bb0471 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 100100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.lachee.discordrpc", 3 | "version": "1.1.1", 4 | "displayName": "Discord Rich Presence", 5 | "description": "Implements the RPC Protocol for discord using Discord RPC CSharp", 6 | "unity": "2018.4", 7 | "documentationUrl": "https://lachee.github.io/discord-rpc-unity/", 8 | "changelogUrl": "https://github.com/Lachee/discord-rpc-unity/commits/master", 9 | "license": "MIT", 10 | "dependencies": { 11 | "com.unity.nuget.newtonsoft-json": "3.0.3" 12 | }, 13 | "samples": [ 14 | { 15 | "displayName": "Basic Usage", 16 | "description": "How to use the basic functionality of the discord manager", 17 | "path": "Samples~/Rich Presence" 18 | } 19 | ], 20 | "keywords": [ 21 | "Discord", 22 | "Rich Presence", 23 | "discord-rpc-csharp", 24 | "RPC" 25 | ], 26 | "author": { 27 | "name": "Lachee", 28 | "url": "https://lachee.dev" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 202c780ef5910ff41adf6199499b6aac 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------