This commit is contained in:
2026-08-17 18:18:13 +08:00
parent 5765f411e9
commit d2cd3b9fa8
188 changed files with 32929 additions and 2 deletions
+16
View File
@@ -109,6 +109,7 @@ AGENTS.md
Assets/Screenshots/
output/
tmp/
Artifacts/
Packages/
Assets/unity-mcp/
Assets/unity-mcp.meta
@@ -121,4 +122,19 @@ KNOWN_PITFALLS.md
Assets/~UnityDirMonSyncFile~84ae11425aac2204fbff8739574b1de6~
.idea/
GeneratedServers/
!GeneratedServers/
GeneratedServers/*
!GeneratedServers/ReplacedPerson.Core/
GeneratedServers/ReplacedPerson.Core/*
!GeneratedServers/ReplacedPerson.Core/ReplacedPerson.Core.csproj
!GeneratedServers/ReplacedPerson.ServerHost/
GeneratedServers/ReplacedPerson.ServerHost/*
!GeneratedServers/ReplacedPerson.ServerHost/ReplacedPerson.ServerHost.csproj
!GeneratedServers/ReplacedPerson.ServerHost/Program.cs
!GeneratedServers/ReplacedPerson.ServerHost/run-server.ps1
!GeneratedServers/ReplacedPerson.ServerHost/README.md
!GeneratedServers/ExampleGreedMod.Rules/
GeneratedServers/ExampleGreedMod.Rules/*
!GeneratedServers/ExampleGreedMod.Rules/ExampleGreedMod.Rules.csproj
!GeneratedServers/ExampleGreedMod.Rules/pack-mod.ps1
GeneratedModSdk/
+11
View File
@@ -0,0 +1,11 @@
{
"mcp": {
"servers": {
"funplay-unity": {
"type": "http",
"url": "http://127.0.0.1:20195/mcp",
"timeoutMs": 60000
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0ad6f74af7a0f854da0f9048dd81adfb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f70a9c8e61d85e441aa0b6b9913225d0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d964bdaad60b51a4e907a36afd24394f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 602d19931142c764ea5782f9dd0ccd2c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 007fd50a189dcdb429f59d7f079c94a7
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: c05ce0fa44f7afd469978f151d4c62c8
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 80d669f920616bc4f9dd1303ad50121f
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: c5f1908c966a51745b49cb415049745e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 706a0c3bc5d551e429c8b496520e21d4
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 4e89719c4b22a6a4691729532aeb3050
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: de93951858707514fbfa07fe0bb2371a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 6af7ddfbc8063a3438d79055d8ac52b7
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: c61c05c482f05a34e9de768086a2487e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 87c34b2fd134be94cb9f123a9a29d3b1
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: ff4a0b0f38cc1ae45aec94b3de0f5d98
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: bdae700573c0b914183ef4bfa3105761
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 1dbe085ce7e9c694b8082803d828032a
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: ce3a95c26eabd1f45b0f7fca6c356b5b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 13367afdf20e47f40b184469b5e36efd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: e4ea3d79e2ad0e243baca4b62e43287c
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- Noto Sans SC
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,93 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 09959c845ab86524f803da99d8fcad6a
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5efe820f5d0ee3648bd837bfe2d5fac1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+166
View File
@@ -0,0 +1,166 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace ReplacedPerson.Core
{
public sealed class ReplacedContentCatalog
{
public Dictionary<string, ReplacedCardDefinition> Cards { get; } = new(StringComparer.Ordinal);
public Dictionary<string, ReplacedEndCardDefinition> EndCards { get; } = new(StringComparer.Ordinal);
public Dictionary<string, ReplacedEnemyDefinition> Enemies { get; } = new(StringComparer.Ordinal);
public Dictionary<string, IReplacedPersonEffectResolver> EffectResolvers { get; } = new(StringComparer.Ordinal);
public ReplacedContentCatalog Clone()
{
var clone = new ReplacedContentCatalog();
foreach (var pair in Cards)
clone.Cards.Add(pair.Key, Clone(pair.Value));
foreach (var pair in EndCards)
clone.EndCards.Add(pair.Key, Clone(pair.Value));
foreach (var pair in Enemies)
clone.Enemies.Add(pair.Key, Clone(pair.Value));
foreach (var pair in EffectResolvers)
clone.EffectResolvers.Add(pair.Key, pair.Value);
return clone;
}
public string ComputeHash()
{
var text = new StringBuilder();
foreach (var card in Cards.Values.OrderBy(value => value.Id, StringComparer.Ordinal))
text.Append("C|").Append(card.Id).Append('|').Append(card.Name).Append('|').Append((int)card.EffectType)
.Append('|').Append(card.Cost).Append('|').Append(card.BaseValue).Append('|').Append(card.Factor)
.Append('|').Append(card.SpecialEffectId).Append('\n');
foreach (var card in EndCards.Values.OrderBy(value => value.Id, StringComparer.Ordinal))
text.Append("E|").Append(card.Id).Append('|').Append(card.Name).Append('|').Append(card.Cost).Append('|')
.Append(string.Join(",", card.SlotBase)).Append('|').Append(string.Join(",", card.SlotFactor)).Append('\n');
foreach (var enemy in Enemies.Values.OrderBy(value => value.Id, StringComparer.Ordinal))
text.Append("N|").Append(enemy.Id).Append('|').Append(enemy.Name).Append('|').Append(enemy.MaxHealth).Append('|')
.Append(string.Join(",", enemy.NormalDeck)).Append('|').Append(string.Join(",", enemy.EndDeck)).Append('\n');
foreach (var effect in EffectResolvers.OrderBy(value => value.Key, StringComparer.Ordinal))
text.Append("R|").Append(effect.Key).Append('|').Append(effect.Value.GetType().FullName).Append('\n');
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToString()));
var hash = new StringBuilder(bytes.Length * 2);
foreach (var value in bytes) hash.Append(value.ToString("x2"));
return hash.ToString();
}
private static ReplacedCardDefinition Clone(ReplacedCardDefinition value) => new()
{
Id = value.Id, Name = value.Name, Description = value.Description, EffectType = value.EffectType,
Cost = value.Cost, BaseValue = value.BaseValue, Factor = value.Factor, SpecialEffectId = value.SpecialEffectId
};
private static ReplacedEndCardDefinition Clone(ReplacedEndCardDefinition value) => new()
{
Id = value.Id, Name = value.Name, Description = value.Description, Cost = value.Cost,
SlotBase = (int[])value.SlotBase.Clone(), SlotFactor = (int[])value.SlotFactor.Clone()
};
private static ReplacedEnemyDefinition Clone(ReplacedEnemyDefinition value) => new()
{
Id = value.Id, Name = value.Name, MaxHealth = value.MaxHealth,
NormalDeck = new List<string>(value.NormalDeck), EndDeck = new List<string>(value.EndDeck),
RewardIds = new List<string>(value.RewardIds)
};
}
public static class ReplacedBuiltInContent
{
public static ReplacedContentCatalog Create()
{
var result = new ReplacedContentCatalog();
AddCard(result, "attack.kill", "杀", ReplacedEffectType.Attack, 2, 2, 2, "直截了当的低费进攻。");
AddCard(result, "attack.elbow", "肘击", ReplacedEffectType.Attack, 3, 3, 2, "稳定进攻,适合试探对位。");
AddCard(result, "attack.break", "破局", ReplacedEffectType.Attack, 4, 3, 3, "高波动进攻。");
AddCard(result, "attack.iron", "铁山靠", ReplacedEffectType.Attack, 5, 5, 2, "高始数压制。");
AddCard(result, "attack.burst", "寸劲", ReplacedEffectType.Attack, 6, 4, 3, "高上限连击。");
AddCard(result, "attack.palm", "黄经极世掌", ReplacedEffectType.Attack, 7, 5, 3, "终盘重击。");
AddCard(result, "dodge.flash", "闪", ReplacedEffectType.Dodge, 2, 4, 2, "闪避成功后获得额外抽牌。");
AddCard(result, "dodge.phase", "虚化", ReplacedEffectType.Dodge, 3, 5, 3, "与反击合成可获得防御。");
AddCard(result, "counter.stance", "架势", ReplacedEffectType.Counter, 1, 2, 2, "低费反击,用来试探攻击并为高费反制保留 COST。");
AddCard(result, "counter.blood", "以血蒙眼", ReplacedEffectType.Counter, 3, 5, 2, "反制进攻并返还伤害。");
AddCard(result, "counter.instant", "一瞬千击", ReplacedEffectType.Counter, 5, 7, 3, "高费反击。");
AddCard(result, "greed.snob", "势利眼", ReplacedEffectType.Dodge, 3, 2, 2, "贪婪的闪避牌。", "greed.draw");
AddCard(result, "greed.turn-profit", "扭亏为盈", ReplacedEffectType.Counter, 4, 3, 3, "反击成功时恢复 COST。", "greed.refund");
AddCard(result, "greed.wage-cut", "降薪", ReplacedEffectType.Attack, 4, 2, 2, "命中后削减对方 COST。", "greed.wage-cut");
AddEnd(result, "end.inherit", "继承", 0, new[] { 0, 0, 0 }, new[] { 0, 0, 0 }, "将左侧普通牌效果继承到三个槽位。");
AddEnd(result, "end.ledger", "账本", 1, new[] { 1, 0, 1 }, new[] { 0, 1, 0 }, "稳定首尾槽。");
AddEnd(result, "end.severance", "断契", 1, new[] { 0, 2, 0 }, new[] { 1, 0, 1 }, "强化中槽始数与边槽因数。");
AddEnd(result, "end.black", "黑色遗嘱", 2, new[] { 1, 1, 1 }, new[] { 1, 0, 1 }, "全面强化,适合孤注一掷。");
var playerDeck = CreateStarterDeck();
result.Enemies.Add("greed.tutorial", new ReplacedEnemyDefinition
{
Id = "greed.tutorial", Name = "贪婪·影子", MaxHealth = 20,
NormalDeck = new List<string> { "attack.kill", "attack.kill", "attack.elbow", "attack.elbow", "dodge.flash", "dodge.flash", "counter.blood", "counter.blood", "greed.snob", "greed.snob", "greed.turn-profit", "greed.wage-cut" },
EndDeck = new List<string>(playerDeck.EndCards), RewardIds = new List<string> { "ornament.gold-eye" }
});
result.Enemies.Add("greed.full", new ReplacedEnemyDefinition
{
Id = "greed.full", Name = "贪婪·葛朗台", MaxHealth = 30,
NormalDeck = new List<string> { "attack.break", "attack.break", "attack.iron", "attack.iron", "dodge.phase", "dodge.phase", "greed.snob", "greed.snob", "greed.turn-profit", "greed.turn-profit", "greed.wage-cut", "greed.wage-cut" },
EndDeck = new List<string>(playerDeck.EndCards), RewardIds = new List<string> { "ornament.gold-eye", "ornament.gold-chain", "ornament.worn-suit", "greed.snob", "greed.turn-profit", "greed.wage-cut" }
});
return result;
}
public static ReplacedDeck CreateStarterDeck() => new()
{
NormalCards = new List<string>
{
"counter.stance", "counter.stance", "counter.blood", "counter.blood", "counter.instant", "counter.instant",
"dodge.flash", "dodge.flash", "attack.kill", "attack.kill", "attack.elbow", "attack.elbow"
},
EndCards = new List<string> { "end.inherit", "end.ledger", "end.severance", "end.black" }
};
private static void AddCard(ReplacedContentCatalog target, string id, string name, ReplacedEffectType type,
int cost, int baseValue, int factor, string description, string special = "")
{
target.Cards.Add(id, new ReplacedCardDefinition
{
Id = id, Name = name, EffectType = type, Cost = cost, BaseValue = baseValue,
Factor = factor, Description = description, SpecialEffectId = special
});
}
private static void AddEnd(ReplacedContentCatalog target, string id, string name, int cost,
int[] slotBase, int[] slotFactor, string description)
{
target.EndCards.Add(id, new ReplacedEndCardDefinition
{
Id = id, Name = name, Cost = cost, SlotBase = slotBase, SlotFactor = slotFactor, Description = description
});
}
}
public static class ReplacedDeckValidator
{
public static IReadOnlyList<string> Validate(ReplacedDeck deck, ReplacedContentCatalog catalog)
{
var errors = new List<string>();
if (deck == null) return new[] { "deck.null" };
if (deck.NormalCards.Count != 12) errors.Add("deck.normal.count");
if (deck.EndCards.Count != 4) errors.Add("deck.end.count");
foreach (var group in deck.NormalCards.GroupBy(value => value, StringComparer.Ordinal))
{
if (!catalog.Cards.ContainsKey(group.Key)) errors.Add("deck.normal.unknown:" + group.Key);
if (group.Count() > 2) errors.Add("deck.normal.duplicate:" + group.Key);
}
foreach (var group in deck.EndCards.GroupBy(value => value, StringComparer.Ordinal))
{
if (!catalog.EndCards.ContainsKey(group.Key)) errors.Add("deck.end.unknown:" + group.Key);
if (group.Count() > 1) errors.Add("deck.end.duplicate:" + group.Key);
}
return errors;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa02820e6d30cc849adac4b52dcd52f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+220
View File
@@ -0,0 +1,220 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace ReplacedPerson.Core
{
public enum ReplacedEffectType
{
Attack = 0,
Dodge = 1,
Counter = 2
}
public enum ReplacedMatchPhase
{
AwaitingLead = 0,
AwaitingResponse = 1,
Resolving = 2,
Completed = 3
}
public enum ReplacedWinner
{
None = -1,
PlayerOne = 0,
PlayerTwo = 1,
Draw = 2
}
[Serializable]
public sealed class ReplacedCardDefinition
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public ReplacedEffectType EffectType { get; set; }
public int Cost { get; set; }
public int BaseValue { get; set; }
public int Factor { get; set; }
public string SpecialEffectId { get; set; } = string.Empty;
}
[Serializable]
public sealed class ReplacedEndCardDefinition
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int Cost { get; set; }
public int[] SlotBase { get; set; } = new int[3];
public int[] SlotFactor { get; set; } = new int[3];
}
[Serializable]
public sealed class ReplacedEnemyDefinition
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int MaxHealth { get; set; } = 30;
public List<string> NormalDeck { get; set; } = new();
public List<string> EndDeck { get; set; } = new();
public List<string> RewardIds { get; set; } = new();
}
[Serializable]
public sealed class ReplacedDeck
{
public List<string> NormalCards { get; set; } = new();
public List<string> EndCards { get; set; } = new();
public ReplacedDeck Clone() => new()
{
NormalCards = new List<string>(NormalCards),
EndCards = new List<string>(EndCards)
};
}
[Serializable]
public sealed class ReplacedTurnSubmission
{
public bool Pass { get; set; }
public bool DiscardEndOnPass { get; set; }
public List<string> NormalCardIds { get; set; } = new();
public List<string> EndCardIds { get; set; } = new();
public int RerollSlot { get; set; } = -1;
public ReplacedTurnSubmission Clone() => new()
{
Pass = Pass,
DiscardEndOnPass = DiscardEndOnPass,
NormalCardIds = new List<string>(NormalCardIds),
EndCardIds = new List<string>(EndCardIds),
RerollSlot = RerollSlot
};
}
[Serializable]
public sealed class ReplacedMatchCommand
{
public long Sequence { get; set; }
public string IdempotencyToken { get; set; } = string.Empty;
public int PlayerIndex { get; set; }
public ReplacedTurnSubmission Submission { get; set; } = new();
public ReplacedMatchCommand Clone() => new()
{
Sequence = Sequence,
IdempotencyToken = IdempotencyToken,
PlayerIndex = PlayerIndex,
Submission = Submission.Clone()
};
}
[Serializable]
public sealed class ReplacedPlayerState
{
public string PlayerId { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public int Health { get; set; } = 30;
public int MaxHealth { get; set; } = 30;
public int Cost { get; set; } = 10;
public int Defense { get; set; }
public int BonusNormalDraw { get; set; }
public bool GuaranteedDodge { get; set; }
public bool SkipNextEndDraw { get; set; }
public int SpentThisRound { get; set; }
public int EffectiveEffectsThisRound { get; set; }
public int DamageThisRound { get; set; }
public List<string> NormalDrawPile { get; set; } = new();
public List<string> NormalDiscard { get; set; } = new();
public List<string> NormalHand { get; set; } = new();
public List<string> EndDrawPile { get; set; } = new();
public List<string> EndDiscard { get; set; } = new();
public List<string> EndHand { get; set; } = new();
public ReplacedPlayerState Clone() => new()
{
PlayerId = PlayerId,
DisplayName = DisplayName,
Health = Health,
MaxHealth = MaxHealth,
Cost = Cost,
Defense = Defense,
BonusNormalDraw = BonusNormalDraw,
GuaranteedDodge = GuaranteedDodge,
SkipNextEndDraw = SkipNextEndDraw,
SpentThisRound = SpentThisRound,
EffectiveEffectsThisRound = EffectiveEffectsThisRound,
DamageThisRound = DamageThisRound,
NormalDrawPile = new List<string>(NormalDrawPile),
NormalDiscard = new List<string>(NormalDiscard),
NormalHand = new List<string>(NormalHand),
EndDrawPile = new List<string>(EndDrawPile),
EndDiscard = new List<string>(EndDiscard),
EndHand = new List<string>(EndHand)
};
}
[Serializable]
public sealed class ReplacedSlotResolution
{
public int SlotIndex { get; set; }
public ReplacedEffectType PlayerOneType { get; set; }
public ReplacedEffectType PlayerTwoType { get; set; }
public int PlayerOneRoll { get; set; }
public int PlayerTwoRoll { get; set; }
public int PlayerOneValue { get; set; }
public int PlayerTwoValue { get; set; }
public int DamageToPlayerOne { get; set; }
public int DamageToPlayerTwo { get; set; }
public bool PlayerOneRerolled { get; set; }
public bool PlayerTwoRerolled { get; set; }
public string Summary { get; set; } = string.Empty;
}
[Serializable]
public sealed class ReplacedMatchState
{
public int Round { get; set; }
public int LeadPlayer { get; set; }
public int InitiativeRoll { get; set; }
public ReplacedMatchPhase Phase { get; set; }
public ReplacedWinner Winner { get; set; } = ReplacedWinner.None;
public bool IsLastStand { get; set; }
public bool LastStandUsed { get; set; }
public ReplacedPlayerState[] Players { get; set; } = Array.Empty<ReplacedPlayerState>();
public ReplacedTurnSubmission?[] Submissions { get; set; } = new ReplacedTurnSubmission?[2];
public List<ReplacedSlotResolution> LastResolution { get; set; } = new();
public List<string> Log { get; set; } = new();
public long[] LastAcceptedSequence { get; set; } = new long[2];
public string ContentHash { get; set; } = string.Empty;
public ReplacedMatchState Clone() => new()
{
Round = Round,
LeadPlayer = LeadPlayer,
InitiativeRoll = InitiativeRoll,
Phase = Phase,
Winner = Winner,
IsLastStand = IsLastStand,
LastStandUsed = LastStandUsed,
Players = new[] { Players[0].Clone(), Players[1].Clone() },
Submissions = new ReplacedTurnSubmission?[] { Submissions[0]?.Clone(), Submissions[1]?.Clone() },
LastResolution = new List<ReplacedSlotResolution>(LastResolution),
Log = new List<string>(Log),
LastAcceptedSequence = (long[])LastAcceptedSequence.Clone(),
ContentHash = ContentHash
};
}
public sealed class ReplacedCommandResult
{
public bool Accepted { get; set; }
public bool Duplicate { get; set; }
public string ErrorCode { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public string StateHash { get; set; } = string.Empty;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7a20d95a3133394cb808caef94ec04c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,610 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace ReplacedPerson.Core
{
public sealed class ReplacedMatchEngine
{
private sealed class PreparedSlot
{
public ReplacedEffectType Type;
public int BaseValue;
public int Factor;
public bool DrawAndRecover;
public bool BonusDamage;
public bool Reflect;
public bool AddDefense;
public bool GuaranteedDodge;
public readonly List<string> SpecialEffects = new();
}
private readonly HashSet<string> _acceptedTokens = new(StringComparer.Ordinal);
private readonly List<ReplacedMatchCommand> _replay = new();
public ReplacedContentCatalog Content { get; }
public ReplacedMatchState State { get; }
public Pcg32 Random { get; }
public IReadOnlyList<ReplacedMatchCommand> Replay => _replay;
public ReplacedMatchEngine(ReplacedContentCatalog content, ReplacedDeck playerOneDeck,
ReplacedDeck playerTwoDeck, ulong seed, string playerOneName = "玩家", string playerTwoName = "贪婪")
{
Content = (content ?? throw new ArgumentNullException(nameof(content))).Clone();
ValidateDeck(playerOneDeck, nameof(playerOneDeck));
ValidateDeck(playerTwoDeck, nameof(playerTwoDeck));
Random = new Pcg32(seed);
State = new ReplacedMatchState
{
Round = 0,
Players = new[]
{
CreatePlayer("p1", playerOneName, playerOneDeck),
CreatePlayer("p2", playerTwoName, playerTwoDeck)
},
ContentHash = Content.ComputeHash()
};
Random.Shuffle(State.Players[0].NormalDrawPile);
Random.Shuffle(State.Players[0].EndDrawPile);
Random.Shuffle(State.Players[1].NormalDrawPile);
Random.Shuffle(State.Players[1].EndDrawPile);
BeginRound(initial: true);
}
public ReplacedCommandResult Apply(ReplacedMatchCommand command)
{
if (command == null)
return Reject("command.null", "指令不能为空。");
if (State.Phase == ReplacedMatchPhase.Completed)
return Reject("match.completed", "对局已经结束。");
if (command.PlayerIndex is < 0 or > 1)
return Reject("player.invalid", "玩家索引无效。");
if (string.IsNullOrWhiteSpace(command.IdempotencyToken))
return Reject("token.empty", "幂等令牌不能为空。");
if (_acceptedTokens.Contains(command.IdempotencyToken))
return new ReplacedCommandResult { Accepted = true, Duplicate = true, Message = "duplicate", StateHash = ComputeStateHash() };
if (command.Sequence <= State.LastAcceptedSequence[command.PlayerIndex])
return Reject("sequence.stale", "指令序号必须严格递增。");
var expectedPlayer = State.Phase == ReplacedMatchPhase.AwaitingLead
? State.LeadPlayer
: 1 - State.LeadPlayer;
if (command.PlayerIndex != expectedPlayer)
return Reject("turn.not-yours", "尚未轮到该玩家提交。");
var validation = ValidateSubmission(command.PlayerIndex, command.Submission);
if (validation != null)
return Reject(validation.Value.code, validation.Value.message);
LockSubmission(command.PlayerIndex, command.Submission);
State.LastAcceptedSequence[command.PlayerIndex] = command.Sequence;
_acceptedTokens.Add(command.IdempotencyToken);
_replay.Add(command.Clone());
State.Log.Add($"R{State.Round} {State.Players[command.PlayerIndex].DisplayName} 锁定出牌");
if (State.Phase == ReplacedMatchPhase.AwaitingLead)
{
State.Phase = ReplacedMatchPhase.AwaitingResponse;
}
else
{
State.Phase = ReplacedMatchPhase.Resolving;
ResolveRound();
}
return new ReplacedCommandResult
{
Accepted = true,
Message = "accepted",
StateHash = ComputeStateHash()
};
}
public string ComputeStateHash() => ReplacedStateHasher.Compute(State, Random);
private void BeginRound(bool initial)
{
State.Round++;
State.Submissions = new ReplacedTurnSubmission?[2];
for (var i = 0; i < 2; i++)
{
var player = State.Players[i];
player.Defense = 0;
player.SpentThisRound = 0;
player.EffectiveEffectsThisRound = 0;
player.DamageThisRound = 0;
if (State.IsLastStand)
{
DiscardHands(player);
DrawNormal(player, 4);
DrawEnd(player, 2);
}
else
{
DrawNormal(player, initial ? 4 : 2 + player.BonusNormalDraw);
player.BonusNormalDraw = 0;
if (initial || !player.SkipNextEndDraw)
DrawEnd(player, 1);
player.SkipNextEndDraw = false;
}
}
State.InitiativeRoll = Random.RollD6();
State.LeadPlayer = State.InitiativeRoll % 2 == 1 ? 0 : 1;
State.Phase = ReplacedMatchPhase.AwaitingLead;
State.Log.Add($"R{State.Round} 先手骰={State.InitiativeRoll}{State.Players[State.LeadPlayer].DisplayName}先手");
}
private (string code, string message)? ValidateSubmission(int playerIndex, ReplacedTurnSubmission? submission)
{
if (submission == null)
return ("submission.null", "提交内容不能为空。");
if (submission.RerollSlot is < -1 or > 2)
return ("reroll.invalid", "重掷槽必须为 -1 到 2。");
var player = State.Players[playerIndex];
if (submission.Pass)
{
if (submission.NormalCardIds.Count != 0 || submission.EndCardIds.Count != 0)
return ("pass.with-cards", "跳过时不能同时提交卡牌。");
return null;
}
var requiredNormal = State.IsLastStand ? 4 : 1;
var requiredEnd = State.IsLastStand ? 2 : 1;
if (submission.NormalCardIds.Count < requiredNormal || submission.NormalCardIds.Count > 4)
return ("normal.count", State.IsLastStand ? "孤注一掷必须提交 4 张普通牌。" : "必须提交 1 到 4 张普通牌。");
if (submission.EndCardIds.Count != requiredEnd)
return ("end.count", State.IsLastStand ? "孤注一掷必须提交主、副两张结束牌。" : "必须提交 1 张结束牌。");
if (!ContainsAll(player.NormalHand, submission.NormalCardIds))
return ("normal.not-in-hand", "普通牌不在手牌中。");
if (!ContainsAll(player.EndHand, submission.EndCardIds))
return ("end.not-in-hand", "结束牌不在手牌中。");
var cost = 0;
foreach (var id in submission.NormalCardIds)
{
if (!Content.Cards.TryGetValue(id, out var card))
return ("normal.unknown", "未知普通牌:" + id);
cost += card.Cost;
}
foreach (var id in submission.EndCardIds)
{
if (!Content.EndCards.TryGetValue(id, out var card))
return ("end.unknown", "未知结束牌:" + id);
cost += card.Cost;
}
if (cost > player.Cost)
return ("cost.insufficient", $"COST 不足:需要 {cost},当前 {player.Cost}。");
return null;
}
private void LockSubmission(int playerIndex, ReplacedTurnSubmission submission)
{
var player = State.Players[playerIndex];
var copy = submission.Clone();
State.Submissions[playerIndex] = copy;
if (copy.Pass)
{
player.SkipNextEndDraw = true;
if (copy.DiscardEndOnPass)
{
player.EndDiscard.AddRange(player.EndHand);
player.EndHand.Clear();
}
return;
}
var spent = 0;
foreach (var id in copy.NormalCardIds)
{
player.NormalHand.Remove(id);
spent += Content.Cards[id].Cost;
}
foreach (var id in copy.EndCardIds)
{
player.EndHand.Remove(id);
spent += Content.EndCards[id].Cost;
}
player.Cost -= spent;
player.SpentThisRound = spent;
}
private void ResolveRound()
{
State.LastResolution.Clear();
var prepared = new[]
{
PrepareSlots(State.Submissions[0]),
PrepareSlots(State.Submissions[1])
};
for (var slotIndex = 0; slotIndex < 3; slotIndex++)
ResolveSlot(slotIndex, prepared[0][slotIndex], prepared[1][slotIndex]);
ApplyRoundRecoveryAndDiscard();
if (State.IsLastStand)
{
CompleteLastStand();
return;
}
var oneDead = State.Players[0].Health <= 0;
var twoDead = State.Players[1].Health <= 0;
if (oneDead && twoDead && !State.LastStandUsed)
{
State.LastStandUsed = true;
State.IsLastStand = true;
State.Players[0].Health = 1;
State.Players[1].Health = 1;
State.Log.Add("双方同时归零,进入孤注一掷");
BeginRound(initial: false);
return;
}
if (oneDead || twoDead)
{
Complete(oneDead ? ReplacedWinner.PlayerTwo : ReplacedWinner.PlayerOne);
return;
}
BeginRound(initial: false);
}
private PreparedSlot?[] PrepareSlots(ReplacedTurnSubmission? submission)
{
var result = new PreparedSlot?[3];
if (submission == null || submission.Pass)
return result;
var primaryEnd = Content.EndCards[submission.EndCardIds[0]];
for (var i = 0; i < submission.NormalCardIds.Count; i++)
{
var card = Content.Cards[submission.NormalCardIds[i]];
var slot = Math.Min(i, 2);
var next = new PreparedSlot
{
Type = card.EffectType,
BaseValue = card.BaseValue,
Factor = card.Factor
};
if (!string.IsNullOrWhiteSpace(card.SpecialEffectId))
next.SpecialEffects.Add(card.SpecialEffectId);
result[slot] = result[slot] == null ? next : Fuse(result[slot]!, next);
}
for (var i = 0; i < result.Length; i++)
{
if (result[i] == null)
continue;
result[i]!.BaseValue += primaryEnd.SlotBase[i];
result[i]!.Factor += primaryEnd.SlotFactor[i];
}
if (State.IsLastStand && submission.EndCardIds.Count > 1 && result[0] != null)
{
var secondary = Content.EndCards[submission.EndCardIds[1]];
result[0]!.BaseValue += secondary.SlotBase[0];
result[0]!.Factor += secondary.SlotFactor[0];
}
return result;
}
private static PreparedSlot Fuse(PreparedSlot left, PreparedSlot right)
{
var result = new PreparedSlot
{
Type = left.Type == ReplacedEffectType.Attack || right.Type == ReplacedEffectType.Attack
? ReplacedEffectType.Attack
: left.Type == ReplacedEffectType.Counter || right.Type == ReplacedEffectType.Counter
? ReplacedEffectType.Counter
: ReplacedEffectType.Dodge,
BaseValue = left.BaseValue + right.BaseValue,
Factor = left.Factor + right.Factor,
DrawAndRecover = left.DrawAndRecover || right.DrawAndRecover,
BonusDamage = left.BonusDamage || right.BonusDamage,
Reflect = left.Reflect || right.Reflect,
AddDefense = left.AddDefense || right.AddDefense,
GuaranteedDodge = left.GuaranteedDodge || right.GuaranteedDodge
};
result.SpecialEffects.AddRange(left.SpecialEffects);
result.SpecialEffects.AddRange(right.SpecialEffects);
var a = left.Type;
var b = right.Type;
if (a == ReplacedEffectType.Attack && b == ReplacedEffectType.Attack)
{
result.BaseValue = (int)Math.Round(result.BaseValue * 1.5, MidpointRounding.AwayFromZero);
result.Factor = (int)Math.Round(result.Factor * 1.5, MidpointRounding.AwayFromZero);
}
else if (IsPair(a, b, ReplacedEffectType.Attack, ReplacedEffectType.Dodge)) result.DrawAndRecover = true;
else if (IsPair(a, b, ReplacedEffectType.Attack, ReplacedEffectType.Counter)) result.BonusDamage = true;
else if (a == ReplacedEffectType.Counter && b == ReplacedEffectType.Counter) result.Reflect = true;
else if (IsPair(a, b, ReplacedEffectType.Dodge, ReplacedEffectType.Counter)) result.AddDefense = true;
else if (a == ReplacedEffectType.Dodge && b == ReplacedEffectType.Dodge) result.GuaranteedDodge = true;
return result;
}
private void ResolveSlot(int slotIndex, PreparedSlot? one, PreparedSlot? two)
{
if (one == null && two == null)
return;
var submissionOne = State.Submissions[0]!;
var submissionTwo = State.Submissions[1]!;
var oneRerolled = false;
var twoRerolled = false;
var oneRoll = one == null ? 0 : RollWithMarkedReroll(submissionOne, slotIndex, out oneRerolled);
var twoRoll = two == null ? 0 : RollWithMarkedReroll(submissionTwo, slotIndex, out twoRerolled);
var oneValue = one == null ? 0 : one.BaseValue + one.Factor * oneRoll;
var twoValue = two == null ? 0 : two.BaseValue + two.Factor * twoRoll;
var tieGuard = 0;
while (one != null && two != null && oneValue == twoValue && tieGuard++ < 32)
{
oneRoll = Random.RollD6();
twoRoll = Random.RollD6();
oneValue = one.BaseValue + one.Factor * oneRoll;
twoValue = two.BaseValue + two.Factor * twoRoll;
}
var resolution = new ReplacedSlotResolution
{
SlotIndex = slotIndex,
PlayerOneType = one?.Type ?? ReplacedEffectType.Dodge,
PlayerTwoType = two?.Type ?? ReplacedEffectType.Dodge,
PlayerOneRoll = oneRoll,
PlayerTwoRoll = twoRoll,
PlayerOneValue = oneValue,
PlayerTwoValue = twoValue,
PlayerOneRerolled = oneRerolled,
PlayerTwoRerolled = twoRerolled
};
if (one == null)
ApplyUnopposed(1, two!, twoValue, resolution);
else if (two == null)
ApplyUnopposed(0, one, oneValue, resolution);
else if (oneValue == twoValue)
resolution.Summary = "连续平点,槽位无效";
else
ApplyOpposed(oneValue > twoValue ? 0 : 1, one, two, oneValue, twoValue, resolution);
State.LastResolution.Add(resolution);
State.Log.Add($"槽{slotIndex + 1}: {resolution.Summary}");
}
private void ApplyUnopposed(int owner, PreparedSlot slot, int value, ReplacedSlotResolution resolution)
{
if (slot.Type == ReplacedEffectType.Attack)
{
var damage = Math.Min(8, value);
DealDamage(owner, 1 - owner, damage, resolution);
ApplySuccess(owner, slot, resolution);
resolution.Summary = $"{State.Players[owner].DisplayName} 无对位进攻 {damage}";
}
else
{
ApplySuccess(owner, slot, resolution);
resolution.Summary = $"{State.Players[owner].DisplayName} 无对位效果发动";
}
}
private void ApplyOpposed(int winner, PreparedSlot one, PreparedSlot two, int oneValue, int twoValue,
ReplacedSlotResolution resolution)
{
var loser = 1 - winner;
var winSlot = winner == 0 ? one : two;
var loseSlot = winner == 0 ? two : one;
var difference = Math.Abs(oneValue - twoValue);
if (winSlot.Type == ReplacedEffectType.Attack)
{
if (loseSlot.Type == ReplacedEffectType.Dodge && State.Players[loser].GuaranteedDodge)
{
State.Players[loser].GuaranteedDodge = false;
resolution.Summary = $"{State.Players[loser].DisplayName} 消耗必定闪避";
return;
}
var damage = Math.Min(8, difference);
DealDamage(winner, loser, damage, resolution);
ApplySuccess(winner, winSlot, resolution);
resolution.Summary = $"{State.Players[winner].DisplayName} 进攻胜出 {damage}";
return;
}
if (winSlot.Type == ReplacedEffectType.Counter && loseSlot.Type == ReplacedEffectType.Attack)
{
var damage = Math.Min(6, difference);
DealDamage(winner, loser, damage, resolution);
ApplySuccess(winner, winSlot, resolution);
resolution.Summary = $"{State.Players[winner].DisplayName} 反击 {damage}";
return;
}
if (winSlot.Type == ReplacedEffectType.Dodge && loseSlot.Type == ReplacedEffectType.Attack)
{
State.Players[winner].BonusNormalDraw++;
ApplySuccess(winner, winSlot, resolution);
resolution.Summary = $"{State.Players[winner].DisplayName} 闪避并获得额外抽牌";
return;
}
ApplySuccess(winner, winSlot, resolution);
resolution.Summary = $"{State.Players[winner].DisplayName} 对位胜出";
}
private void ApplySuccess(int owner, PreparedSlot slot, ReplacedSlotResolution resolution)
{
var player = State.Players[owner];
player.EffectiveEffectsThisRound++;
if (slot.DrawAndRecover)
{
DrawNormal(player, 1);
player.Cost = Math.Min(10, player.Cost + 1);
}
if (slot.BonusDamage)
DealDamage(owner, 1 - owner, 3, resolution);
if (slot.AddDefense)
player.Defense += 2;
if (slot.GuaranteedDodge)
player.GuaranteedDodge = true;
if (slot.Reflect && player.DamageThisRound > 0)
DealDamage(owner, 1 - owner, Math.Min(5, (int)Math.Ceiling(player.DamageThisRound * 0.5)), resolution);
foreach (var special in slot.SpecialEffects)
{
switch (special)
{
case "greed.draw": DrawNormal(player, 1); break;
case "greed.refund": player.Cost = Math.Min(10, player.Cost + 1); break;
case "greed.wage-cut": State.Players[1 - owner].Cost = Math.Max(0, State.Players[1 - owner].Cost - 2); break;
default:
if (Content.EffectResolvers.TryGetValue(special, out var resolver))
{
var effectResult = resolver.Resolve(new ReplacedEffectContext
{
EffectId = special,
OwnerIndex = owner,
State = State,
Value = 0
});
player.Cost = Math.Min(10, Math.Max(0, player.Cost + effectResult.CostDelta));
DrawNormal(player, Math.Max(0, effectResult.DrawDelta));
if (effectResult.BonusDamage > 0) DealDamage(owner, 1 - owner, effectResult.BonusDamage, resolution);
if (!string.IsNullOrWhiteSpace(effectResult.Log)) State.Log.Add(effectResult.Log);
}
break;
}
}
}
private void DealDamage(int source, int target, int amount, ReplacedSlotResolution resolution)
{
if (amount <= 0) return;
var targetState = State.Players[target];
var afterDefense = Math.Max(0, amount - targetState.Defense);
if (State.IsLastStand)
{
State.Players[source].DamageThisRound += afterDefense;
}
else
{
targetState.Health -= afterDefense;
State.Players[source].DamageThisRound += afterDefense;
}
if (target == 0) resolution.DamageToPlayerOne += afterDefense;
else resolution.DamageToPlayerTwo += afterDefense;
}
private int RollWithMarkedReroll(ReplacedTurnSubmission submission, int slotIndex, out bool rerolled)
{
var roll = Random.RollD6();
rerolled = submission.RerollSlot == slotIndex && roll <= 2;
return rerolled ? Random.RollD6() : roll;
}
private void ApplyRoundRecoveryAndDiscard()
{
for (var i = 0; i < 2; i++)
{
var player = State.Players[i];
player.Cost = Math.Min(10, player.Cost + 1 + player.SpentThisRound / 2);
var submission = State.Submissions[i];
if (submission == null || submission.Pass) continue;
player.NormalDiscard.AddRange(submission.NormalCardIds);
player.EndDiscard.AddRange(submission.EndCardIds);
}
}
private void CompleteLastStand()
{
var first = State.Players[0];
var second = State.Players[1];
if (first.DamageThisRound != second.DamageThisRound)
Complete(first.DamageThisRound > second.DamageThisRound ? ReplacedWinner.PlayerOne : ReplacedWinner.PlayerTwo);
else if (first.EffectiveEffectsThisRound != second.EffectiveEffectsThisRound)
Complete(first.EffectiveEffectsThisRound > second.EffectiveEffectsThisRound ? ReplacedWinner.PlayerOne : ReplacedWinner.PlayerTwo);
else
Complete(Random.RollD6() % 2 == 1 ? ReplacedWinner.PlayerOne : ReplacedWinner.PlayerTwo);
}
private void Complete(ReplacedWinner winner)
{
State.Winner = winner;
State.Phase = ReplacedMatchPhase.Completed;
State.Log.Add("对局结束:" + winner);
}
private void DrawNormal(ReplacedPlayerState player, int count)
{
for (var i = 0; i < count && player.NormalHand.Count < 9; i++)
{
RecycleIfNeeded(player.NormalDrawPile, player.NormalDiscard);
if (player.NormalDrawPile.Count == 0) return;
var last = player.NormalDrawPile.Count - 1;
player.NormalHand.Add(player.NormalDrawPile[last]);
player.NormalDrawPile.RemoveAt(last);
}
}
private void DrawEnd(ReplacedPlayerState player, int count)
{
for (var i = 0; i < count; i++)
{
RecycleIfNeeded(player.EndDrawPile, player.EndDiscard);
if (player.EndDrawPile.Count == 0) return;
var last = player.EndDrawPile.Count - 1;
player.EndHand.Add(player.EndDrawPile[last]);
player.EndDrawPile.RemoveAt(last);
}
}
private void RecycleIfNeeded(List<string> draw, List<string> discard)
{
if (draw.Count > 0 || discard.Count == 0) return;
draw.AddRange(discard);
discard.Clear();
Random.Shuffle(draw);
}
private static bool ContainsAll(List<string> hand, List<string> requested)
{
var available = new List<string>(hand);
foreach (var id in requested)
if (!available.Remove(id)) return false;
return true;
}
private static bool IsPair(ReplacedEffectType a, ReplacedEffectType b, ReplacedEffectType x, ReplacedEffectType y) =>
(a == x && b == y) || (a == y && b == x);
private static ReplacedPlayerState CreatePlayer(string id, string name, ReplacedDeck deck) => new()
{
PlayerId = id,
DisplayName = name,
NormalDrawPile = new List<string>(deck.NormalCards),
EndDrawPile = new List<string>(deck.EndCards)
};
private void ValidateDeck(ReplacedDeck deck, string parameter)
{
var errors = ReplacedDeckValidator.Validate(deck, Content);
if (errors.Count > 0)
throw new ArgumentException(string.Join(", ", errors), parameter);
}
private static void DiscardHands(ReplacedPlayerState player)
{
player.NormalDiscard.AddRange(player.NormalHand);
player.NormalHand.Clear();
player.EndDiscard.AddRange(player.EndHand);
player.EndHand.Clear();
}
private ReplacedCommandResult Reject(string code, string message) => new()
{
Accepted = false,
ErrorCode = code,
Message = message,
StateHash = ComputeStateHash()
};
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47d95b984ad7b2546856f977713e4853
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+169
View File
@@ -0,0 +1,169 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace ReplacedPerson.Core
{
public interface IReplacedPersonMod
{
string Id { get; }
string Version { get; }
void Register(IReplacedPersonRegistry registry);
}
public interface IReplacedPersonRegistry
{
void RegisterCard(ReplacedCardDefinition definition);
void RegisterEndCard(ReplacedEndCardDefinition definition);
void RegisterEnemy(ReplacedEnemyDefinition definition);
void RegisterReward(string id, string displayName);
void RegisterStory(string id, string title, string body);
void RegisterEffectResolver(string effectId, IReplacedPersonEffectResolver resolver);
void RegisterCommand(string path, Func<IReadOnlyList<string>, string> handler);
}
public interface IReplacedPersonEffectResolver
{
ReplacedEffectResult Resolve(ReplacedEffectContext context);
}
public sealed class ReplacedEffectContext
{
public string EffectId { get; set; } = string.Empty;
public int OwnerIndex { get; set; }
public int OpponentIndex => 1 - OwnerIndex;
public int Value { get; set; }
public ReplacedMatchState State { get; set; } = null!;
}
public sealed class ReplacedEffectResult
{
public int BonusDamage { get; set; }
public int CostDelta { get; set; }
public int DrawDelta { get; set; }
public string Log { get; set; } = string.Empty;
}
[Serializable]
public sealed class ReplacedPersonModFile
{
public string Path { get; set; } = string.Empty;
public string Sha256 { get; set; } = string.Empty;
}
[Serializable]
public sealed class ReplacedPersonModManifest
{
public string Id { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public List<ReplacedPersonModFile> Files { get; set; } = new();
}
public sealed class ReplacedModCompatibilityResult
{
public bool Compatible => Differences.Count == 0;
public List<string> Differences { get; } = new();
}
public static class ReplacedModCompatibility
{
public static ReplacedModCompatibilityResult Compare(IEnumerable<ReplacedPersonModManifest> authority,
IEnumerable<ReplacedPersonModManifest> client)
{
var result = new ReplacedModCompatibilityResult();
var expected = authority.ToDictionary(value => value.Id, StringComparer.Ordinal);
var actual = client.ToDictionary(value => value.Id, StringComparer.Ordinal);
foreach (var id in expected.Keys.Union(actual.Keys, StringComparer.Ordinal).OrderBy(value => value, StringComparer.Ordinal))
{
if (!expected.TryGetValue(id, out var expectedMod)) { result.Differences.Add("unexpected-mod:" + id); continue; }
if (!actual.TryGetValue(id, out var actualMod)) { result.Differences.Add("missing-mod:" + id); continue; }
if (!string.Equals(expectedMod.Version, actualMod.Version, StringComparison.Ordinal))
result.Differences.Add($"version:{id}:{expectedMod.Version}!={actualMod.Version}");
CompareFiles(id, expectedMod.Files, actualMod.Files, result.Differences);
}
return result;
}
public static string ComputeManifestSetHash(IEnumerable<ReplacedPersonModManifest> manifests)
{
var text = new StringBuilder();
foreach (var manifest in manifests.OrderBy(value => value.Id, StringComparer.Ordinal))
{
text.Append(manifest.Id).Append('|').Append(manifest.Version).Append('\n');
foreach (var file in manifest.Files.OrderBy(value => value.Path, StringComparer.Ordinal))
text.Append(file.Path).Append('|').Append(file.Sha256.ToLowerInvariant()).Append('\n');
}
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToString()));
return string.Concat(bytes.Select(value => value.ToString("x2")));
}
private static void CompareFiles(string modId, IEnumerable<ReplacedPersonModFile> expected,
IEnumerable<ReplacedPersonModFile> actual, ICollection<string> differences)
{
var expectedFiles = expected.ToDictionary(value => value.Path, StringComparer.Ordinal);
var actualFiles = actual.ToDictionary(value => value.Path, StringComparer.Ordinal);
foreach (var path in expectedFiles.Keys.Union(actualFiles.Keys, StringComparer.Ordinal).OrderBy(value => value, StringComparer.Ordinal))
{
if (!expectedFiles.TryGetValue(path, out var expectedFile)) { differences.Add($"unexpected-file:{modId}:{path}"); continue; }
if (!actualFiles.TryGetValue(path, out var actualFile)) { differences.Add($"missing-file:{modId}:{path}"); continue; }
if (!string.Equals(expectedFile.Sha256, actualFile.Sha256, StringComparison.OrdinalIgnoreCase))
differences.Add($"hash:{modId}:{path}");
}
}
}
public sealed class ReplacedPersonRegistry : IReplacedPersonRegistry
{
private readonly ReplacedContentCatalog _catalog;
private readonly Dictionary<string, string> _rewards = new(StringComparer.Ordinal);
private readonly Dictionary<string, (string title, string body)> _stories = new(StringComparer.Ordinal);
private readonly Dictionary<string, IReplacedPersonEffectResolver> _effects = new(StringComparer.Ordinal);
private readonly Dictionary<string, Func<IReadOnlyList<string>, string>> _commands = new(StringComparer.OrdinalIgnoreCase);
public ReplacedPersonRegistry(ReplacedContentCatalog baseCatalog) => _catalog = baseCatalog.Clone();
public ReplacedContentCatalog CreateSnapshot() => _catalog.Clone();
public IReadOnlyDictionary<string, IReplacedPersonEffectResolver> Effects => _effects;
public IReadOnlyDictionary<string, Func<IReadOnlyList<string>, string>> Commands => _commands;
public void RegisterCard(ReplacedCardDefinition definition) => AddUnique(_catalog.Cards, definition.Id, definition);
public void RegisterEndCard(ReplacedEndCardDefinition definition) => AddUnique(_catalog.EndCards, definition.Id, definition);
public void RegisterEnemy(ReplacedEnemyDefinition definition) => AddUnique(_catalog.Enemies, definition.Id, definition);
public void RegisterReward(string id, string displayName) => AddUnique(_rewards, id, displayName);
public void RegisterStory(string id, string title, string body) => AddUnique(_stories, id, (title, body));
public void RegisterEffectResolver(string effectId, IReplacedPersonEffectResolver resolver)
{
AddUnique(_effects, effectId, resolver);
AddUnique(_catalog.EffectResolvers, effectId, resolver);
}
public void RegisterCommand(string path, Func<IReadOnlyList<string>, string> handler) => AddUnique(_commands, path, handler);
private static void AddUnique<T>(IDictionary<string, T> target, string id, T value)
{
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Registry id must not be empty.", nameof(id));
var key = id.Trim();
if (target.ContainsKey(key)) throw new InvalidOperationException("Duplicate registry id: " + id);
target.Add(key, value);
}
}
public sealed class ReplacedHotReloadCatalog
{
public ReplacedContentCatalog Current { get; private set; }
public ReplacedContentCatalog? Pending { get; private set; }
public string CurrentHash => Current.ComputeHash();
public string PendingHash => Pending?.ComputeHash() ?? string.Empty;
public ReplacedHotReloadCatalog(ReplacedContentCatalog initial) => Current = initial.Clone();
public void Stage(ReplacedContentCatalog next) => Pending = next.Clone();
public ReplacedContentCatalog SnapshotForNewMatch()
{
if (Pending != null) { Current = Pending; Pending = null; }
return Current.Clone();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf4213a9a53d2c74a917cdd129f062ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,272 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace ReplacedPerson.Core
{
public enum ReplacedNetworkMessageKind
{
Join = 0,
Create = 1,
Ready = 2,
Deck = 3,
MatchCommand = 4,
MatchSnapshot = 5,
MatchEvent = 6,
Reconnect = 7,
Error = 8
}
[Serializable]
public sealed class ReplacedNetworkEnvelope
{
public ReplacedNetworkMessageKind Kind { get; set; }
public long Sequence { get; set; }
public string IdempotencyToken { get; set; } = string.Empty;
public string RoomId { get; set; } = string.Empty;
public string PlayerId { get; set; } = string.Empty;
public string Payload { get; set; } = string.Empty;
public string StateHash { get; set; } = string.Empty;
}
[Serializable]
public sealed class ReplacedJoinRequest
{
public string RoomId { get; set; } = string.Empty;
public string PlayerId { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public List<ReplacedPersonModManifest> Mods { get; set; } = new();
}
[Serializable]
public sealed class ReplacedRoomSnapshot
{
public string RoomId { get; set; } = string.Empty;
public string ContentHash { get; set; } = string.Empty;
public List<string> Players { get; set; } = new();
public List<string> ReadyPlayers { get; set; } = new();
public bool MatchStarted { get; set; }
public string MatchStateHash { get; set; } = string.Empty;
}
[Serializable]
public sealed class ReplacedReadyRequest
{
public bool Ready { get; set; }
}
[Serializable]
public sealed class ReplacedMatchSnapshotPayload
{
public ReplacedRoomSnapshot Room { get; set; } = new();
public ReplacedMatchState? Match { get; set; }
public int LocalPlayerIndex { get; set; } = -1;
}
[Serializable]
public sealed class ReplacedNetworkResultPayload
{
public bool Accepted { get; set; }
public bool Duplicate { get; set; }
public string Code { get; set; } = string.Empty;
public string Detail { get; set; } = string.Empty;
}
public sealed class ReplacedRoomSeat
{
public string PlayerId { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public ReplacedDeck? Deck { get; set; }
public bool Ready { get; set; }
public bool Connected { get; set; } = true;
public DateTime ReservedUntilUtc { get; set; }
public int ConsecutiveTimeouts { get; set; }
}
public sealed class ReplacedRoomAuthority
{
private readonly ReplacedContentCatalog _content;
private readonly List<ReplacedPersonModManifest> _mods;
private readonly ReplacedRoomSeat?[] _seats = new ReplacedRoomSeat?[2];
private readonly ulong _seed;
public string RoomId { get; }
public ReplacedMatchEngine? Match { get; private set; }
public IReadOnlyList<ReplacedRoomSeat?> Seats => _seats;
public ReplacedRoomAuthority(string roomId, ReplacedContentCatalog content,
IEnumerable<ReplacedPersonModManifest> mods, ulong seed)
{
RoomId = roomId;
_content = content.Clone();
_mods = new List<ReplacedPersonModManifest>(mods);
_seed = seed;
}
public ReplacedModCompatibilityResult TryJoin(ReplacedJoinRequest request, DateTime nowUtc)
{
var compatibility = ReplacedModCompatibility.Compare(_mods, request.Mods);
if (!compatibility.Compatible) return compatibility;
for (var i = 0; i < _seats.Length; i++)
{
if (_seats[i]?.PlayerId == request.PlayerId)
{
_seats[i]!.Connected = true;
_seats[i]!.ReservedUntilUtc = nowUtc.AddSeconds(30);
return compatibility;
}
}
for (var i = 0; i < _seats.Length; i++)
{
if (_seats[i] == null || (!_seats[i]!.Connected && _seats[i]!.ReservedUntilUtc <= nowUtc))
{
_seats[i] = new ReplacedRoomSeat
{
PlayerId = request.PlayerId,
DisplayName = request.DisplayName,
Connected = true,
ReservedUntilUtc = nowUtc.AddSeconds(30)
};
return compatibility;
}
}
compatibility.Differences.Add("room.full");
return compatibility;
}
public string SetDeck(string playerId, ReplacedDeck deck)
{
var seat = FindSeat(playerId);
var errors = ReplacedDeckValidator.Validate(deck, _content);
if (errors.Count > 0) return string.Join(",", errors);
seat.Deck = deck.Clone();
seat.Ready = false;
return string.Empty;
}
public string SetReady(string playerId, bool ready)
{
var seat = FindSeat(playerId);
if (ready && seat.Deck == null) return "deck.missing";
seat.Ready = ready;
if (Match == null && _seats[0]?.Ready == true && _seats[1]?.Ready == true)
Match = new ReplacedMatchEngine(_content, _seats[0]!.Deck!, _seats[1]!.Deck!, _seed,
_seats[0]!.DisplayName, _seats[1]!.DisplayName);
return string.Empty;
}
public string SetDeckAndReady(string playerId, ReplacedDeck deck)
{
var error = SetDeck(playerId, deck);
return string.IsNullOrEmpty(error) ? SetReady(playerId, true) : error;
}
public ReplacedCommandResult Apply(string playerId, ReplacedMatchCommand command)
{
if (Match == null) return new ReplacedCommandResult { ErrorCode = "match.not-started", Message = "match.not-started" };
var index = _seats[0]?.PlayerId == playerId ? 0 : _seats[1]?.PlayerId == playerId ? 1 : -1;
if (index < 0) return new ReplacedCommandResult { ErrorCode = "player.not-seated", Message = "player.not-seated" };
command.PlayerIndex = index;
return Match.Apply(command);
}
public void Disconnect(string playerId, DateTime nowUtc)
{
var seat = FindSeat(playerId);
seat.Connected = false;
seat.ReservedUntilUtc = nowUtc.AddSeconds(30);
}
public void RecordTurnTimeout(string playerId)
{
var seat = FindSeat(playerId);
seat.ConsecutiveTimeouts++;
if (seat.ConsecutiveTimeouts >= 2 && Match != null && Match.State.Phase != ReplacedMatchPhase.Completed)
{
Match.State.Winner = ReferenceEquals(seat, _seats[0]) ? ReplacedWinner.PlayerTwo : ReplacedWinner.PlayerOne;
Match.State.Phase = ReplacedMatchPhase.Completed;
Match.State.Log.Add("连续两个回合超时判负");
}
}
public bool Kick(string playerId)
{
for (var i = 0; i < _seats.Length; i++)
{
if (_seats[i]?.PlayerId != playerId) continue;
_seats[i] = null;
return true;
}
return false;
}
public int GetPlayerIndex(string playerId)
{
if (_seats[0]?.PlayerId == playerId) return 0;
if (_seats[1]?.PlayerId == playerId) return 1;
return -1;
}
public ReplacedMatchState? PlayerMatchSnapshot(string playerId)
{
if (Match == null) return null;
var playerIndex = GetPlayerIndex(playerId);
if (playerIndex < 0) return null;
var snapshot = Match.State.Clone();
var opponent = snapshot.Players[1 - playerIndex];
opponent.NormalHand = HiddenCards(opponent.NormalHand.Count);
opponent.EndHand = HiddenCards(opponent.EndHand.Count);
opponent.NormalDrawPile = HiddenCards(opponent.NormalDrawPile.Count);
opponent.EndDrawPile = HiddenCards(opponent.EndDrawPile.Count);
return snapshot;
}
public ReplacedRoomSnapshot Snapshot()
{
var result = new ReplacedRoomSnapshot
{
RoomId = RoomId,
ContentHash = _content.ComputeHash(),
MatchStarted = Match != null,
MatchStateHash = Match?.ComputeStateHash() ?? string.Empty
};
foreach (var seat in _seats)
{
if (seat == null) continue;
result.Players.Add(seat.PlayerId);
if (seat.Ready) result.ReadyPlayers.Add(seat.PlayerId);
}
return result;
}
private ReplacedRoomSeat FindSeat(string playerId)
{
foreach (var seat in _seats)
if (seat?.PlayerId == playerId) return seat;
throw new InvalidOperationException("Player is not seated: " + playerId);
}
private static List<string> HiddenCards(int count)
{
var result = new List<string>(count);
for (var i = 0; i < count; i++) result.Add(string.Empty);
return result;
}
}
public static class ReplacedReplayRunner
{
public static ReplacedMatchEngine Replay(ReplacedContentCatalog content, ReplacedDeck first, ReplacedDeck second,
ulong seed, IEnumerable<ReplacedMatchCommand> commands)
{
var engine = new ReplacedMatchEngine(content, first, second, seed);
foreach (var command in commands)
{
var result = engine.Apply(command.Clone());
if (!result.Accepted) throw new InvalidOperationException(result.ErrorCode + ":" + result.Message);
}
return engine;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ac7eae5b8f766a40ab60ce8ea0fb184
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+64
View File
@@ -0,0 +1,64 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace ReplacedPerson.Core
{
/// <summary>PCG-XSH-RR 32; state is explicit so replays are portable across Unity and .NET.</summary>
public sealed class Pcg32
{
public ulong State { get; private set; }
public ulong Increment { get; private set; }
public Pcg32(ulong seed, ulong stream = 54UL)
{
State = 0UL;
Increment = (stream << 1) | 1UL;
NextUInt();
State += seed;
NextUInt();
}
private Pcg32(ulong state, ulong increment, bool _) { State = state; Increment = increment; }
public Pcg32 Clone() => new(State, Increment, true);
public uint NextUInt()
{
var oldState = State;
State = unchecked(oldState * 6364136223846793005UL + Increment);
var xorShifted = (uint)(((oldState >> 18) ^ oldState) >> 27);
var rotation = (int)(oldState >> 59);
return (xorShifted >> rotation) | (xorShifted << ((-rotation) & 31));
}
public int NextInt(int exclusiveMax)
{
if (exclusiveMax <= 0)
throw new ArgumentOutOfRangeException(nameof(exclusiveMax));
var bound = (uint)exclusiveMax;
var threshold = unchecked((uint)(-bound)) % bound;
while (true)
{
var value = NextUInt();
if (value >= threshold)
return (int)(value % bound);
}
}
public int RollD6() => NextInt(6) + 1;
public void Shuffle<T>(IList<T> values)
{
if (values == null)
throw new ArgumentNullException(nameof(values));
for (var i = values.Count - 1; i > 0; i--)
{
var j = NextInt(i + 1);
(values[i], values[j]) = (values[j], values[i]);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9886d17170c61941a970081ad8e742a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,81 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace ReplacedPerson.Core
{
[Serializable]
public sealed class ReplacedPersonSaveData
{
public const int CurrentVersion = 3;
public int Version { get; set; } = CurrentVersion;
public int ChapterProgress { get; set; }
public List<string> CollectedCards { get; set; } = new();
public List<string> Ornaments { get; set; } = new();
public List<string> EquippedOrnaments { get; set; } = new();
public ReplacedDeck ActiveDeck { get; set; } = ReplacedBuiltInContent.CreateStarterDeck();
public List<ReplacedMatchCommand> RecentReplay { get; set; } = new();
}
public static class ReplacedPersonSaveMigration
{
public static ReplacedPersonSaveData Migrate(ReplacedPersonSaveData source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (source.Version > ReplacedPersonSaveData.CurrentVersion)
throw new InvalidOperationException("Save data comes from a newer version.");
source.CollectedCards ??= new List<string>();
source.Ornaments ??= new List<string>();
source.EquippedOrnaments ??= new List<string>();
source.ActiveDeck ??= ReplacedBuiltInContent.CreateStarterDeck();
source.RecentReplay ??= new List<ReplacedMatchCommand>();
if (source.Version < 2)
{
if (source.ChapterProgress > 0 && !source.Ornaments.Contains("ornament.gold-eye"))
source.Ornaments.Add("ornament.gold-eye");
source.Version = 2;
}
if (source.Version < 3)
{
var starter = ReplacedBuiltInContent.CreateStarterDeck();
if (IsLegacyStarter(source.ActiveDeck)) source.ActiveDeck = starter;
EnsureOwnedCopies(source.CollectedCards, starter.NormalCards);
source.Version = 3;
}
return source;
}
public static ReplacedPersonSaveData CreateNew()
{
var deck = ReplacedBuiltInContent.CreateStarterDeck();
return new ReplacedPersonSaveData
{
ActiveDeck = deck,
CollectedCards = new List<string>(deck.NormalCards)
};
}
private static bool IsLegacyStarter(ReplacedDeck deck)
{
var legacy = new[]
{
"attack.kill", "attack.kill", "attack.elbow", "attack.elbow", "attack.break", "attack.break",
"dodge.flash", "dodge.flash", "dodge.phase", "dodge.phase", "counter.blood", "counter.blood"
};
return deck.NormalCards.OrderBy(value => value, StringComparer.Ordinal)
.SequenceEqual(legacy.OrderBy(value => value, StringComparer.Ordinal)) &&
deck.EndCards.SequenceEqual(new[] { "end.inherit", "end.ledger", "end.severance", "end.black" });
}
private static void EnsureOwnedCopies(List<string> collection, IEnumerable<string> starterCards)
{
foreach (var group in starterCards.GroupBy(value => value, StringComparer.Ordinal))
{
var missing = group.Count() - collection.Count(value => value == group.Key);
for (var i = 0; i < missing; i++) collection.Add(group.Key);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e65918b8d9a6065499ad8cd7f6247e40
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,84 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace ReplacedPerson.Core
{
public sealed class ReplacedAi
{
private readonly ReplacedContentCatalog _content;
public ReplacedAi(ReplacedContentCatalog content) => _content = content ?? throw new ArgumentNullException(nameof(content));
/// <summary>Uses only own hand plus the already-public lead submission.</summary>
public ReplacedTurnSubmission Choose(ReplacedMatchState visibleState, int playerIndex)
{
var self = visibleState.Players[playerIndex];
var targetCount = visibleState.IsLastStand ? 4 : Math.Min(3, self.NormalHand.Count);
var publicOpponent = visibleState.Submissions[1 - playerIndex];
var opponentTypes = publicOpponent == null
? Array.Empty<ReplacedEffectType>()
: publicOpponent.NormalCardIds.Where(_content.Cards.ContainsKey).Select(id => _content.Cards[id].EffectType).ToArray();
var scored = new List<(string id, double score)>();
foreach (var id in self.NormalHand.Distinct(StringComparer.Ordinal))
{
var card = _content.Cards[id];
var expected = card.BaseValue + card.Factor * 3.5;
var efficiency = expected / Math.Max(1, card.Cost);
var survival = self.Health <= 10 && card.EffectType != ReplacedEffectType.Attack ? 4.0 : 0.0;
var counterScore = opponentTypes.Sum(type => MatchupScore(card.EffectType, type));
scored.Add((id, expected + efficiency * 2.0 + survival + counterScore));
}
var selected = new List<string>();
var remainingCost = self.Cost;
foreach (var candidate in scored.OrderByDescending(value => value.score).ThenBy(value => value.id, StringComparer.Ordinal))
{
var available = self.NormalHand.Count(id => id == candidate.id);
for (var copy = 0; copy < available && selected.Count < targetCount; copy++)
{
var cost = _content.Cards[candidate.id].Cost;
if (cost > remainingCost) continue;
selected.Add(candidate.id);
remainingCost -= cost;
}
}
var endCount = visibleState.IsLastStand ? 2 : 1;
var ends = self.EndHand.Where(id => _content.EndCards[id].Cost <= remainingCost)
.OrderByDescending(id => _content.EndCards[id].SlotBase.Sum() + _content.EndCards[id].SlotFactor.Sum() * 3.5)
.ThenBy(id => id, StringComparer.Ordinal).Take(endCount).ToList();
if (selected.Count < (visibleState.IsLastStand ? 4 : 1) || ends.Count < endCount)
return new ReplacedTurnSubmission { Pass = true, DiscardEndOnPass = false };
return new ReplacedTurnSubmission
{
NormalCardIds = selected,
EndCardIds = ends,
RerollSlot = ChooseRerollSlot(selected)
};
}
private int ChooseRerollSlot(IReadOnlyList<string> selected)
{
if (selected.Count == 0) return -1;
var weakest = double.MaxValue;
var result = -1;
for (var i = 0; i < Math.Min(3, selected.Count); i++)
{
var card = _content.Cards[selected[i]];
var expected = card.BaseValue + card.Factor * 3.5;
if (expected < weakest) { weakest = expected; result = i; }
}
return result;
}
private static double MatchupScore(ReplacedEffectType own, ReplacedEffectType opponent) =>
own == ReplacedEffectType.Counter && opponent == ReplacedEffectType.Attack ? 4.0 :
own == ReplacedEffectType.Dodge && opponent == ReplacedEffectType.Attack ? 3.0 :
own == ReplacedEffectType.Attack && opponent == ReplacedEffectType.Dodge ? 2.0 : 0.0;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54aee14b2a69366498d14e2f9294bcdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
{
"name": "ReplacedPerson.Core",
"rootNamespace": "ReplacedPerson.Core",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2d11e372f3354d047977650288e4ea13
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,69 @@
#nullable enable
using System;
using System.Security.Cryptography;
using System.Text;
namespace ReplacedPerson.Core
{
public static class ReplacedStateHasher
{
public static string Compute(ReplacedMatchState state, Pcg32 random)
{
var text = new StringBuilder(2048);
text.Append(state.Round).Append('|').Append(state.LeadPlayer).Append('|').Append(state.InitiativeRoll)
.Append('|').Append((int)state.Phase).Append('|').Append((int)state.Winner).Append('|')
.Append(state.IsLastStand ? 1 : 0).Append('|').Append(state.LastStandUsed ? 1 : 0).Append('|')
.Append(state.ContentHash).Append('|').Append(random.State).Append('|').Append(random.Increment).Append('\n');
for (var index = 0; index < state.Players.Length; index++)
{
var player = state.Players[index];
text.Append(index).Append('|').Append(player.PlayerId).Append('|').Append(player.Health).Append('|')
.Append(player.Cost).Append('|').Append(player.Defense).Append('|').Append(player.BonusNormalDraw)
.Append('|').Append(player.GuaranteedDodge ? 1 : 0).Append('|').Append(player.SkipNextEndDraw ? 1 : 0)
.Append('|').Append(player.SpentThisRound).Append('|').Append(player.EffectiveEffectsThisRound)
.Append('|').Append(player.DamageThisRound).Append('\n');
AppendList(text, player.NormalDrawPile);
AppendList(text, player.NormalDiscard);
AppendList(text, player.NormalHand);
AppendList(text, player.EndDrawPile);
AppendList(text, player.EndDiscard);
AppendList(text, player.EndHand);
}
for (var i = 0; i < state.LastAcceptedSequence.Length; i++)
text.Append("S|").Append(i).Append('|').Append(state.LastAcceptedSequence[i]).Append('\n');
for (var i = 0; i < state.Submissions.Length; i++)
{
var submission = state.Submissions[i];
text.Append("C|").Append(i).Append('|');
if (submission == null)
{
text.Append("null\n");
continue;
}
text.Append(submission.Pass ? 1 : 0).Append('|').Append(submission.DiscardEndOnPass ? 1 : 0)
.Append('|').Append(submission.RerollSlot).Append('|');
AppendList(text, submission.NormalCardIds);
AppendList(text, submission.EndCardIds);
}
foreach (var resolution in state.LastResolution)
{
text.Append("R|").Append(resolution.SlotIndex).Append('|').Append((int)resolution.PlayerOneType).Append('|')
.Append((int)resolution.PlayerTwoType).Append('|').Append(resolution.PlayerOneRoll).Append('|')
.Append(resolution.PlayerTwoRoll).Append('|').Append(resolution.PlayerOneValue).Append('|')
.Append(resolution.PlayerTwoValue).Append('|').Append(resolution.PlayerOneRerolled ? 1 : 0).Append('|')
.Append(resolution.PlayerTwoRerolled ? 1 : 0).Append('|').Append(resolution.DamageToPlayerOne).Append('|')
.Append(resolution.DamageToPlayerTwo).Append('|').Append(resolution.Summary).Append('\n');
}
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToString()));
var result = new StringBuilder(bytes.Length * 2);
foreach (var value in bytes) result.Append(value.ToString("x2"));
return result.ToString();
}
private static void AppendList(StringBuilder target, System.Collections.Generic.IEnumerable<string> values) =>
target.Append('[').Append(string.Join(",", values)).Append("]\n");
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c301f3f68d939564db3971ef7199dd01
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8478e2702461e554cb1391f199c19b7c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
# 美术与字体来源
## 论坛原始资产
`Art/ForumOriginal/` 中的纸张、卡面、牌序示意和结算图来自原策划页面:[Game14 - 被替代之人](https://forum.crash.work/d/22-game14-bei-ti-dai-zhi-ren)。这些文件用于复现该策划的 Demo 视觉,后续发行前应由项目方再次确认原作者授权范围。
示例 Mod 的 `icon.png` 由同目录反击卡面裁取复用,因此遵循相同授权边界。
## 字体
中文界面使用 Noto Sans SC Variable
- 项目文件:`Art/NotoSansSC-Variable.ttf`
- 许可证:`Art/OFL-NotoSansSC.txt`
- 上游:[Google Fonts - Noto Sans SC](https://fonts.google.com/noto/specimen/Noto+Sans+SC)
字体采用 SIL Open Font License 1.1。Unity 生成的 `NotoSansSC SDF.asset` 是动态 TMP 字体资产。
## 音效
战斗与界面音效取自本机提供的 `C:\Users\im\Downloads\Soundpack`Demo 内副本位于
`Resources/ReplacedPersonAudio/`。当前使用了 `UI``Card and Board``Environment`
`Combat and Gore``Musical Effects` 分类中的按钮、纸牌、锁、骰子、打击和结算短音效。
该本地素材包未附带可识别的许可证文件;当前仅用于 Demo 验证。正式分发前需要由项目方补充来源与授权证明,或替换为许可证明确的音效。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 30d059b0300835a4d95851bd9615f14f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
# Mod 开发
## 公共接口
纯规则层位于 `ReplacedPerson.Core`,可在 Unity 和 .NET 8 独服中使用:
- `IReplacedPersonMod`
- `IReplacedPersonRegistry`
- `IReplacedPersonEffectResolver`
- `ReplacedPersonModManifest`
可注册卡牌、结束牌、敌人、奖励、剧情、规则效果和命令。联网规则不得依赖 Unity 类型。Unity 适配层通过 ShrinkModFramework 生命周期把规则实现注册到 `replaced-person.mods``AutoApplyHarmonyPatches` 应保持关闭。
## 审计训练 Mod
示例位于 `Assets/Demos/ReplacedPerson/Mods/ExampleGreedMod/`,包含:
- 普通牌“审计”:反击类型,触发正式效果解析器后恢复 1 COST。
- 训练敌人“训练用审计员”。
- 奖励、短剧情和 `example audit` 命令。
- 纯规则程序集和可选 Unity 适配程序集。
- 内容 JSON、PNG 图标和严格文件哈希清单。
构建并打包:
```powershell
./GeneratedServers/ExampleGreedMod.Rules/pack-mod.ps1
```
输出:
```text
Artifacts/ReplacedPersonMods/ExampleGreedMod-1.0.0.zip
GeneratedServers/ReplacedPerson.ServerHost/Mods/ExampleGreedMod/
```
## 包结构
```text
ExampleGreedMod/
manifest.json
content.json
icon.png
ExampleGreedMod.Rules.dll
ExampleGreedMod.Unity.dll # 可选,仅 Unity 端需要
```
`manifest.json``files` 必须覆盖包内所有参与联网兼容判断的文件,并写入小写 SHA-256。独服启动时先校验文件,再加载纯规则 DLL;当前房间保留不可变内容快照,`reload-mods` 只影响之后创建的房间。
不可信 DLL 不在本 Demo 的安全边界内。服务器管理员只应安装可信来源的规则程序集。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e94c9c61703afeb43a2339f08b037ec1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
# 联网协议
## 传输
- TCP:默认端口 `18140`
- KCP:默认端口 `18141`
- UDP 房间发现:默认端口 `18142`
- 直接 IP 在端口可达时可用于局域网或公网;不提供匹配、NAT 穿透或账号系统。
Unity 内置 Host 与 .NET 独服使用同一个 `ReplacedRoomAuthority``ReplacedMatchEngine`。消息体是 UTF-8 JSON,底层 TCP/KCP 传输负责完整包边界。
## 消息
| Kind | 用途 |
| --- | --- |
| `Create` | 创建房间并占用席位 |
| `Join` | 加入房间;房间为空时独服可按配置创建 |
| `Ready` | 设置准备状态 |
| `Deck` | 提交并校验 12+4 牌组 |
| `MatchCommand` | 提交回合指令 |
| `MatchSnapshot` | 玩家裁剪快照、房间状态和状态哈希 |
| `MatchEvent` | 成功、重复包等权威结果 |
| `Reconnect` | 在 30 秒席位保留期内重新绑定 |
| `Error` | 结构化错误码和差异详情 |
所有对局指令包含玩家序号和幂等令牌。已接受令牌的重发返回 `Duplicate=true`,不会重复结算。快照只保留对手隐藏区的数量,不暴露卡牌 ID。
## Mod 握手
加入请求携带所有启用 Mod 的 ID、版本及文件 SHA-256。权威端逐文件比较,报告 `missing-mod``unexpected-mod``version``missing-file``unexpected-file``hash` 差异,并拒绝准备。
房间创建时冻结内容快照。热重载只更新之后创建的房间或下一场单机对局,不修改进行中的状态机。
## 断线和超时
- 断线后席位保留 30 秒。
- 连续两个回合超时判负。
- 客户端重连后以权威快照和状态哈希恢复,不在客户端补算遗漏骰点。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7824b0b1c4c6ebd45bc397fd2302a6ce
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+48
View File
@@ -0,0 +1,48 @@
# 对局规则
## 牌组与资源
- 每套牌固定 12 张普通牌和 4 张结束牌。
- 普通牌同名最多 2 张,结束牌同名最多 1 张。
- 初始 30 HP、10 COST。首次抽 4 张普通牌和 1 张结束牌,此后每回合抽 2+1。
- 普通手牌上限 9。抽牌堆耗尽时,权威 PCG32 将弃牌堆洗回。
- 锁牌时支付 COST。回合末恢复 `1 + floor(本回合消耗 / 2)`,最多恢复到 10。
- 跳过不支付 COST;未弃掉的结束牌会使下一回合跳过结束牌抽取。
## 回合顺序
1. 权威端投先手骰并确定先手。
2. 双方抽牌。
3. 先手公开锁定 1 至 4 张普通牌和 1 张结束牌,或跳过。
4. 后手看到牌序与效果范围后进行针对性锁定。
5. 每方可预先标记一个效果槽;该槽初骰不高于 2 时自动重掷一次。
6. 三个效果槽从左到右执行继承、合成、拼点、联锁和资源结算。
结束牌提供三个效果槽。单槽值为 `始数 + 因数 x D6`。平点继续由权威随机源重掷,直到分出胜负。
## 基础克制
- 攻击胜出:造成差值伤害,单槽最多 8。
- 反击胜出:取消攻击并反伤差值,单槽最多 6。
- 闪避胜出:取消攻击,并使成功方下一回合多抽 1 张普通牌。
## 两牌合成
| 组合 | 结果 |
| --- | --- |
| 攻+攻 | 合并值乘 1.5 |
| 攻+闪 | 成功后抽 1 张牌并恢复 1 COST |
| 攻+反 | 成功后追加 3 伤害 |
| 反+反 | 返还首次伤害的 50%,最多 5 |
| 闪+反 | 获得 2 点当回合 DEF |
| 闪+闪 | 获得一次必定闪避 |
## 孤注一掷
双方在同一次结算中同时归零时,只进入一次孤注一掷:各抽 4 张普通牌和主、副两张结束牌,恢复量转为伤害。仍平局时先比较本回合有效果数,最后才使用权威骰决定胜负。
## 贪婪
第一场使用 20 HP 的教学形态;第二场使用 30 HP 的完整形态。Boss 牌包括“势利眼”“扭亏为盈”“降薪”。完整形态胜利可获得金色势利眼、金链、褶皱西装和三张对应卡牌。
AI 的输入是裁剪后的可见对局状态;评分考虑期望值、存活、COST 效率、合成收益和公开对位,不读取未来骰点或隐藏抽牌。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: fa254a4eee4dd4e4e9e72373bf0f1ec6
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
# 与论坛原案的差异
本 Demo 保留原案的牌序反制、结束牌继承、三个效果槽拼点、骰子、合成和“孤注一掷”。以下内容为了可验证性与首章节奏做了明确化或平衡调整。
| 项目 | 论坛原案 | Demo 实现 |
| --- | --- | --- |
| 随机源 | 未限定实现 | 单一 PCG32 权威随机源,可回放并跨端复现 |
| 单槽掷骰 | 可有多种解释 | 每侧每槽初掷一次;只允许一个预标记的低点重掷 |
| 平点 | 继续掷骰 | 保留,并由权威端持续到分胜负 |
| 攻击伤害 | 差值 | 差值,单槽封顶 8 |
| 反击反伤 | 差值 | 差值,单槽封顶 6 |
| COST 恢复 | 原案数值未完全封闭 | `1 + floor(本回合消耗/2)`,上限 10 |
| 合成 | 以概念描述为主 | 六种组合使用固定表,避免客户端解释分歧 |
| 同时归零 | 孤注一掷 | 只触发一次;有效果数后才用权威骰决胜 |
| 内容范围 | 论坛概念和部分牌面 | 首版只完成“贪婪”第一章两战,并补齐四张结束牌 |
| 联网 | 未指定 | 权威状态机、序号、幂等令牌、状态哈希与严格 Mod 清单 |
原案来源:[Game14 - 被替代之人](https://forum.crash.work/d/22-game14-bei-ti-dai-zhi-ren)。
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f0d9cba143ca12748867116ba01261f8
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fa52c3405309824498188ecc9bba1ed1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
{
"name": "ReplacedPerson.Editor",
"rootNamespace": "ReplacedPerson.Editor",
"references": [
"ReplacedPerson.Core",
"ReplacedPerson.Runtime",
"ShrinkApp.Core.Runtime",
"ShrinkContext.AppAdapter.Runtime",
"ShrinkModFramework.Runtime",
"ShrinkTutorial.Runtime",
"UnityEngine.UI",
"Unity.TextMeshPro"
],
"includePlatforms": ["Editor"],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 04e8103b42db45f49966a408dc47dda1
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,524 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ReplacedPerson.Runtime;
using ShrinkApp;
using ShrinkContext.AppAdapter;
using ShrinkModFramework;
using ShrinkTutorial;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.UI;
namespace ReplacedPerson.Editor
{
public static class ReplacedPersonSceneBuilder
{
private const string Root = "Assets/Demos/ReplacedPerson";
private const string PrefabRoot = Root + "/Prefabs";
private const string ResourceRoot = Root + "/Resources";
private const string ScenePath = Root + "/ReplacedPerson.unity";
private const string DemoPrefabPath = PrefabRoot + "/ReplacedPersonDemo.prefab";
private const string BattlePrefabPath = PrefabRoot + "/BattleScreen.prefab";
private const string ModalPrefabPath = PrefabRoot + "/ModalOverlay.prefab";
private const string FontPath = Root + "/Art/NotoSansSC-Variable.ttf";
private const string FontAssetPath = Root + "/Art/NotoSansSC SDF.asset";
private static TMP_FontAsset? _font;
private static Sprite? _paper;
private static Sprite? _attack;
private static Sprite? _dodge;
private static Sprite? _counter;
[MenuItem("ShrinkSDK/Demos/Replaced Person/Rebuild Full Demo")]
public static void Rebuild()
{
Directory.CreateDirectory(PrefabRoot);
Directory.CreateDirectory(ResourceRoot);
AssetDatabase.Refresh();
EnsureSdkAssets();
ConfigureSprites();
EnsureFont();
BuildBattlePrefab();
BuildModalPrefab();
BuildDemoPrefab();
BuildTutorialAssets();
BuildScene();
AddToBuildSettings();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[ReplacedPerson] Demo rebuilt: {ScenePath}");
}
[MenuItem("ShrinkSDK/Demos/Replaced Person/Build Windows Player")]
public static void BuildWindowsPlayer()
{
var output = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Artifacts", "ReplacedPersonBuild", "ReplacedPerson.exe"));
Directory.CreateDirectory(Path.GetDirectoryName(output)!);
var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions
{
scenes = new[] { ScenePath },
locationPathName = output,
target = BuildTarget.StandaloneWindows64,
options = BuildOptions.Development
});
if (report.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded)
throw new InvalidOperationException($"Windows build failed: errors={report.summary.totalErrors}, warnings={report.summary.totalWarnings}");
Debug.Log($"[ReplacedPerson] Windows build complete: {output}, warnings={report.summary.totalWarnings}");
}
private static void EnsureSdkAssets()
{
const string appSettingsPath = "Assets/Resources/ShrinkAppSettings.asset";
var appSettings = AssetDatabase.LoadAssetAtPath<ShrinkAppSettings>(appSettingsPath);
var appScript = appSettings == null
? null
: new SerializedObject(appSettings).FindProperty("m_Script")?.objectReferenceValue;
if (appSettings == null || appScript == null)
{
if (!AssetDatabase.IsValidFolder("Assets/Resources")) AssetDatabase.CreateFolder("Assets", "Resources");
if (!string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(appSettingsPath))) AssetDatabase.DeleteAsset(appSettingsPath);
appSettings = ScriptableObject.CreateInstance<ReplacedPersonAppSettings>();
appSettings.hostingMode = ShrinkAppHostingMode.ContextLoader;
appSettings.verboseLogging = false;
AssetDatabase.CreateAsset(appSettings, appSettingsPath);
}
const string modSettingsPath = ResourceRoot + "/ShrinkModFrameworkSettings.asset";
var modSettings = AssetDatabase.LoadAssetAtPath<ShrinkModFrameworkSettings>(modSettingsPath);
if (modSettings == null)
{
modSettings = ScriptableObject.CreateInstance<ShrinkModFrameworkSettings>();
modSettings.verboseLogging = false;
AssetDatabase.CreateAsset(modSettings, modSettingsPath);
}
const string profilePath = "Assets/Resources/ShrinkAppComposition.asset";
var profile = AssetDatabase.LoadAssetAtPath<ShrinkAppCompositionProfile>(profilePath);
if (profile != null)
{
var document = profile.ResolveDocument();
if (document.entries.All(value => !string.Equals(value.id, "demo.replaced-person", StringComparison.OrdinalIgnoreCase)))
document.entries.Add(new ShrinkAppCompositionEntry("demo.replaced-person"));
profile.SetDocument(document);
EditorUtility.SetDirty(profile);
}
AssetDatabase.SaveAssets();
}
private static void ConfigureSprites()
{
var paths = new[]
{
Root + "/Art/ForumOriginal/paper.jpg",
Root + "/Art/ForumOriginal/card-attack-a.png",
Root + "/Art/ForumOriginal/card-dodge-a.png",
Root + "/Art/ForumOriginal/card-counter.png",
Root + "/Art/ForumOriginal/card-back.png"
};
foreach (var path in paths)
{
if (AssetImporter.GetAtPath(path) is not TextureImporter importer) continue;
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.mipmapEnabled = false;
importer.alphaIsTransparency = true;
importer.SaveAndReimport();
}
_paper = AssetDatabase.LoadAssetAtPath<Sprite>(paths[0]);
_attack = AssetDatabase.LoadAssetAtPath<Sprite>(paths[1]);
_dodge = AssetDatabase.LoadAssetAtPath<Sprite>(paths[2]);
_counter = AssetDatabase.LoadAssetAtPath<Sprite>(paths[3]);
}
private static void EnsureFont()
{
DeleteAssetIfExists(FontAssetPath);
var source = AssetDatabase.LoadAssetAtPath<Font>(FontPath);
if (source == null) throw new InvalidOperationException("CJK source font missing: " + FontPath);
_font = TMP_FontAsset.CreateFontAsset(source, 48, 9, GlyphRenderMode.SDFAA, 1024, 1024,
AtlasPopulationMode.Dynamic, true);
_font.name = "NotoSansSC SDF";
_font.atlasPopulationMode = AtlasPopulationMode.Dynamic;
AssetDatabase.CreateAsset(_font, FontAssetPath);
foreach (var atlas in _font.atlasTextures)
{
if (atlas == null || AssetDatabase.Contains(atlas)) continue;
atlas.name = _font.name + " Atlas";
AssetDatabase.AddObjectToAsset(atlas, _font);
}
if (_font.material != null && !AssetDatabase.Contains(_font.material))
{
_font.material.name = _font.name + " Material";
AssetDatabase.AddObjectToAsset(_font.material, _font);
}
EditorUtility.SetDirty(_font);
AssetDatabase.SaveAssets();
}
private static void BuildBattlePrefab()
{
var screen = Screen("BattleScreen");
try
{
var header = Panel(screen.transform, "Header", new Color(0.05f, 0.055f, 0.06f, 0.96f),
new Vector2(0, .92f), Vector2.one);
Button(header.transform, "BackButton", "", new Vector2(0, 0), new Vector2(.07f, 1), 32);
Text(header.transform, "RoundText", "回合 1", 25, TextAlignmentOptions.Center,
new Vector2(.08f, 0), new Vector2(.92f, 1));
var enemy = Panel(screen.transform, "EnemyArea", new Color(.11f, .12f, .12f, .94f),
new Vector2(0, .75f), new Vector2(1, .92f));
Text(enemy.transform, "EnemyStatus", "贪婪 HP 30/30 COST 10/10", 24, TextAlignmentOptions.Left,
new Vector2(.025f, .56f), new Vector2(.98f, .95f));
Text(enemy.transform, "EnemyQueue", "等待敌方锁定", 22, TextAlignmentOptions.Left,
new Vector2(.025f, .08f), new Vector2(.98f, .54f));
Anchor(enemy.gameObject, "rp.matchup");
var effects = FullRect(screen.transform, "EffectArea", new Vector2(.03f, .43f), new Vector2(.97f, .74f));
var layout = effects.gameObject.AddComponent<HorizontalLayoutGroup>();
layout.spacing = 18; layout.childAlignment = TextAnchor.MiddleCenter;
layout.childControlWidth = true; layout.childControlHeight = true; layout.childForceExpandWidth = true;
for (var i = 1; i <= 3; i++)
{
var slot = Panel(effects, "Slot" + i, i == 2 ? new Color(.29f, .11f, .10f, .94f) : new Color(.11f, .14f, .14f, .94f), Vector2.zero, Vector2.one);
Text(slot.transform, "Text", $"槽 {i}\n等待拼点", 21, TextAlignmentOptions.Center, Vector2.zero, Vector2.one, new Vector2(20, 16), new Vector2(-20, -16));
}
var log = Panel(screen.transform, "LogPanel", new Color(.04f, .045f, .05f, .90f), new Vector2(.03f, .30f), new Vector2(.97f, .42f));
Text(log.transform, "LogText", "战斗记录", 15, TextAlignmentOptions.TopLeft, Vector2.zero, new Vector2(.48f, 1), new Vector2(16, 10), new Vector2(-12, -10));
Text(log.transform, "CardDetailTitle", "卡牌效果", 18, TextAlignmentOptions.TopLeft,
new Vector2(.51f, .60f), new Vector2(.98f, .94f));
Text(log.transform, "CardDetailText", "悬停、点击或拖动卡牌查看完整效果。", 15, TextAlignmentOptions.TopLeft,
new Vector2(.51f, .06f), new Vector2(.98f, .62f));
var player = Panel(screen.transform, "PlayerArea", new Color(.08f, .085f, .09f, .97f), new Vector2(0, 0), new Vector2(1, .29f));
Text(player.transform, "PlayerStatus", "被替代之人 HP 30/30 COST 10/10", 21, TextAlignmentOptions.Left,
new Vector2(.02f, .82f), new Vector2(.5f, .99f));
Text(player.transform, "SelectionText", "普通牌:未选择\n结束牌:未选择", 15, TextAlignmentOptions.TopLeft,
new Vector2(.51f, .73f), new Vector2(.98f, .99f));
var normalHand = FullRect(player.transform, "NormalHand", new Vector2(.02f, .30f), new Vector2(.72f, .81f));
var normalLayout = normalHand.gameObject.AddComponent<HorizontalLayoutGroup>();
normalLayout.spacing = 8; normalLayout.childControlWidth = false; normalLayout.childControlHeight = true;
normalLayout.childForceExpandWidth = false; normalLayout.padding = new RectOffset(0, 0, 4, 4);
CardTemplate(normalHand, "CardButtonTemplate");
Anchor(normalHand.gameObject, "rp.normal-hand");
var endHand = FullRect(player.transform, "EndHand", new Vector2(.73f, .30f), new Vector2(.98f, .81f));
var endLayout = endHand.gameObject.AddComponent<HorizontalLayoutGroup>();
endLayout.spacing = 8; endLayout.childControlWidth = false; endLayout.childControlHeight = true; endLayout.childForceExpandWidth = false;
CardTemplate(endHand, "CardButtonTemplate");
Anchor(endHand.gameObject, "rp.end-hand");
var actions = FullRect(player.transform, "Actions", new Vector2(.02f, .03f), new Vector2(.98f, .28f));
var actionLayout = actions.gameObject.AddComponent<HorizontalLayoutGroup>();
actionLayout.spacing = 10; actionLayout.childControlHeight = true; actionLayout.childControlWidth = true; actionLayout.childForceExpandWidth = true;
Button(actions, "Reroll1Button", "骰 1", Vector2.zero, Vector2.one, 17);
Button(actions, "Reroll2Button", "骰 2", Vector2.zero, Vector2.one, 17);
Button(actions, "Reroll3Button", "骰 3", Vector2.zero, Vector2.one, 17);
Button(actions, "PassButton", "跳过", Vector2.zero, Vector2.one, 18);
Button(actions, "SubmitButton", "锁定", Vector2.zero, Vector2.one, 19, new Color(.66f, .12f, .10f, 1));
Anchor(actions.gameObject, "rp.reroll");
PrefabUtility.SaveAsPrefabAsset(screen, BattlePrefabPath);
}
finally { UnityEngine.Object.DestroyImmediate(screen); }
}
private static void BuildModalPrefab()
{
var root = Screen("ModalOverlay");
try
{
root.AddComponent<CanvasGroup>();
var scrim = Image(root.transform, "Scrim", null, new Color(0, 0, 0, .72f), Vector2.zero, Vector2.one);
scrim.raycastTarget = true;
var panel = Panel(root.transform, "Panel", new Color(.11f, .12f, .12f, 1), new Vector2(.3f, .32f), new Vector2(.7f, .68f));
Text(panel.transform, "Title", "提示", 30, TextAlignmentOptions.Center, new Vector2(.08f, .67f), new Vector2(.92f, .94f));
Text(panel.transform, "Message", "", 20, TextAlignmentOptions.Center, new Vector2(.08f, .28f), new Vector2(.92f, .68f));
Button(panel.transform, "CloseButton", "确定", new Vector2(.32f, .08f), new Vector2(.68f, .25f), 20);
PrefabUtility.SaveAsPrefabAsset(root, ModalPrefabPath);
}
finally { UnityEngine.Object.DestroyImmediate(root); }
}
private static void BuildDemoPrefab()
{
var root = new GameObject("ReplacedPersonDemo", typeof(RectTransform), typeof(ReplacedPersonDemoController));
try
{
var canvasObject = new GameObject("Canvas", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvasObject.transform.SetParent(root.transform, false);
var canvas = canvasObject.GetComponent<Canvas>(); canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = canvasObject.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920, 1080); scaler.matchWidthOrHeight = .5f;
Stretch(canvasObject.GetComponent<RectTransform>());
var background = Image(canvasObject.transform, "Background", _paper, new Color(.32f, .35f, .36f, 1), Vector2.zero, Vector2.one);
background.preserveAspect = false;
var safe = FullRect(canvasObject.transform, "SafeAreaRoot", Vector2.zero, Vector2.one);
safe.gameObject.AddComponent<ReplacedSafeArea>();
var screens = FullRect(safe, "Screens", Vector2.zero, Vector2.one);
BuildTitle(screens);
BuildChapter(screens);
BuildDeck(screens);
BuildLan(screens);
var battlePrefab = AssetDatabase.LoadAssetAtPath<GameObject>(BattlePrefabPath) ?? throw new InvalidOperationException("Battle prefab missing.");
var battle = (GameObject)PrefabUtility.InstantiatePrefab(battlePrefab);
battle.transform.SetParent(screens, false); Stretch(battle.GetComponent<RectTransform>());
BuildReward(screens);
BuildSettings(screens);
PrefabUtility.SaveAsPrefabAsset(root, DemoPrefabPath);
}
finally { UnityEngine.Object.DestroyImmediate(root); }
}
private static void BuildTitle(Transform parent)
{
var screen = Screen("TitleScreen", parent);
Text(screen.transform, "Title", "被替代之人", 70, TextAlignmentOptions.Center, new Vector2(.08f, .70f), new Vector2(.92f, .94f));
Text(screen.transform, "Subtitle", "第一章 · 贪婪", 24, TextAlignmentOptions.Center, new Vector2(.15f, .62f), new Vector2(.85f, .72f));
var art = FullRect(screen.transform, "ArtTriptych", new Vector2(.08f, .20f), new Vector2(.58f, .62f));
var layout = art.gameObject.AddComponent<HorizontalLayoutGroup>(); layout.spacing = 20; layout.childControlHeight = true; layout.childControlWidth = true; layout.childForceExpandWidth = true;
Image(art, "Attack", _attack, Color.white, Vector2.zero, Vector2.one).preserveAspect = true;
Image(art, "Dodge", _dodge, Color.white, Vector2.zero, Vector2.one).preserveAspect = true;
Image(art, "Counter", _counter, Color.white, Vector2.zero, Vector2.one).preserveAspect = true;
var actions = FullRect(screen.transform, "Actions", new Vector2(.64f, .20f), new Vector2(.91f, .61f));
var vertical = actions.gameObject.AddComponent<VerticalLayoutGroup>();
vertical.spacing = 14; vertical.childControlWidth = true; vertical.childControlHeight = true; vertical.childForceExpandHeight = true;
Button(actions, "ContinueButton", "进入章节", Vector2.zero, Vector2.one, 25, new Color(.67f, .13f, .11f, 1));
Button(actions, "DeckButton", "牌组", Vector2.zero, Vector2.one, 22);
Button(actions, "LanButton", "局域网", Vector2.zero, Vector2.one, 22);
Button(actions, "SettingsButton", "设置", Vector2.zero, Vector2.one, 22);
Text(screen.transform, "Footer", "DEMO / WINDOWS PC / v1", 14, TextAlignmentOptions.BottomLeft, new Vector2(.03f, .02f), new Vector2(.7f, .09f));
}
private static void BuildChapter(Transform parent)
{
var screen = Screen("ChapterScreen", parent);
Header(screen.transform, "章节");
var content = FullRect(screen.transform, "Content", new Vector2(.10f, .17f), new Vector2(.90f, .88f));
Text(content, "ChapterLabel", "第一章 · 贪婪", 42, TextAlignmentOptions.TopLeft, new Vector2(0, .77f), Vector2.one);
Text(content, "StoryText", "", 26, TextAlignmentOptions.TopLeft, Vector2.zero, new Vector2(1, .76f));
var footer = FullRect(screen.transform, "Footer", new Vector2(.66f, .05f), new Vector2(.90f, .14f));
Button(footer, "StartBattleButton", "进入第一战", Vector2.zero, Vector2.one, 24, new Color(.67f, .13f, .11f, 1));
}
private static void BuildDeck(Transform parent)
{
var screen = Screen("DeckScreen", parent); Header(screen.transform, "牌组编辑");
var content = FullRect(screen.transform, "Content", new Vector2(.04f, .15f), new Vector2(.96f, .88f));
Text(content, "DeckStatus", "普通牌 12/12", 22, TextAlignmentOptions.Left, new Vector2(0, .91f), Vector2.one);
ScrollList(content, "CurrentDeck", "当前牌组 · 点击移除", new Vector2(0, 0), new Vector2(.48f, .89f));
ScrollList(content, "Collection", "收藏 · 点击加入", new Vector2(.52f, 0), new Vector2(1, .89f));
var footer = FullRect(screen.transform, "Footer", new Vector2(.62f, .04f), new Vector2(.96f, .12f));
var layout = footer.gameObject.AddComponent<HorizontalLayoutGroup>(); layout.spacing = 12; layout.childControlHeight = true; layout.childControlWidth = true;
Button(footer, "ResetButton", "重置", Vector2.zero, Vector2.one, 20);
Button(footer, "SaveButton", "保存", Vector2.zero, Vector2.one, 20, new Color(.67f, .13f, .11f, 1));
}
private static void BuildLan(Transform parent)
{
var screen = Screen("LanScreen", parent); Header(screen.transform, "局域网大厅");
var content = FullRect(screen.transform, "Content", new Vector2(.18f, .16f), new Vector2(.82f, .84f));
Text(content, "StatusText", "尚未连接", 24, TextAlignmentOptions.Center, new Vector2(.05f, .68f), new Vector2(.95f, .92f));
Input(content, "AddressInput", "127.0.0.1", new Vector2(.18f, .49f), new Vector2(.82f, .62f));
Button(content, "HostButton", "创建房间", new Vector2(.18f, .31f), new Vector2(.48f, .43f), 22);
Button(content, "JoinButton", "直接加入", new Vector2(.52f, .31f), new Vector2(.82f, .43f), 22, new Color(.67f, .13f, .11f, 1));
Button(content, "StartMatchButton", "进入对局", new Vector2(.34f, .16f), new Vector2(.66f, .27f), 21, new Color(.67f, .13f, .11f, 1));
Text(content, "DiscoveryText", "局域网发现会自动广播可用房间", 17, TextAlignmentOptions.Center, new Vector2(.1f, .01f), new Vector2(.9f, .14f));
}
private static void BuildReward(Transform parent)
{
var screen = Screen("RewardScreen", parent);
Text(screen.transform, "Title", "结算", 28, TextAlignmentOptions.Center, new Vector2(.2f, .87f), new Vector2(.8f, .96f));
var content = FullRect(screen.transform, "Content", new Vector2(.18f, .22f), new Vector2(.82f, .84f));
Text(content, "ResultText", "胜利", 52, TextAlignmentOptions.Center, new Vector2(.1f, .75f), new Vector2(.9f, .94f));
Text(content, "RewardText", "", 21, TextAlignmentOptions.TopLeft, new Vector2(.05f, .30f), new Vector2(.48f, .73f));
Text(content, "AppearanceText", "当前装饰:无", 20, TextAlignmentOptions.Left, new Vector2(.52f, .61f), new Vector2(.98f, .72f));
var ornaments = FullRect(content, "Ornaments", new Vector2(.52f, .30f), new Vector2(.98f, .59f));
var ornamentLayout = ornaments.gameObject.AddComponent<VerticalLayoutGroup>();
ornamentLayout.spacing = 8; ornamentLayout.childControlWidth = true; ornamentLayout.childControlHeight = true; ornamentLayout.childForceExpandHeight = true;
Button(ornaments, "GoldEyeButton", "金色势利眼", Vector2.zero, Vector2.one, 17);
Button(ornaments, "GoldChainButton", "金链", Vector2.zero, Vector2.one, 17);
Button(ornaments, "WornSuitButton", "褶皱西装", Vector2.zero, Vector2.one, 17);
var footer = FullRect(screen.transform, "Footer", new Vector2(.38f, .08f), new Vector2(.62f, .17f));
Button(footer, "ContinueButton", "继续", Vector2.zero, Vector2.one, 23, new Color(.67f, .13f, .11f, 1));
}
private static void BuildSettings(Transform parent)
{
var screen = Screen("SettingsScreen", parent); Header(screen.transform, "设置");
var content = FullRect(screen.transform, "Content", new Vector2(.24f, .25f), new Vector2(.76f, .78f));
Text(content, "TransportStatus", "LAN 传输:TCP", 28, TextAlignmentOptions.Center, new Vector2(.1f, .68f), new Vector2(.9f, .91f));
Button(content, "TcpButton", "TCP", new Vector2(.12f, .42f), new Vector2(.48f, .61f), 23);
Button(content, "KcpButton", "KCP", new Vector2(.52f, .42f), new Vector2(.88f, .61f), 23);
Text(content, "Note", "1920×1080 基准 · 支持 16:10 与 4:3\n拖牌与点牌共用同一提交队列", 18, TextAlignmentOptions.Center, new Vector2(.06f, .09f), new Vector2(.94f, .34f));
}
private static void Header(Transform screen, string title)
{
var header = Panel(screen, "Header", new Color(.05f, .055f, .06f, .94f), new Vector2(0, .90f), Vector2.one);
Button(header.transform, "BackButton", "", new Vector2(0, 0), new Vector2(.08f, 1), 34);
Text(header.transform, "Title", title, 30, TextAlignmentOptions.Left, new Vector2(.10f, 0), new Vector2(.9f, 1));
}
private static void ScrollList(Transform parent, string name, string title, Vector2 min, Vector2 max)
{
var root = Panel(parent, name, new Color(.08f, .085f, .09f, .94f), min, max);
Text(root.transform, "Title", title, 20, TextAlignmentOptions.Left, new Vector2(.04f, .90f), new Vector2(.96f, .99f));
var viewport = FullRect(root.transform, "Viewport", new Vector2(.03f, .03f), new Vector2(.97f, .88f));
viewport.gameObject.AddComponent<RectMask2D>();
var content = FullRect(viewport, "Content", new Vector2(0, 1), Vector2.one);
content.anchorMin = new Vector2(0, 1); content.anchorMax = new Vector2(1, 1); content.pivot = new Vector2(.5f, 1); content.sizeDelta = new Vector2(0, 0);
var layout = content.gameObject.AddComponent<VerticalLayoutGroup>();
layout.spacing = 6; layout.childControlWidth = true; layout.childControlHeight = true; layout.childForceExpandWidth = true; layout.childForceExpandHeight = false; layout.padding = new RectOffset(4, 4, 4, 4);
var fitter = content.gameObject.AddComponent<ContentSizeFitter>(); fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
var row = Button(content, "RowTemplate", "卡牌", Vector2.zero, Vector2.one, 17);
row.gameObject.AddComponent<LayoutElement>().preferredHeight = 54; row.gameObject.SetActive(false);
var scroll = root.gameObject.AddComponent<ScrollRect>(); scroll.viewport = viewport; scroll.content = content; scroll.horizontal = false; scroll.movementType = ScrollRect.MovementType.Clamped;
}
private static void CardTemplate(Transform parent, string name)
{
var button = Button(parent, name, "卡牌", Vector2.zero, Vector2.one, 14, new Color(.90f, .87f, .78f, .98f));
button.gameObject.AddComponent<ReplacedCardDragHandler>();
var layout = button.gameObject.AddComponent<LayoutElement>(); layout.preferredWidth = 132; layout.minWidth = 116;
var rect = button.GetComponent<RectTransform>(); rect.sizeDelta = new Vector2(132, 132);
button.gameObject.SetActive(false);
}
private static GameObject Screen(string name, Transform? parent = null)
{
var result = new GameObject(name, typeof(RectTransform));
if (parent != null) result.transform.SetParent(parent, false);
Stretch(result.GetComponent<RectTransform>());
return result;
}
private static RectTransform FullRect(Transform parent, string name, Vector2 min, Vector2 max)
{
var result = new GameObject(name, typeof(RectTransform)).GetComponent<RectTransform>();
result.SetParent(parent, false); result.anchorMin = min; result.anchorMax = max; result.offsetMin = Vector2.zero; result.offsetMax = Vector2.zero;
return result;
}
private static Image Panel(Transform parent, string name, Color color, Vector2 min, Vector2 max) => Image(parent, name, null, color, min, max);
private static Image Image(Transform parent, string name, Sprite? sprite, Color color, Vector2 min, Vector2 max)
{
var rect = FullRect(parent, name, min, max);
var image = rect.gameObject.AddComponent<Image>(); image.sprite = sprite; image.color = color; image.raycastTarget = false;
return image;
}
private static TMP_Text Text(Transform parent, string name, string value, float size, TextAlignmentOptions alignment,
Vector2 min, Vector2 max, Vector2? offsetMin = null, Vector2? offsetMax = null)
{
var rect = FullRect(parent, name, min, max); rect.offsetMin = offsetMin ?? Vector2.zero; rect.offsetMax = offsetMax ?? Vector2.zero;
var text = rect.gameObject.AddComponent<TextMeshProUGUI>(); text.font = _font; text.text = value; text.fontSize = size; text.alignment = alignment;
text.color = new Color(.92f, .91f, .86f, 1); text.enableWordWrapping = true; text.overflowMode = TextOverflowModes.Ellipsis; text.raycastTarget = false;
return text;
}
private static Button Button(Transform parent, string name, string label, Vector2 min, Vector2 max, float size, Color? color = null)
{
var image = Image(parent, name, null, color ?? new Color(.18f, .19f, .19f, .98f), min, max); image.raycastTarget = true;
var button = image.gameObject.AddComponent<Button>(); button.targetGraphic = image;
Text(image.transform, "Label", label, size, TextAlignmentOptions.Center, Vector2.zero, Vector2.one, new Vector2(8, 4), new Vector2(-8, -4));
return button;
}
private static TMP_InputField Input(Transform parent, string name, string placeholder, Vector2 min, Vector2 max)
{
var background = Image(parent, name, null, new Color(.92f, .90f, .84f, 1), min, max); background.raycastTarget = true;
var area = FullRect(background.transform, "TextArea", new Vector2(.04f, .1f), new Vector2(.96f, .9f)); area.gameObject.AddComponent<RectMask2D>();
var text = Text(area, "Text", string.Empty, 22, TextAlignmentOptions.Left, Vector2.zero, Vector2.one); text.color = Color.black;
var hint = Text(area, "Placeholder", placeholder, 22, TextAlignmentOptions.Left, Vector2.zero, Vector2.one); hint.color = new Color(.2f, .2f, .2f, .45f);
var input = background.gameObject.AddComponent<TMP_InputField>(); input.textViewport = area; input.textComponent = (TMP_Text)text; input.placeholder = hint;
return input;
}
private static void Anchor(GameObject target, string id)
{
var anchor = target.AddComponent<ShrinkTutorialAnchor>(); anchor.SetAnchorId(id);
}
private static void Stretch(RectTransform rect)
{
rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero;
}
private static void BuildTutorialAssets()
{
DeleteAssetIfExists(ResourceRoot + "/ReplacedPersonFirstMatchTutorial.asset");
DeleteAssetIfExists(ResourceRoot + "/ReplacedPersonTutorialDatabase.asset");
DeleteAssetIfExists(ResourceRoot + "/ShrinkTutorialSettings.asset");
DeleteAssetIfExists(ResourceRoot + "/ReplacedPersonTutorialSettings.asset");
var tutorial = ScriptableObject.CreateInstance<ShrinkTutorialData>();
tutorial.tutorialId = "replaced-person.first-match";
tutorial.steps = new List<ShrinkTutorialStep>
{
Step("rp.normal-hand", "rp.card-selected", "选择一张普通牌。牌面顺序会从左到右继承到结束牌。", ShrinkTutorialDialogAnchor.Top),
Step("rp.end-hand", "rp.end-selected", "选择结束牌。它提供三个效果槽,并锁定本次提交。", ShrinkTutorialDialogAnchor.Top),
Step("rp.matchup", "rp.matchup-submitted", "后手能看到先手牌序。对齐克制关系后再锁定。", ShrinkTutorialDialogAnchor.Bottom),
Step("rp.reroll", "rp.reroll-marked", "标记一个效果槽;初次骰点不高于 2 时会自动重掷一次。", ShrinkTutorialDialogAnchor.Top)
};
AssetDatabase.CreateAsset(tutorial, ResourceRoot + "/ReplacedPersonFirstMatchTutorial.asset");
var database = ScriptableObject.CreateInstance<ShrinkTutorialDatabase>();
var serialized = new SerializedObject(database); var tutorials = serialized.FindProperty("tutorials"); tutorials.arraySize = 1; tutorials.GetArrayElementAtIndex(0).objectReferenceValue = tutorial; serialized.ApplyModifiedPropertiesWithoutUndo();
AssetDatabase.CreateAsset(database, ResourceRoot + "/ReplacedPersonTutorialDatabase.asset");
var settings = ScriptableObject.CreateInstance<ShrinkTutorialSettings>(); settings.database = database; settings.playerPrefsStorageKey = "ReplacedPerson.Tutorial"; settings.showSkipButton = true; settings.canvasSortingOrder = 5000;
settings.name = "ReplacedPersonTutorialSettings";
AssetDatabase.CreateAsset(settings, ResourceRoot + "/ReplacedPersonTutorialSettings.asset");
}
private static ShrinkTutorialStep Step(string anchor, string signal, string text, ShrinkTutorialDialogAnchor dialogAnchor) => new()
{
targetMode = ShrinkTutorialTargetMode.AnchorId,
anchorId = anchor,
completeCondition = ShrinkTutorialCompleteCondition.CustomEvent,
customEventName = signal,
fallbackText = text,
dialogAnchor = dialogAnchor,
maskShape = ShrinkTutorialMaskShape.Rect,
maskPadding = 10,
waitForTarget = true,
waitTimeout = 3
};
private static void BuildScene()
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(DemoPrefabPath) ?? throw new InvalidOperationException("Demo prefab missing.");
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, scene); instance.name = "ReplacedPersonDemo";
var cameraObject = new GameObject("Main Camera", typeof(Camera), typeof(AudioListener));
cameraObject.tag = "MainCamera";
cameraObject.transform.position = new Vector3(0, 0, -10);
var camera = cameraObject.GetComponent<Camera>();
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(.02f, .025f, .03f, 1);
camera.cullingMask = 0;
camera.orthographic = true;
camera.depth = -100;
new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule));
EditorSceneManager.SaveScene(scene, ScenePath);
}
private static void AddToBuildSettings()
{
var scenes = EditorBuildSettings.scenes.ToList();
var existing = scenes.FindIndex(value => value.path == ScenePath);
if (existing >= 0) scenes[existing] = new EditorBuildSettingsScene(ScenePath, true);
else scenes.Add(new EditorBuildSettingsScene(ScenePath, true));
EditorBuildSettings.scenes = scenes.ToArray();
}
private static void DeleteAssetIfExists(string path) { if (AssetDatabase.LoadMainAssetAtPath(path) != null) AssetDatabase.DeleteAsset(path); }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ebf6b9d7b155d3547a01f5a0aa2b2154
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4e7dc26bd42476a4c8191a4363d6ca28
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f1b4f95bd66e1c84cb9dc77af99ee9cb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c18deeb181f6fc94d92fbd8276548949
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
{
"name": "ExampleGreedMod.Rules",
"rootNamespace": "ExampleGreedMod",
"references": ["ReplacedPerson.Core"],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 375cebea661eef74dae3870143ca3f6e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
#nullable enable
using System.Collections.Generic;
using ReplacedPerson.Core;
namespace ExampleGreedMod
{
public sealed class ExampleGreedRules : IReplacedPersonMod
{
public string Id => "example.greedy-training";
public string Version => "1.0.0";
public void Register(IReplacedPersonRegistry registry)
{
registry.RegisterCard(new ReplacedCardDefinition
{
Id = "example.audit",
Name = "审计",
Description = "测试卡:反击成功后额外恢复 1 COST。",
EffectType = ReplacedEffectType.Counter,
Cost = 2,
BaseValue = 4,
Factor = 2,
SpecialEffectId = "example.audit-refund"
});
registry.RegisterEffectResolver("example.audit-refund", new AuditRefundResolver());
registry.RegisterEnemy(new ReplacedEnemyDefinition
{
Id = "example.training",
Name = "训练用审计员",
MaxHealth = 18,
NormalDeck = new List<string>
{
"example.audit", "example.audit", "attack.kill", "attack.kill", "attack.elbow", "attack.elbow",
"dodge.flash", "dodge.flash", "counter.blood", "counter.blood", "greed.snob", "greed.snob"
},
EndDeck = new List<string> { "end.inherit", "end.ledger", "end.severance", "end.black" },
RewardIds = new List<string> { "example.audit" }
});
registry.RegisterReward("example.audit", "审计卡");
registry.RegisterStory("example.training.story", "审计训练", "账本上的每一行都在回望你。");
registry.RegisterCommand("example audit", _ => "audit-ready");
}
private sealed class AuditRefundResolver : IReplacedPersonEffectResolver
{
public ReplacedEffectResult Resolve(ReplacedEffectContext context) => new()
{
CostDelta = 1,
Log = "审计返还 1 COST"
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e717a2e339d03044a80d69f36999b23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 89bbdbc3beff793428dc10c25f88c27e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
{
"name": "ExampleGreedMod.Unity",
"rootNamespace": "ExampleGreedMod",
"references": ["ExampleGreedMod.Rules", "ReplacedPerson.Core", "ReplacedPerson.Runtime", "ShrinkModFramework.Runtime"],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 16ac024bec35a9143a014e25d8b5f634
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
#nullable enable
using ReplacedPerson.Core;
using ReplacedPerson.Runtime;
using ShrinkModFramework;
namespace ExampleGreedMod
{
[ShrinkMod("example.greedy-training", "贪婪训练 Mod", "1.0.0", AutoApplyHarmonyPatches = false)]
public sealed class ExampleGreedUnityMod : ShrinkModBase
{
public override void OnRegisterContent(ShrinkModContext context)
{
context.GetRegistry<IReplacedPersonMod>(ReplacedPersonModBridge.RegistryName)
.Register(context.ModInfo.ModId, context.ModInfo.ModId, new ExampleGreedRules());
}
public override void OnReady(ShrinkModContext context)
{
context.Log("Formal registry integration ready; no Harmony patching used.");
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b6c3206fd5108243b0e11bc7d4f0194
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,6 @@
{
"cards": ["example.audit"],
"effects": ["example.audit-refund"],
"enemies": ["example.training"],
"commands": ["example audit"]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ee633347fcb672343b380db07a6b05f4
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

@@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 5ec4baff1b62606418c49acbda4b0a50
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
{
"id": "example.greedy-training",
"version": "1.0.0",
"rulesAssembly": "ExampleGreedMod.Rules.dll",
"unityAssembly": "ExampleGreedMod.Unity.dll",
"content": "content.json",
"icon": "icon.png",
"networkCompatible": true,
"files": []
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9e393e811edc18443b507bf3a2112174
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 80fa129ba99db0c42b4a2ba7124be22d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 692b0f6e220f978478a4cbb5c68bb2b7
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,728 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &109461749179269432
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7134254617425657058}
- component: {fileID: 3922028287645489926}
- component: {fileID: 5260202726735049939}
m_Layer: 0
m_Name: Scrim
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7134254617425657058
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 109461749179269432}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4709999122157385881}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3922028287645489926
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 109461749179269432}
m_CullTransparentMesh: 1
--- !u!114 &5260202726735049939
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 109461749179269432}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.72}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &1071967189918236762
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4060912498424251528}
- component: {fileID: 6470522395222180645}
- component: {fileID: 7838437069278855208}
m_Layer: 0
m_Name: Title
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4060912498424251528
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1071967189918236762}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8277422716032291198}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.08, y: 0.67}
m_AnchorMax: {x: 0.92, y: 0.94}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6470522395222180645
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1071967189918236762}
m_CullTransparentMesh: 1
--- !u!114 &7838437069278855208
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1071967189918236762}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u63D0\u793A"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 13367afdf20e47f40b184469b5e36efd, type: 2}
m_sharedMaterial: {fileID: 8333056236493354663, guid: 13367afdf20e47f40b184469b5e36efd, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.92, g: 0.91, b: 0.86, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 30
m_fontSizeBase: 30
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 1
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &1193336366473257478
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3844761814136918061}
- component: {fileID: 996824119279668335}
- component: {fileID: 2500486358091292699}
m_Layer: 0
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3844761814136918061
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1193336366473257478}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8638408449368862406}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: -16, y: -8}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &996824119279668335
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1193336366473257478}
m_CullTransparentMesh: 1
--- !u!114 &2500486358091292699
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1193336366473257478}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: "\u786E\u5B9A"
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 13367afdf20e47f40b184469b5e36efd, type: 2}
m_sharedMaterial: {fileID: 8333056236493354663, guid: 13367afdf20e47f40b184469b5e36efd, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.92, g: 0.91, b: 0.86, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 20
m_fontSizeBase: 20
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 1
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &1305232677574263801
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2439426371679133391}
- component: {fileID: 4157319648314641653}
- component: {fileID: 7101854651374399573}
m_Layer: 0
m_Name: Message
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2439426371679133391
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1305232677574263801}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8277422716032291198}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.08, y: 0.28}
m_AnchorMax: {x: 0.92, y: 0.68}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4157319648314641653
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1305232677574263801}
m_CullTransparentMesh: 1
--- !u!114 &7101854651374399573
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1305232677574263801}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text:
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 13367afdf20e47f40b184469b5e36efd, type: 2}
m_sharedMaterial: {fileID: 8333056236493354663, guid: 13367afdf20e47f40b184469b5e36efd, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 0.92, g: 0.91, b: 0.86, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: 0
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 20
m_fontSizeBase: 20
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_enableWordWrapping: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 1
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 1
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &3028599952828932685
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8638408449368862406}
- component: {fileID: 9093197756632392431}
- component: {fileID: 8448248933604724443}
- component: {fileID: 571939219385614648}
m_Layer: 0
m_Name: CloseButton
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8638408449368862406
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3028599952828932685}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 3844761814136918061}
m_Father: {fileID: 8277422716032291198}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.32, y: 0.08}
m_AnchorMax: {x: 0.68, y: 0.25}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9093197756632392431
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3028599952828932685}
m_CullTransparentMesh: 1
--- !u!114 &8448248933604724443
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3028599952828932685}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.18, g: 0.19, b: 0.19, a: 0.98}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &571939219385614648
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3028599952828932685}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 8448248933604724443}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &5016995505581043146
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8277422716032291198}
- component: {fileID: 9109043046766128537}
- component: {fileID: 3344880143442789142}
m_Layer: 0
m_Name: Panel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8277422716032291198
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5016995505581043146}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4060912498424251528}
- {fileID: 2439426371679133391}
- {fileID: 8638408449368862406}
m_Father: {fileID: 4709999122157385881}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.3, y: 0.32}
m_AnchorMax: {x: 0.7, y: 0.68}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9109043046766128537
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5016995505581043146}
m_CullTransparentMesh: 1
--- !u!114 &3344880143442789142
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5016995505581043146}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.11, g: 0.12, b: 0.12, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &6064900120442679474
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4709999122157385881}
- component: {fileID: 4245676085209672744}
m_Layer: 0
m_Name: ModalOverlay
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4709999122157385881
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6064900120442679474}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7134254617425657058}
- {fileID: 8277422716032291198}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!225 &4245676085209672744
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6064900120442679474}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0fce8f35ff57b4a428f1c1a1e3a1de62
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More