Merge pull request 'master' (#96) from Alienjack/AxibugEmuOnline:master into master

Reviewed-on: sin365/AxibugEmuOnline#96
This commit is contained in:
sin365 2025-04-22 19:02:09 +08:00
commit ee9d54386b
27 changed files with 781 additions and 134 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,130 @@
fileFormatVersion: 2
guid: 994c93ed03dadee49974aebf3cb5365d
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: 0
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: 4
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: 4
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: 4
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: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,130 @@
fileFormatVersion: 2
guid: 630c435b903e3dc4e80038c0a531162d
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: 0
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: 4
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: 4
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: 4
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: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,130 @@
fileFormatVersion: 2
guid: c5a9535bb63e1f14f9a1528566864ab2
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: 0
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: 4
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: 4
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: 4
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: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,130 @@
fileFormatVersion: 2
guid: 28db37d78cad20f4397804dfa4572829
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: 0
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: 4
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: 4
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: 4
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: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -551,13 +551,13 @@ RectTransform:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4232056521759880276} m_GameObject: {fileID: 4232056521759880276}
m_LocalRotation: {x: 1, y: 0, z: 0, w: 0} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: -1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 4232056520494431727} m_Father: {fileID: 4232056520494431727}
m_LocalEulerAnglesHint: {x: 180, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1} m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}

View File

@ -1688,7 +1688,7 @@ RectTransform:
m_GameObject: {fileID: 5162569472849600096} m_GameObject: {fileID: 5162569472849600096}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: -1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 5970282275929291192} m_Father: {fileID: 5970282275929291192}

View File

@ -63,6 +63,12 @@ namespace AxibugEmuOnline.Client.ClientCore
{ {
return s_persistentRoot + "/" + emuPlatform.ToString(); return s_persistentRoot + "/" + emuPlatform.ToString();
} }
public static string UserPersistenDataPath(RomPlatformType emuPlatform)
{
return string.Format("{0}/{1}", PersistentDataPath(emuPlatform), user.userdata.UID);
}
public static string PersistentDataRoot() => s_persistentRoot; public static string PersistentDataRoot() => s_persistentRoot;
public static RomLib GetRomLib(RomPlatformType platform) public static RomLib GetRomLib(RomPlatformType platform)

View File

@ -1,14 +1,15 @@
using AxibugEmuOnline.Client.ClientCore; using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.Tools; using AxibugEmuOnline.Client.Tools;
using AxibugProtobuf; using AxibugProtobuf;
using MAME.Core;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace AxibugEmuOnline.Client namespace AxibugEmuOnline.Client
{ {
/// <summary> 存档文件管理类 </summary> /// <summary> 存档文件抽象类 </summary>
public class SaveFile public partial class SaveFile
{ {
public SavCloudApi CloudAPI => App.SavMgr.CloudApi; public SavCloudApi CloudAPI => App.SavMgr.CloudApi;
@ -28,8 +29,8 @@ namespace AxibugEmuOnline.Client
{ {
get get
{ {
var path = App.PersistentDataPath(EmuPlatform); var path = App.UserPersistenDataPath(EmuPlatform);
path = $"{path}/Slot/{EmuPlatform}/{RomID}"; path = $"{path}/Slot/{RomID}";
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
@ -38,7 +39,24 @@ namespace AxibugEmuOnline.Client
} }
} }
public bool IsBusy
{
get
{
if (FSM.CurrentState is DownloadingState) return true;
else if (FSM.CurrentState is UploadingState) return true;
return false;
}
}
public SimpleFSM<SaveFile>.State GetState()
{
return FSM.CurrentState;
}
public event Action OnSavSuccessed; public event Action OnSavSuccessed;
public event Action OnStateChanged;
/// <summary> 存档顺序号,用于判断云存档和本地存档的同步状态,是否存在冲突 </summary> /// <summary> 存档顺序号,用于判断云存档和本地存档的同步状态,是否存在冲突 </summary>
public uint Sequecen { get; private set; } public uint Sequecen { get; private set; }
@ -55,11 +73,12 @@ namespace AxibugEmuOnline.Client
EmuPlatform = platform; EmuPlatform = platform;
SlotIndex = slotIndex; SlotIndex = slotIndex;
FSM = new SimpleFSM<SaveFile>(this); FSM = new SimpleFSM<SaveFile>(this);
FSM.AddState<UnkownState>(); FSM.AddState<IdleState>();
FSM.AddState<CheckingState>(); FSM.AddState<CheckingState>();
FSM.AddState<DownloadingState>(); FSM.AddState<DownloadingState>();
FSM.AddState<UploadingState>(); FSM.AddState<UploadingState>();
FSM.AddState<SyncedState>(); FSM.AddState<SyncedState>();
FSM.OnStateChanged += FSM_OnStateChanged;
IsEmpty = !File.Exists(FilePath); IsEmpty = !File.Exists(FilePath);
@ -82,7 +101,12 @@ namespace AxibugEmuOnline.Client
streaming.Dispose(); streaming.Dispose();
} }
FSM.ChangeState<UnkownState>(); FSM.ChangeState<IdleState>();
}
private void FSM_OnStateChanged()
{
OnStateChanged?.Invoke();
} }
public void Update() public void Update()
@ -145,6 +169,8 @@ namespace AxibugEmuOnline.Client
public unsafe void Save(uint sequence, byte[] savData, byte[] screenShotData) public unsafe void Save(uint sequence, byte[] savData, byte[] screenShotData)
{ {
if (IsBusy) return;
var filePath = FilePath; var filePath = FilePath;
var header = new Header var header = new Header
@ -190,9 +216,9 @@ namespace AxibugEmuOnline.Client
/// </summary> /// </summary>
public void TrySync() public void TrySync()
{ {
if (FSM.CurrentState is not UnkownState && FSM.CurrentState is not SyncedState) return; if (FSM.CurrentState is not IdleState && FSM.CurrentState is not SyncedState) return;
FSM.ChangeState<CheckingState>(); FSM.ChangeState<CheckingNetworkState>();
} }
@ -210,14 +236,5 @@ namespace AxibugEmuOnline.Client
[FieldOffset(20)] [FieldOffset(20)]
public uint ScreenShotLength; public uint ScreenShotLength;
} }
public enum EnumState
{
Unkown,
Checking,
Downloading,
Uploading,
Synced
}
} }
} }

View File

@ -0,0 +1,21 @@
using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.Tools;
using System;
namespace AxibugEmuOnline.Client
{
public partial class SaveFile
{
public class CheckingNetworkState : SimpleFSM<SaveFile>.State
{
public override void OnUpdate()
{
if (App.network.isConnected)
{
FSM.ChangeState<CheckingState>();
}
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 288e73ce3cb773e46b7d37a53a3b43ee

View File

@ -4,45 +4,48 @@ using UnityEngine;
namespace AxibugEmuOnline.Client namespace AxibugEmuOnline.Client
{ {
public class CheckingState : SimpleFSM<SaveFile>.State public partial class SaveFile
{ {
private float m_timeOut; public class CheckingState : SimpleFSM<SaveFile>.State
public Protobuf_Mine_GameSavInfo NetData { get; private set; }
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{ {
m_timeOut = 5f; private float m_timeOut;
Host.CloudAPI.OnFetchGameSavList += CloudAPI_OnFetchGameSavList;
Host.CloudAPI.SendGetGameSavList(Host.RomID);
}
public override void OnExit(SimpleFSM<SaveFile>.State nextState) public Protobuf_Mine_GameSavInfo NetData { get; private set; }
{
Host.CloudAPI.OnFetchGameSavList -= CloudAPI_OnFetchGameSavList;
}
public override void OnUpdate() public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{
m_timeOut -= Time.deltaTime;
if (m_timeOut < 0) //已超时
{ {
FSM.ChangeState<UnkownState>(); m_timeOut = 5f;
Host.CloudAPI.OnFetchGameSavList += CloudAPI_OnFetchGameSavList;
Host.CloudAPI.SendGetGameSavList(Host.RomID);
} }
}
private void CloudAPI_OnFetchGameSavList(int romID, Protobuf_Mine_GameSavInfo[] savSlotData) public override void OnExit(SimpleFSM<SaveFile>.State nextState)
{
if (romID != Host.RomID) return;
NetData = savSlotData[Host.SlotIndex];
if (NetData == null) //云存档不存在,上传本地存档
{ {
FSM.ChangeState<UploadingState>(); Host.CloudAPI.OnFetchGameSavList -= CloudAPI_OnFetchGameSavList;
} }
else
public override void OnUpdate()
{ {
FSM.ChangeState<DownloadingState>(); m_timeOut -= Time.deltaTime;
if (m_timeOut < 0) //已超时
{
FSM.ChangeState<IdleState>();
}
}
private void CloudAPI_OnFetchGameSavList(int romID, Protobuf_Mine_GameSavInfo[] savSlotData)
{
if (romID != Host.RomID) return;
NetData = savSlotData[Host.SlotIndex];
if (NetData == null) //云存档不存在,上传本地存档
{
FSM.ChangeState<UploadingState>();
}
else
{
FSM.ChangeState<DownloadingState>();
}
} }
} }
} }

View File

@ -2,8 +2,11 @@
namespace AxibugEmuOnline.Client namespace AxibugEmuOnline.Client
{ {
public class ConflictState : SimpleFSM<SaveFile>.State public partial class SaveFile
{ {
public class ConflictState : SimpleFSM<SaveFile>.State
{
}
} }
} }

View File

@ -3,51 +3,54 @@ using AxibugEmuOnline.Client.Tools;
namespace AxibugEmuOnline.Client namespace AxibugEmuOnline.Client
{ {
public class DownloadingState : SimpleFSM<SaveFile>.State public partial class SaveFile
{ {
uint m_sequece; public class DownloadingState : SimpleFSM<SaveFile>.State
private AxiHttpProxy.SendDownLoadProxy m_downloadTask;
private AxiHttpProxy.SendDownLoadProxy m_downloadTaskImg;
public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{ {
var checkState = preState as CheckingState; uint m_sequece;
private AxiHttpProxy.SendDownLoadProxy m_downloadTask;
private AxiHttpProxy.SendDownLoadProxy m_downloadTaskImg;
var netData = checkState.NetData; public override void OnEnter(SimpleFSM<SaveFile>.State preState)
if (Host.Sequecen >= (uint)netData.Sequence)
{ {
FSM.ChangeState<ConflictState>(); var checkState = preState as CheckingState;
return;
var netData = checkState.NetData;
if (Host.Sequecen >= (uint)netData.Sequence)
{
FSM.ChangeState<ConflictState>();
return;
}
m_sequece = (uint)netData.Sequence;
m_downloadTask = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavUrl}");
m_downloadTaskImg = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavImgUrl}");
} }
m_sequece = (uint)netData.Sequence; public override void OnUpdate()
m_downloadTask = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavUrl}");
m_downloadTaskImg = AxiHttpProxy.GetDownLoad($"{App.httpAPI.WebHost}/{netData.SavImgUrl}");
}
public override void OnUpdate()
{
if (!m_downloadTask.downloadHandler.isDone) return;
if (m_downloadTask.downloadHandler.bHadErr) //下载失败
{ {
FSM.ChangeState<UnkownState>(); if (!m_downloadTask.downloadHandler.isDone) return;
return;
if (m_downloadTask.downloadHandler.bHadErr) //下载失败
{
FSM.ChangeState<IdleState>();
return;
}
if (!m_downloadTaskImg.downloadHandler.isDone) return;
if (m_downloadTaskImg.downloadHandler.bHadErr) //下载失败
{
FSM.ChangeState<IdleState>();
return;
}
var savData = Host.CloudAPI.UnGzipData(m_downloadTask.downloadHandler.data);
var imgData = Host.CloudAPI.UnGzipData(m_downloadTaskImg.downloadHandler.data);
Host.Save(m_sequece, savData, imgData);
FSM.ChangeState<SyncedState>();
} }
if (!m_downloadTaskImg.downloadHandler.isDone) return;
if (m_downloadTaskImg.downloadHandler.bHadErr) //下载失败
{
FSM.ChangeState<UnkownState>();
return;
}
var savData = Host.CloudAPI.UnGzipData(m_downloadTask.downloadHandler.data);
var imgData = Host.CloudAPI.UnGzipData(m_downloadTaskImg.downloadHandler.data);
Host.Save(m_sequece, savData, imgData);
FSM.ChangeState<SyncedState>();
} }
} }
} }

View File

@ -0,0 +1,11 @@
using AxibugEmuOnline.Client.Tools;
namespace AxibugEmuOnline.Client
{
public partial class SaveFile
{
public class IdleState : SimpleFSM<SaveFile>.State
{
}
}
}

View File

@ -2,7 +2,10 @@
namespace AxibugEmuOnline.Client namespace AxibugEmuOnline.Client
{ {
public class SyncedState : SimpleFSM<SaveFile>.State public partial class SaveFile
{ {
public class SyncedState : SimpleFSM<SaveFile>.State
{
}
} }
} }

View File

@ -1,8 +0,0 @@
using AxibugEmuOnline.Client.Tools;
namespace AxibugEmuOnline.Client
{
public class UnkownState : SimpleFSM<SaveFile>.State
{
}
}

View File

@ -2,27 +2,30 @@
namespace AxibugEmuOnline.Client namespace AxibugEmuOnline.Client
{ {
public class UploadingState : SimpleFSM<SaveFile>.State public partial class SaveFile
{ {
public override void OnEnter(SimpleFSM<SaveFile>.State preState) public class UploadingState : SimpleFSM<SaveFile>.State
{ {
Host.CloudAPI.OnUploadedSavData += Api_OnUploadedSavData; public override void OnEnter(SimpleFSM<SaveFile>.State preState)
{
Host.CloudAPI.OnUploadedSavData += Api_OnUploadedSavData;
Host.GetSavData(out byte[] savData, out byte[] screenData); Host.GetSavData(out byte[] savData, out byte[] screenData);
Host.CloudAPI.SendUpLoadGameSav(Host.RomID, Host.SlotIndex, Host.Sequecen, savData, screenData); Host.CloudAPI.SendUpLoadGameSav(Host.RomID, Host.SlotIndex, Host.Sequecen, savData, screenData);
} }
public override void OnExit(SimpleFSM<SaveFile>.State nextState) public override void OnExit(SimpleFSM<SaveFile>.State nextState)
{ {
Host.CloudAPI.OnUploadedSavData -= Api_OnUploadedSavData; Host.CloudAPI.OnUploadedSavData -= Api_OnUploadedSavData;
} }
private void Api_OnUploadedSavData(int romID, int slotIndex, AxibugProtobuf.Protobuf_Mine_GameSavInfo savInfo) private void Api_OnUploadedSavData(int romID, int slotIndex, AxibugProtobuf.Protobuf_Mine_GameSavInfo savInfo)
{ {
if (Host.RomID != romID) return; if (Host.RomID != romID) return;
if (Host.SlotIndex != slotIndex) return; if (Host.SlotIndex != slotIndex) return;
FSM.ChangeState<SyncedState>(); FSM.ChangeState<SyncedState>();
}
} }
} }
} }

View File

@ -4,10 +4,10 @@ using System.Linq;
namespace AxibugEmuOnline.Client.Tools namespace AxibugEmuOnline.Client.Tools
{ {
public class SimpleFSM<HOST> public partial class SimpleFSM<HOST>
{ {
public event Action OnStateChanged;
private Dictionary<Type, State> m_states = new Dictionary<Type, State>(); private Dictionary<Type, State> m_states = new Dictionary<Type, State>();
private State m_current;
public HOST Host { get; private set; } public HOST Host { get; private set; }
@ -16,7 +16,18 @@ namespace AxibugEmuOnline.Client.Tools
Host = host; Host = host;
} }
public State CurrentState => m_current; private State m_current;
public State CurrentState
{
get => m_current;
set
{
if (m_current == value) return;
m_current = value;
OnStateChanged?.Invoke();
}
}
public T AddState<T>() where T : State, new() public T AddState<T>() where T : State, new()
{ {
var stateType = typeof(T); var stateType = typeof(T);

View File

@ -46,6 +46,13 @@ namespace AxibugEmuOnline.Client
m_subOptions.Add(new LoadMenuItem(inGameui, savFile)); m_subOptions.Add(new LoadMenuItem(inGameui, savFile));
} }
public override void OnShow(OptionUI_MenuItem ui)
{
base.OnShow(ui);
SavFile.TrySync();
}
protected override List<InternalOptionMenu> GetOptionMenus() protected override List<InternalOptionMenu> GetOptionMenus()
{ {
return m_subOptions; return m_subOptions;
@ -65,9 +72,16 @@ namespace AxibugEmuOnline.Client
public override void OnExcute(OptionUI optionUI, ref bool cancelHide) public override void OnExcute(OptionUI optionUI, ref bool cancelHide)
{ {
if (m_savFile.IsBusy)
{
OverlayManager.PopTip("存档正在同步中");
cancelHide = true;
return;
}
var stateData = m_ingameUI.Core.GetStateBytes(); var stateData = m_ingameUI.Core.GetStateBytes();
var tex = m_ingameUI.Core.OutputPixel; var tex = m_ingameUI.Core.OutputPixel;
var screenData = tex.ToJPG(); var screenData = tex.ToJPG(m_ingameUI.Core.DrawCanvas.transform.localScale);
m_savFile.Save(m_savFile.Sequecen, stateData, screenData); m_savFile.Save(m_savFile.Sequecen, stateData, screenData);
} }

View File

@ -439,7 +439,6 @@ namespace AxibugEmuOnline.Client
public abstract string Name { get; } public abstract string Name { get; }
public virtual Sprite Icon { get; } public virtual Sprite Icon { get; }
public virtual bool Visible => true; public virtual bool Visible => true;
public virtual bool Enable => true;
public virtual void OnFocus() { } public virtual void OnFocus() { }
public virtual void OnShow(OptionUI_MenuItem ui) { } public virtual void OnShow(OptionUI_MenuItem ui) { }

View File

@ -1,4 +1,7 @@
using UnityEngine; using AxibugEmuOnline.Client.Tools;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using static AxibugEmuOnline.Client.InGameUI_SaveStateMenu; using static AxibugEmuOnline.Client.InGameUI_SaveStateMenu;
@ -10,15 +13,38 @@ namespace AxibugEmuOnline.Client
public Image UI_Empty; public Image UI_Empty;
public Text UI_SavTime; public Text UI_SavTime;
public GameObject UI_Disconnect;
public GameObject UI_Syncing;
public GameObject UI_Conflict;
public GameObject UI_Synced;
Texture2D m_screenTex; Texture2D m_screenTex;
Dictionary<Type, GameObject> m_stateNodes = new Dictionary<Type, GameObject>();
private void Awake()
{
m_stateNodes[typeof(SaveFile.CheckingState)] = UI_Syncing;
m_stateNodes[typeof(SaveFile.ConflictState)] = UI_Conflict;
m_stateNodes[typeof(SaveFile.DownloadingState)] = UI_Syncing;
m_stateNodes[typeof(SaveFile.SyncedState)] = UI_Synced;
m_stateNodes[typeof(SaveFile.UploadingState)] = UI_Syncing;
m_stateNodes[typeof(SaveFile.CheckingNetworkState)] = UI_Disconnect;
}
protected override void OnSetData(InternalOptionMenu menuData) protected override void OnSetData(InternalOptionMenu menuData)
{ {
base.OnSetData(menuData); base.OnSetData(menuData);
RefreshUI(); RefreshUI();
MenuData.SavFile.OnSavSuccessed += SavFile_OnSavSuccessed;
MenuData.SavFile.OnStateChanged += UpdateStateNode;
}
MenuData.SavFile.OnSavSuccessed += RefreshUI; private void SavFile_OnSavSuccessed()
{
MenuData.SavFile.TrySync();
RefreshUI();
} }
private void RefreshUI() private void RefreshUI()
@ -48,6 +74,21 @@ namespace AxibugEmuOnline.Client
m_screenTex.LoadImage(screenShotData); m_screenTex.LoadImage(screenShotData);
UI_ScreenShot.texture = m_screenTex; UI_ScreenShot.texture = m_screenTex;
} }
UpdateStateNode();
}
private void UpdateStateNode()
{
var stateType = MenuData.SavFile.GetState().GetType();
foreach (var item in m_stateNodes)
{
var type = item.Key;
var nodeGo = item.Value;
nodeGo.SetActiveEx(type == stateType);
}
} }
public override void OnHide() public override void OnHide()
@ -60,7 +101,8 @@ namespace AxibugEmuOnline.Client
m_screenTex = null; m_screenTex = null;
} }
MenuData.SavFile.OnSavSuccessed -= RefreshUI; MenuData.SavFile.OnSavSuccessed -= SavFile_OnSavSuccessed;
MenuData.SavFile.OnStateChanged -= UpdateStateNode;
} }
public override void OnExecute(OptionUI optionUI, ref bool cancelHide) public override void OnExecute(OptionUI optionUI, ref bool cancelHide)

View File

@ -55,29 +55,26 @@ namespace AxibugEmuOnline.Client
} }
public static byte[] ToJPG(this Texture texture) public static byte[] ToJPG(this Texture texture, Vector2 scale)
{ {
Texture2D outputTex = null; Texture2D outputTex = ConvertFromRenderTexture(texture, scale);
if (texture is RenderTexture rt)
{
outputTex = ConvertFromRenderTexture(rt);
}
else if (texture is Texture2D)
{
outputTex = texture as Texture2D;
}
return outputTex.EncodeToJPG(); return outputTex.EncodeToJPG();
} }
private static Texture2D ConvertFromRenderTexture(RenderTexture rt) private static Texture2D ConvertFromRenderTexture(Texture src, Vector2 scale)
{ {
float offsetX = (scale.x < 0) ? 1 : 0;
float offsetY = (scale.y < 0) ? 1 : 0;
var offset = new Vector2(offsetX, offsetY);
// 创建临时RenderTexture并拷贝内容 // 创建临时RenderTexture并拷贝内容
RenderTexture tempRT = RenderTexture.GetTemporary(rt.width, rt.height, 0, RenderTextureFormat.ARGB32); RenderTexture tempRT = RenderTexture.GetTemporary(src.width, src.height, 0, RenderTextureFormat.ARGB32);
Graphics.Blit(rt, tempRT); Graphics.Blit(src, tempRT, scale, offset);
// 读取到Texture2D // 读取到Texture2D
Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGBA32, false); Texture2D tex = new Texture2D(src.width, src.height, TextureFormat.RGBA32, false);
RenderTexture.active = tempRT; RenderTexture.active = tempRT;
tex.ReadPixels(new Rect(0, 0, tempRT.width, tempRT.height), 0, 0); tex.ReadPixels(new Rect(0, 0, tempRT.width, tempRT.height), 0, 0);
tex.Apply(); tex.Apply();