Commit 92558f94 authored by 16서원빈's avatar 16서원빈

Basic judge and note generation

To be refactored
parent 1f86687b
...@@ -20,7 +20,7 @@ GameObject: ...@@ -20,7 +20,7 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 224168227853942078} - component: {fileID: 224168227853942078}
m_Layer: 5 m_Layer: 5
m_Name: Long Button Note m_Name: LBT
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
...@@ -189,10 +189,10 @@ RectTransform: ...@@ -189,10 +189,10 @@ RectTransform:
m_Father: {fileID: 224168227853942078} m_Father: {fileID: 224168227853942078}
m_RootOrder: 1 m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0.5, y: 1} m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 15, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 30, y: 0} m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!224 &224045426401352174 --- !u!224 &224045426401352174
RectTransform: RectTransform:
...@@ -207,9 +207,9 @@ RectTransform: ...@@ -207,9 +207,9 @@ RectTransform:
m_Father: {fileID: 224168227853942078} m_Father: {fileID: 224168227853942078}
m_RootOrder: 2 m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0.5, y: 1} m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 30, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 3, y: 0} m_SizeDelta: {x: 3, y: 0}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!224 &224168227853942078 --- !u!224 &224168227853942078
...@@ -246,8 +246,8 @@ RectTransform: ...@@ -246,8 +246,8 @@ RectTransform:
m_Father: {fileID: 224168227853942078} m_Father: {fileID: 224168227853942078}
m_RootOrder: 0 m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0.5, y: 1} m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 3, y: 0} m_SizeDelta: {x: 3, y: 0}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
...@@ -38,7 +38,7 @@ RenderSettings: ...@@ -38,7 +38,7 @@ RenderSettings:
m_ReflectionIntensity: 1 m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0} m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0} m_Sun: {fileID: 0}
m_IndirectSpecularColor: {r: 0.45120573, g: 0.50094587, b: 0.5736755, a: 1} m_IndirectSpecularColor: {r: 0.45120555, g: 0.5009458, b: 0.5736756, a: 1}
--- !u!157 &3 --- !u!157 &3
LightmapSettings: LightmapSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
...@@ -385,8 +385,6 @@ MonoBehaviour: ...@@ -385,8 +385,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 2fcba0157b527de4e89b842943a90af3, type: 3} m_Script: {fileID: 11500000, guid: 2fcba0157b527de4e89b842943a90af3, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
latency: 225
offset: {fileID: 2001722606}
--- !u!114 &750753105 --- !u!114 &750753105
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
...@@ -504,6 +502,39 @@ RectTransform: ...@@ -504,6 +502,39 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0} m_Pivot: {x: 0, y: 0}
--- !u!1 &791052774
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 791052775}
m_Layer: 5
m_Name: Noteobj
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &791052775
RectTransform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 791052774}
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_Children: []
m_Father: {fileID: 2001722607}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 1.5, y: 0}
m_SizeDelta: {x: 3, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1073141666 --- !u!1 &1073141666
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
...@@ -878,7 +909,7 @@ GameObject: ...@@ -878,7 +909,7 @@ GameObject:
- component: {fileID: 1665813942} - component: {fileID: 1665813942}
- component: {fileID: 1665813941} - component: {fileID: 1665813941}
m_Layer: 5 m_Layer: 5
m_Name: Guage m_Name: Motion Guage
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
...@@ -1123,7 +1154,8 @@ RectTransform: ...@@ -1123,7 +1154,8 @@ RectTransform:
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_Children: [] m_Children:
- {fileID: 791052775}
m_Father: {fileID: 295245170} m_Father: {fileID: 295245170}
m_RootOrder: 1 m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
......
...@@ -11,8 +11,11 @@ public class GameManager : MonoBehaviour { ...@@ -11,8 +11,11 @@ public class GameManager : MonoBehaviour {
{ {
if (instance == null) if (instance == null)
{ {
instance = FindObjectOfType(typeof(GameManager)) as GameManager; instance = FindObjectOfType(typeof(GameManager))
instance.CurrentTrack = new TrackInfo("Assets/Tracks/Tutorial/tutorial1.bpe"); as GameManager;
instance.CurrentTrack
= new TrackInfo("Assets/Tracks/Tutorial/tutorial1.bpe");
} }
return instance; return instance;
...@@ -21,6 +24,9 @@ public class GameManager : MonoBehaviour { ...@@ -21,6 +24,9 @@ public class GameManager : MonoBehaviour {
public TrackInfo CurrentTrack { get; set; } public TrackInfo CurrentTrack { get; set; }
public int Combo { get; set; }
public int Score { get; set; }
// Use this for initialization // Use this for initialization
void Start() void Start()
{ {
......
...@@ -10,30 +10,48 @@ public class InputManager : MonoBehaviour { ...@@ -10,30 +10,48 @@ public class InputManager : MonoBehaviour {
{ {
if (instance == null) if (instance == null)
{ {
instance = FindObjectOfType(typeof(InputManager)) as InputManager; instance = FindObjectOfType(typeof(InputManager))
as InputManager;
} }
return instance; return instance;
} }
} }
public bool IsButtonDown { get; private set; } private bool IsButtonDown { get; set; }
// Use this for initialization public bool IsButtonPressed
void Start() {
get
{ {
return !IsButtonDown && Input.anyKey;
}
} }
// Update is called once per frame public bool IsButtonReleased
void Update() {
get
{
return IsButtonDown && !Input.anyKey;
}
}
public bool IsButtonHolding
{ {
if (Input.anyKey) get
{ {
IsButtonDown = true; return IsButtonDown && Input.anyKey;
} }
else }
// Use this for initialization
void Start()
{ {
IsButtonDown = false;
} }
// Update is called once per frame
void Update()
{
IsButtonDown = Input.anyKey;
} }
} }
\ No newline at end of file
...@@ -8,16 +8,16 @@ public class Judge ...@@ -8,16 +8,16 @@ public class Judge
{ {
public static readonly List<Judge> JudgeList = new List<Judge> public static readonly List<Judge> JudgeList = new List<Judge>
{ {
new Judge("PERFECT") { ButtonTimingRange = 80f, Score = 10, Color = Color.cyan }, new Judge("PERFECT") { ButtonTimingRange = 80f, Score = 2, Color = Color.cyan },
new Judge("GOOD") { ButtonTimingRange = 100f, Score = 7, Color = Color.yellow }, new Judge("GOOD") { ButtonTimingRange = 100f, Score = 1, Color = Color.yellow },
new Judge("BAD") { ButtonTimingRange = 120f, Score = 3, Color = Color.blue, IsBreak = true }, new Judge("BAD") { ButtonTimingRange = 120f, Color = Color.blue, IsBreak = true },
new Judge("MISS") { Score = 0, Color = Color.red, IsBreak = true } new Judge("MISS") { Color = Color.red, IsBreak = true }
}; };
private Judge() private Judge()
{ {
ButtonTimingRange = 0f; ButtonTimingRange = 0f;
MotionTimingRange = 0f; MotionTimingRange = ButtonTimingRange;
Score = 0; Score = 0;
IsBreak = false; IsBreak = false;
Color = Color.black; Color = Color.black;
...@@ -29,18 +29,22 @@ public class Judge ...@@ -29,18 +29,22 @@ public class Judge
public float ButtonTimingRange { get; private set; } public float ButtonTimingRange { get; private set; }
public float MotionTimingRange { get; private set; } public float MotionTimingRange { get; private set; }
public float Score { get; private set; } public int Score { get; private set; }
public bool IsBreak { get; private set; } public bool IsBreak { get; private set; }
public Color Color { get; private set; } public Color Color { get; private set; }
public static readonly Judge MISS = JudgeList.Last(); public static readonly Judge MISS = JudgeList.Last();
private static readonly float MaxButtonTimingRange = JudgeList[2].ButtonTimingRange; private static readonly
float MaxButtonTimingRange = JudgeList[2].ButtonTimingRange;
public static Judge TestJudge(float elapsedTime) public static Judge TestJudge(Note note, float elapsedTime, bool end = false)
{ {
float timing = end ? note.EndTiming : note.StartTiming;
float difference = Mathf.Abs(elapsedTime - timing);
foreach (Judge judge in JudgeList) foreach (Judge judge in JudgeList)
{ {
if (Mathf.Abs(elapsedTime) < judge.ButtonTimingRange) if (difference < judge.ButtonTimingRange)
{ {
return judge; return judge;
} }
...@@ -49,8 +53,9 @@ public class Judge ...@@ -49,8 +53,9 @@ public class Judge
return JudgeList.Last(); return JudgeList.Last();
} }
public bool IsNonEmptyMiss(float elapsedTime) public static bool IsNonEmptyMiss(Note note, float elapsedTime, bool end = false)
{ {
return this == MISS && elapsedTime > MaxButtonTimingRange; float timing = end ? note.EndTiming : note.StartTiming;
return elapsedTime - timing > MaxButtonTimingRange;
} }
} }
\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
public class JudgeManager : MonoBehaviour public class JudgeManager : MonoBehaviour
{ {
...@@ -12,17 +13,22 @@ public class JudgeManager : MonoBehaviour ...@@ -12,17 +13,22 @@ public class JudgeManager : MonoBehaviour
{ {
if (instance == null) if (instance == null)
{ {
instance = FindObjectOfType(typeof(JudgeManager)) as JudgeManager; instance = FindObjectOfType(typeof(JudgeManager))
as JudgeManager;
} }
return instance; return instance;
} }
} }
[SerializeField]
float latency = 225f; float latency = 225f;
[SerializeField] float scrollMultiplier = 1.0f;
GameObject offset;
private GameObject offset, noteobj;
private GameObject motionGuage;
private GameObject judgeText;
private Vector3 initialPos;
private float elapsedTime = 0; private float elapsedTime = 0;
...@@ -34,27 +40,134 @@ public class JudgeManager : MonoBehaviour ...@@ -34,27 +40,134 @@ public class JudgeManager : MonoBehaviour
} }
} }
private float ScrollSpeed
{
get
{
return scrollMultiplier * MsPerBeat / 1000f;
}
}
// Use this for initialization // Use this for initialization
void Start() void Start()
{ {
offset = GameObject.Find("Offset");
noteobj = GameObject.Find("Noteobj");
judgeText = GameObject.Find("Judge");
motionGuage = GameObject.Find("Motion Guage");
initialPos = offset.transform.position;
judgeText.SetActive(false);
motionGuage.transform.parent.gameObject.SetActive(false);
LoadNotes(GameManager.Instance.CurrentTrack.Notes);
} }
// Update is called once per frame // Update is called once per frame
void Update() void Update()
{ {
elapsedTime += Time.deltaTime * 1000; elapsedTime += Time.deltaTime * 1000;
offset.transform.Translate(-Time.deltaTime * MsPerBeat, 0, 0); float timing = elapsedTime + latency;
offset.transform.position = new Vector3(
-timing * ScrollSpeed, 0, 0);
if (noteobj.transform.childCount <= 0)
return;
if (InputManager.Instance.IsButtonDown) GameObject obj = noteobj.transform.GetChild(0).gameObject;
Note note = obj.GetComponent<Note.Controller>().Instance;
if (note.IsLong && note.Activated)
{
if (InputManager.Instance.IsButtonHolding)
{
if(Judge.IsNonEmptyMiss(note, timing, true))
{
SetJudge(Judge.MISS);
DeactivateNote(note);
}
return;
}
if (InputManager.Instance.IsButtonReleased)
{ {
Judge judge = Judge.TestJudge(elapsedTime + latency); SetJudge(Judge.TestJudge(note, timing, true));
DeactivateNote(note);
return;
}
}
if (!judge.IsBreak) Judge judge = Judge.TestJudge(note, timing);
if (Judge.IsNonEmptyMiss(note, timing))
{
SetJudge(judge);
DeactivateNote(note);
}
if (InputManager.Instance.IsButtonPressed)
{
SetJudge(judge);
if (judge == Judge.MISS) // Empty Miss
{ {
return;
} }
if (note.IsLong)
note.Activated = true;
else else
DeactivateNote(note);
}
}
private void DeactivateNote(Note note)
{ {
note.Activated = false;
note.Component.transform.SetParent(offset.transform);
note.Component.Deactivate();
} }
private void SetJudge(Judge judge)
{
if (!judge.IsBreak)
GameManager.Instance.Combo++;
GameManager.Instance.Score += judge.Score;
Debug.Log(judge.Name);
judgeText.SetActive(true);
judgeText.GetComponent<Text>().text = judge.Name;
judgeText.GetComponent<Text>().color = judge.Color;
}
private void LoadNotes(List<Note> notes)
{
foreach (Note note in notes) {
GameObject obj = Instantiate(
Resources.Load(note.Type.ToString(), typeof(GameObject)),
noteobj.transform)
as GameObject;
if (note.IsLong)
{
float length = note.Length * ScrollSpeed;
var holdTransform = obj.transform.GetChild(1)
.GetComponent<RectTransform>();
var endTransform = obj.transform.GetChild(2)
.GetComponent<RectTransform>();
holdTransform.SetSizeWithCurrentAnchors(
RectTransform.Axis.Horizontal, length);
holdTransform.position += new Vector3(length / 2, 0, 0);
endTransform.position += new Vector3(length, 0, 0);
}
obj.transform.position += initialPos + new Vector3(
note.StartTiming * ScrollSpeed, 0, 0);
obj.AddComponent<Note.Controller>().Instance = note;
} }
} }
} }
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public enum NoteType { SBT, LBT, SMO, LMO }
public class Note
{
public class Controller : MonoBehaviour
{
public Note Instance { get; set; }
private float time = 0f;
private readonly float minAlpha = 0.3f;
private readonly float maxAlpha = 0.7f;
// Use this for initialization
void Start()
{
Instance.Component = this;
}
// Update is called once per frame
void Update()
{
if (Instance.Activated)
{
time += Time.deltaTime * 3;
foreach (Image img in GetComponentsInChildren<Image>())
{
Color tmp = img.color;
tmp.a = 0f;
img.color = tmp + new Color(0, 0, 0,
(maxAlpha + minAlpha) / 2
+ Mathf.Cos(time * 4) * (maxAlpha - minAlpha) / 2);
}
}
}
public void Deactivate()
{
foreach (Image img in GetComponentsInChildren<Image>())
{
Color tmp = img.color;
tmp.a = 0f;
img.color = tmp + new Color(0, 0, 0, minAlpha);
}
}
}
public Controller Component { get; private set; }
public float StartTiming { get; private set; }
public float EndTiming { get; private set; }
public NoteType Type { get; private set; }
public string Key { get; private set; }
public bool Activated { get; set; }
public bool IsLong
{
get
{
return Length != 0f;
}
}
public float Length
{
get
{
return EndTiming - StartTiming;
}
}
public Note(float start, float end)
{
StartTiming = start;
if (end == 0f)
EndTiming = start;
else
EndTiming = end;
}
public Note(NoteType type, float start, float end = 0f)
: this(start, end)
{
Type = type;
}
public Note(string type, float start, float end = 0f)
: this(start, end)
{
switch (type)
{
case "SBT":
Type = NoteType.SBT;
break;
case "LBT":
Type = NoteType.LBT;
break;
case "SMO":
Type = NoteType.SMO;
break;
case "LMO":
Type = NoteType.LMO;
break;
default:
break;
}
}
public Note(string key, string type, float start, float end = 0f)
: this(type, start, end)
{
Key = key;
}
}
fileFormatVersion: 2
guid: 338f557d1ce40744ca3e370dc683577e
timeCreated: 1502086841
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using UnityEngine; using UnityEngine;
public class TrackInfo public class TrackInfo
...@@ -11,13 +12,16 @@ public class TrackInfo ...@@ -11,13 +12,16 @@ public class TrackInfo
public float BPM { get; private set; } public float BPM { get; private set; }
public int Level { get; private set; } public int Level { get; private set; }
public TrackInfo(string path) : this(new FileInfo(path)) { } public List<Note> Notes { get; private set; }
public TrackInfo(string path): this(new FileInfo(path)) { }
public TrackInfo(FileInfo file) public TrackInfo(FileInfo file)
{ {
ParseBPE(file); ParseBPEHeader(file);
Notes = ParseBPENote(file);
} }
private void ParseBPE(FileInfo file) private void ParseBPEHeader(FileInfo file)
{ {
using (StreamReader reader using (StreamReader reader
= new StreamReader(new BufferedStream(file.OpenRead()))) = new StreamReader(new BufferedStream(file.OpenRead())))
...@@ -57,29 +61,74 @@ public class TrackInfo ...@@ -57,29 +61,74 @@ public class TrackInfo
else else
Level = 0; Level = 0;
break; break;
default:
ParseNote(field, value);
break;
} }
} }
} }
} }
private void ParseNote(string field, string value) public List<Note> ParseBPENote(FileInfo file)
{
List<Note> notes = new List<Note>();
float? tempLongStart = null;
using (StreamReader reader
= new StreamReader(new BufferedStream(file.OpenRead())))
{
string line;
while ((line = reader.ReadLine()) != null)
{ {
if (line == "" || line[0] != '#')
continue;
string[] token = line.Split(new char[] { ' ' }, 2);
string field = token[0];
string value = token[1].Trim();
if (field.Length != 7) if (field.Length != 7)
return; continue;
string[] CHANNELS = { "SBT", "LBT", "SMN", "LMN" }; string[] CHANNELS = { "SBT", "LBT", "SMO", "LMO" };
int measure; int measure;
string channel = field.Substring(4, 3); string channel = field.Substring(4, 3);
if (!int.TryParse(field.Substring(1, 3), out measure)) if (!int.TryParse(field.Substring(1, 3), out measure))
return; continue;
if (Array.FindIndex(CHANNELS, x => x == value) == -1) if (Array.FindIndex(CHANNELS, x => x == channel) == -1)
return; continue;
int seq = value.Length / 2;
float ms = 4 * 60 * 1000f / BPM;
for (int i = 0; i < seq; i++)
{
string key = value.Substring(i * 2, 2);
if (key == "00")
continue;
float timing = (measure + (float)i / seq) * ms;
if (channel[0] == 'S')
{
notes.Add(new Note(key, channel, timing));
}
else if (channel[0] == 'L')
{
if (tempLongStart == null)
{
tempLongStart = timing;
continue;
}
float start = tempLongStart.Value;
notes.Add(new Note(key, channel, start, timing));
tempLongStart = null;
}
}
}
}
return notes;
} }
} }
\ No newline at end of file
...@@ -28,7 +28,8 @@ public class TrackManager : MonoBehaviour { ...@@ -28,7 +28,8 @@ public class TrackManager : MonoBehaviour {
void LoadTracks() void LoadTracks()
{ {
var files = new DirectoryInfo("Assets/Tracks").GetFiles("*.bpe", SearchOption.AllDirectories); var files = new DirectoryInfo("Assets/Tracks")
.GetFiles("*.bpe", SearchOption.AllDirectories);
TrackInfo[] tracks = files.Select(s => new TrackInfo(s)).ToArray(); TrackInfo[] tracks = files.Select(s => new TrackInfo(s)).ToArray();
......
#TITLE Tutorial #TITLE Tutorial
#ARTIST Various Artists #ARTIST Various Artists
#GENRE TUTORIAL CORE #GENRE TUTORIAL CORE
#BPM 440 #BPM 120
#PLAYLEVEL 0 #PLAYLEVEL 0
#001SBT 00AA00BB00AA00BB #002SBT AA000000000000000000000000AA
#002SBT AABBCCDD000000000000000000000000 #003LBT AA00000000000000AA00BB00BB000000
#002LBT 00000000000000AA000000BB #004LBT AA00
\ No newline at end of file #008LBT 00AA
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment