From 61a4e88161a3d238277c7d06263f4a8aaf31eaf1 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 8 Oct 2017 23:52:03 +0100 Subject: [PATCH] Add archived files --- s2prototype.sln | 20 + s2prototype/Animation.cs | 140 + s2prototype/App.config | 6 + s2prototype/Camera.cs | 91 + s2prototype/ControllerState.cs | 92 + s2prototype/Font.cs | 77 + s2prototype/GameScreen.cs | 24 + s2prototype/Graphics.cs | 68 + s2prototype/Landscape.cs | 358 +++ s2prototype/Level.cs | 488 +++ s2prototype/LevelConverter.cs | 677 ++++ s2prototype/LevelManager.cs | 27 + s2prototype/LevelObject.cs | 335 ++ s2prototype/LevelObjectDefinition.cs | 103 + s2prototype/LevelObjectManager.cs | 101 + s2prototype/LevelScreen.cs | 31 + s2prototype/LoadingScreen.cs | 80 + s2prototype/MusicManager.cs | 80 + s2prototype/Objects/Animal.cs | 119 + s2prototype/Objects/Badnik.cs | 67 + s2prototype/Objects/Buzzer.cs | 193 ++ s2prototype/Objects/Character.cs | 2743 +++++++++++++++++ s2prototype/Objects/Coconuts.cs | 238 ++ s2prototype/Objects/CollisionPlaneSwitcher.cs | 152 + s2prototype/Objects/EHZPlatform.cs | 154 + s2prototype/Objects/EHZSpiralPathway.cs | 173 ++ s2prototype/Objects/Explosion.cs | 55 + s2prototype/Objects/LogBridge.cs | 188 ++ s2prototype/Objects/Masher.cs | 71 + s2prototype/Objects/Monitor.cs | 333 ++ s2prototype/Objects/Platform.cs | 100 + s2prototype/Objects/Ring.cs | 117 + s2prototype/Objects/Signpost.cs | 182 ++ s2prototype/Objects/SolidObject.cs | 311 ++ s2prototype/Objects/Sonic.cs | 16 + s2prototype/Objects/Spikes.cs | 188 ++ s2prototype/Objects/Spring.cs | 429 +++ s2prototype/Objects/Starpost.cs | 122 + s2prototype/Player.cs | 261 ++ s2prototype/PlayerView.cs | 152 + s2prototype/Program.cs | 82 + s2prototype/ResourceManager.cs | 149 + s2prototype/SonicGame.cs | 276 ++ s2prototype/SonicMaths.cs | 126 + s2prototype/TitleCard.cs | 138 + s2prototype/s2prototype.csproj | 95 + 46 files changed, 10028 insertions(+) create mode 100644 s2prototype.sln create mode 100644 s2prototype/Animation.cs create mode 100644 s2prototype/App.config create mode 100644 s2prototype/Camera.cs create mode 100644 s2prototype/ControllerState.cs create mode 100644 s2prototype/Font.cs create mode 100644 s2prototype/GameScreen.cs create mode 100644 s2prototype/Graphics.cs create mode 100644 s2prototype/Landscape.cs create mode 100644 s2prototype/Level.cs create mode 100644 s2prototype/LevelConverter.cs create mode 100644 s2prototype/LevelManager.cs create mode 100644 s2prototype/LevelObject.cs create mode 100644 s2prototype/LevelObjectDefinition.cs create mode 100644 s2prototype/LevelObjectManager.cs create mode 100644 s2prototype/LevelScreen.cs create mode 100644 s2prototype/LoadingScreen.cs create mode 100644 s2prototype/MusicManager.cs create mode 100644 s2prototype/Objects/Animal.cs create mode 100644 s2prototype/Objects/Badnik.cs create mode 100644 s2prototype/Objects/Buzzer.cs create mode 100644 s2prototype/Objects/Character.cs create mode 100644 s2prototype/Objects/Coconuts.cs create mode 100644 s2prototype/Objects/CollisionPlaneSwitcher.cs create mode 100644 s2prototype/Objects/EHZPlatform.cs create mode 100644 s2prototype/Objects/EHZSpiralPathway.cs create mode 100644 s2prototype/Objects/Explosion.cs create mode 100644 s2prototype/Objects/LogBridge.cs create mode 100644 s2prototype/Objects/Masher.cs create mode 100644 s2prototype/Objects/Monitor.cs create mode 100644 s2prototype/Objects/Platform.cs create mode 100644 s2prototype/Objects/Ring.cs create mode 100644 s2prototype/Objects/Signpost.cs create mode 100644 s2prototype/Objects/SolidObject.cs create mode 100644 s2prototype/Objects/Sonic.cs create mode 100644 s2prototype/Objects/Spikes.cs create mode 100644 s2prototype/Objects/Spring.cs create mode 100644 s2prototype/Objects/Starpost.cs create mode 100644 s2prototype/Player.cs create mode 100644 s2prototype/PlayerView.cs create mode 100644 s2prototype/Program.cs create mode 100644 s2prototype/ResourceManager.cs create mode 100644 s2prototype/SonicGame.cs create mode 100644 s2prototype/SonicMaths.cs create mode 100644 s2prototype/TitleCard.cs create mode 100644 s2prototype/s2prototype.csproj diff --git a/s2prototype.sln b/s2prototype.sln new file mode 100644 index 0000000..46401e1 --- /dev/null +++ b/s2prototype.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "s2prototype", "s2prototype\s2prototype.csproj", "{5E2B7BF4-6F4C-4DCC-93A8-DAA31AF01848}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5E2B7BF4-6F4C-4DCC-93A8-DAA31AF01848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E2B7BF4-6F4C-4DCC-93A8-DAA31AF01848}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E2B7BF4-6F4C-4DCC-93A8-DAA31AF01848}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E2B7BF4-6F4C-4DCC-93A8-DAA31AF01848}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/s2prototype/Animation.cs b/s2prototype/Animation.cs new file mode 100644 index 0000000..7e07bf1 --- /dev/null +++ b/s2prototype/Animation.cs @@ -0,0 +1,140 @@ + +namespace IntelOrca.Sonic +{ + class Animation + { + private byte[][] mScripts; + private int mIndex; + private int mNextIndex; + private int mFrame; + private int mFrameDuration; + private int mFrameValue; + + public Animation() + { + } + + public Animation(byte[][] scripts) + { + mScripts = scripts; + } + + public void Update() + { + if (mIndex != mNextIndex) { + mNextIndex = mIndex; + mFrame = 0; + mFrameDuration = 0; + } + + if (mScripts == null) + return; + + if (mIndex >= mScripts.Length) + return; + + byte[] animationScript = mScripts[mIndex]; + mFrameDuration--; + if (mFrameDuration >= 0) + return; + + mFrameDuration = animationScript[0]; + ProcessScript(animationScript); + } + + private void ProcessScript(byte[] animationScript) + { + byte asf = animationScript[mFrame + 1]; + switch (asf) { + case 0xFF: + mFrameValue = animationScript[1]; + mFrame = 0; + break; + case 0xFE: + mFrame -= animationScript[mFrame + 2]; + mFrameValue = animationScript[mFrame + 1]; + mFrame++; + break; + case 0xFD: + mIndex = animationScript[mFrame + 2]; + break; + default: + mFrameValue = asf; + mFrame++; + break; + } + } + + public byte[][] Scripts + { + get + { + return mScripts; + } + set + { + mScripts = value; + } + } + + public int Index + { + get + { + return mIndex; + } + set + { + mIndex = value; + } + } + + public int NextIndex + { + get + { + return mNextIndex; + } + set + { + mNextIndex = value; + } + } + + public int Frame + { + get + { + return mFrame; + } + set + { + mFrame = value; + } + } + + public int FrameDuration + { + get + { + return mFrameDuration; + } + set + { + mFrameDuration = value; + } + } + + public int FrameValue + { + get + { + return mFrameValue; + } + set + { + mFrameValue = value; + } + } + } +} diff --git a/s2prototype/App.config b/s2prototype/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/s2prototype/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/s2prototype/Camera.cs b/s2prototype/Camera.cs new file mode 100644 index 0000000..0a9d4cf --- /dev/null +++ b/s2prototype/Camera.cs @@ -0,0 +1,91 @@ +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class Camera + { + private int mX; + private int mY; + // private int mPartialX; + // private int mPartialY; + private int mBiasY; + private int mScrollDelayX; + + public Camera() + { + } + + public Rectangle GetViewBounds(Rectangle visibleRegion, int width, int height) + { + Rectangle cameraView = new Rectangle(mX - (width / 2), mY - (height / 2), width, height); + + // Ensure minimum position + if (cameraView.X < visibleRegion.X) + cameraView.X = visibleRegion.X; + if (cameraView.Y < visibleRegion.Y) + cameraView.Y = visibleRegion.Y; + + // Ensure maximum position + if (cameraView.X + cameraView.Width > visibleRegion.X + visibleRegion.Width) + cameraView.X = visibleRegion.X + visibleRegion.Width - width; + if (cameraView.Y + cameraView.Height > visibleRegion.Y + visibleRegion.Height) + cameraView.Y = visibleRegion.Y + visibleRegion.Height - height; + + // Check if still unhandled + if (cameraView.Width > visibleRegion.Width) + cameraView.X = -((visibleRegion.Width - cameraView.Width) / 2); + if (cameraView.Height > visibleRegion.Height) + cameraView.Y = -((visibleRegion.Height - cameraView.Height) / 2); + + return cameraView; + } + + public int X + { + get + { + return mX; + } + set + { + mX = value; + } + } + + public int Y + { + get + { + return mY; + } + set + { + mY = value; + } + } + + public int BiasY + { + get + { + return mBiasY; + } + set + { + mBiasY = value; + } + } + + public int ScrollDelayX + { + get + { + return mScrollDelayX; + } + set + { + mScrollDelayX = value; + } + } + } +} diff --git a/s2prototype/ControllerState.cs b/s2prototype/ControllerState.cs new file mode 100644 index 0000000..fe9efdb --- /dev/null +++ b/s2prototype/ControllerState.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + struct ControllerState + { + private int mButtons; + + public ControllerState GetNewDown(ControllerState mLastControlState) + { + ControllerState state = new ControllerState(); + state.mButtons = mButtons & ~mLastControlState.mButtons; + return state; + } + + public void Set(int alterMask, bool state) + { + if (state) + mButtons |= alterMask; + else + mButtons &= ~alterMask; + } + + public bool Up + { + get + { + return ((mButtons & 1) != 0); + } + } + + public bool Down + { + get + { + return ((mButtons & 2) != 0); + } + } + + public bool Left + { + get + { + return ((mButtons & 4) != 0); + } + } + + public bool Right + { + get + { + return ((mButtons & 8) != 0); + } + } + + public bool Start + { + get + { + return ((mButtons & 16) != 0); + } + } + + public bool A + { + get + { + return ((mButtons & 32) != 0); + } + } + + public bool B + { + get + { + return ((mButtons & 64) != 0); + } + } + + public bool C + { + get + { + return ((mButtons & 128) != 0); + } + } + } +} diff --git a/s2prototype/Font.cs b/s2prototype/Font.cs new file mode 100644 index 0000000..3336a28 --- /dev/null +++ b/s2prototype/Font.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Font + { + private Texture2D mTexture; + + private char[] mCharacters = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ':' }; + private int[] mCharacterMapX = new int[] { 3, 13, 23, 33, 43, 53, 63, 73, 83, 93, 3, 13, 23, 33, 43, 53, 63, 73, 86, 93, 103, 113, 122, 3, 13, 23, 33, 43, 53, 63, 73, 83, 93, 103, 113, 122, 103 }; + private int[] mCharacterMapY = new int[] { 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 395, 395, 395, 395, 395, 395, 395, 395, 395, 395, 395, 395, 395, 407, 407, 407, 407, 407, 407, 407, 407, 407, 407, 407, 407, 407, 379 }; + private int[] mCharacterMapW = new int[] { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 3, 7, 8, 6, 10, 9, 7, 7, 7, 7, 7, 7, 7, 7, 10, 8, 7, 8, 7 }; + private int mHeight = 11; + + private int mLetterSpacing = 1; + + public Font(Texture2D texture) + { + mTexture = texture; + } + + public int MeasureStringWidth(string text) + { + int x = 0; + foreach (char c in text) { + int charMapIndex = GetCharacterMapIndex(c); + if (charMapIndex == -1) { + x += 7 + mLetterSpacing; + continue; + } + + x += (mCharacterMapW[charMapIndex] * 4) + (mLetterSpacing * 4); + } + return x; + } + + public void DrawString(Graphics g, string text, int x, int y) + { + DrawString(g, text, x, y, Color.White); + } + + public void DrawString(Graphics g, string text, int x, int y, Color colour) + { + foreach (char c in text) { + int charMapIndex = GetCharacterMapIndex(c); + if (charMapIndex == -1) { + x += 7 + mLetterSpacing; + continue; + } + + DrawCharacter(g, charMapIndex, x, y, colour); + x += (mCharacterMapW[charMapIndex] * 4) + (mLetterSpacing * 4); + } + } + + private void DrawCharacter(Graphics g, int index, int x, int y, Color colour) + { + Rectangle src = new Rectangle(mCharacterMapX[index], mCharacterMapY[index], mCharacterMapW[index], mHeight); + Rectangle dst = new Rectangle(x, y, mCharacterMapW[index] * 4, mHeight * 4); + g.DrawImage(mTexture, dst, src, colour); + } + + private int GetCharacterMapIndex(char c) + { + for (int i = 0; i < mCharacters.Length; i++) + if (mCharacters[i] == c) + return i; + return -1; + } + } +} diff --git a/s2prototype/GameScreen.cs b/s2prototype/GameScreen.cs new file mode 100644 index 0000000..568ccd2 --- /dev/null +++ b/s2prototype/GameScreen.cs @@ -0,0 +1,24 @@ + +namespace IntelOrca.Sonic +{ + abstract class GameScreen + { + private SonicGame mGame; + + public GameScreen(SonicGame game) + { + mGame = game; + } + + public virtual void Update() { } + public virtual void Draw(Graphics g) { } + + public SonicGame Game + { + get + { + return mGame; + } + } + } +} diff --git a/s2prototype/Graphics.cs b/s2prototype/Graphics.cs new file mode 100644 index 0000000..8cb09a6 --- /dev/null +++ b/s2prototype/Graphics.cs @@ -0,0 +1,68 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Graphics + { + private SpriteBatch mSpriteBatch; + private int mTranslationX; + private int mTranslationY; + + public Graphics(SpriteBatch spriteBatch) + { + mSpriteBatch = spriteBatch; + } + + public void DrawImage(Texture2D texture, Rectangle destination, Color colour) + { + mSpriteBatch.Draw(texture, GetClientDestination(destination), colour); + } + + public void DrawImage(Texture2D texture, Vector2 destination, Color colour) + { + mSpriteBatch.Draw(texture, GetClientDestination(new Rectangle((int)destination.X, (int)destination.Y, texture.Width, texture.Height)), colour); + } + + public void DrawImage(Texture2D texture, Rectangle destination, Rectangle source, Color colour) + { + mSpriteBatch.Draw(texture, GetClientDestination(destination), source, colour); + } + + public void DrawImage(Texture2D texture, Rectangle destination, Rectangle source, Color colour, SpriteEffects effects) + { + mSpriteBatch.Draw(texture, GetClientDestination(destination), source, colour, 0.0f, new Vector2(0, 0), effects, 0.0f); + } + + public void DrawImage(Texture2D texture, Rectangle destination, Rectangle source, Color colour, float rotation, Vector2 origin, SpriteEffects effects) + { + mSpriteBatch.Draw(texture, GetClientDestination(destination), source, colour, rotation, origin, effects, 0.0f); + } + + public void DrawMarker() + { + DrawImage(ResourceManager.MarkerTexture, new Vector2(-30, -30), Color.White); + } + + public void Translate(int x, int y) + { + mTranslationX += x; + mTranslationY += y; + } + + private Rectangle GetClientDestination(Rectangle destination) + { + destination.X += mTranslationX; + destination.Y += mTranslationY; + return destination; + } + + public GraphicsDevice GraphicsDevice + { + get + { + return mSpriteBatch.GraphicsDevice; + } + } + } +} diff --git a/s2prototype/Landscape.cs b/s2prototype/Landscape.cs new file mode 100644 index 0000000..c66ee68 --- /dev/null +++ b/s2prototype/Landscape.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace IntelOrca.Sonic +{ + class Landscape + { + private string mInformation = String.Empty; + private List mChunks = new List(); + + public Landscape() + { + } + + public Landscape(string file) + { + Open(file); + } + + public Landscape(Stream stream) + { + Open(stream); + } + + private bool Open(string file) + { + using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read)) + return Open(fs); + } + + private bool Open(Stream stream) + { + BinaryReader br = new BinaryReader(stream); + int magicNumber = br.ReadInt32(); + mInformation = br.ReadString(); + int version = br.ReadInt32(); + int compression = br.ReadInt32(); + long uncompressedSize = br.ReadInt64(); + + // uncompress stuff + + int numChunks = br.ReadInt32(); + for (int i = 0; i < numChunks; i++) + mChunks.Add(new Chunk(stream)); + + return true; + } + + public bool Save(string file) + { + using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write)) + return Save(fs); + } + + public bool Save(Stream stream) + { + long dataLengthPosition, dataEndPosition; + + BinaryWriter bw = new BinaryWriter(stream); + bw.Write(0x4C455354); + bw.Write(mInformation); + bw.Write(1); + bw.Write(0); + + dataLengthPosition = stream.Position; + + // Length of data + bw.Write(0L); + + bw.Write(mChunks.Count); + foreach (Chunk chunk in mChunks) + chunk.Save(stream); + + dataEndPosition = stream.Position; + stream.Position = dataLengthPosition; + bw.Write(dataEndPosition - dataLengthPosition + 4); + stream.Position = dataEndPosition; + + return true; + } + + public string Information + { + get + { + return mInformation; + } + set + { + mInformation = value; + } + } + + public List Chunks + { + get + { + return mChunks; + } + set + { + mChunks = value; + } + } + + public class Chunk + { + private Layer mBackLayer; + private Layer mFrontLayer; + + public Chunk() + { + mBackLayer = new Layer(); + mFrontLayer = new Layer(); + } + + public Chunk(Stream stream) + { + mBackLayer = new Layer(stream); + mFrontLayer = new Layer(stream); + } + + public void Save(Stream stream) + { + mBackLayer.Save(stream); + mFrontLayer.Save(stream); + } + + public Layer BackLayer + { + get + { + return mBackLayer; + } + set + { + mBackLayer = value; + } + } + + public Layer FrontLayer + { + get + { + return mFrontLayer; + } + set + { + mFrontLayer = value; + } + } + + public class Layer + { + private sbyte[,] mAngles; + private bool[,] mCollisionLRB; + private bool[,] mCollisionT; + private Image mArt; + + public Layer() + { + mAngles = new sbyte[8, 8]; + mCollisionLRB = new bool[128, 128]; + mCollisionT = new bool[128, 128]; + mArt = new Image(); + } + + public Layer(Stream stream) + : this() + { + BinaryReader br = new BinaryReader(stream); + + // Read angles + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + mAngles[x, y] = br.ReadSByte(); + + // Read collision masks + int bits = 0; + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + if (x % 32 == 0) + bits = br.ReadInt32(); + + mCollisionLRB[x, y] = ((bits & (1 << (x % 32))) != 0); + } + } + + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + if (x % 32 == 0) + bits = br.ReadInt32(); + + mCollisionT[x, y] = ((bits & (1 << (x % 32))) != 0); + } + } + + // Read art + mArt = new Image(stream); + } + + public void Save(Stream stream) + { + BinaryWriter bw = new BinaryWriter(stream); + + // Write angles + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + bw.Write(mAngles[x, y]); + + // Write collision masks + int bits = 0; + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + if (mCollisionLRB[x, y]) + bits |= 1 << (x % 32); + if (x % 32 == 31) { + bw.Write(bits); + bits = 0; + } + } + } + + bits = 0; + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + if (mCollisionT[x, y]) + bits |= 1 << (x % 32); + if (x % 32 == 31) { + bw.Write(bits); + bits = 0; + } + } + } + + // Write art + mArt.Save(stream); + } + + public sbyte[,] Angles + { + get + { + return mAngles; + } + set + { + mAngles = value; + } + } + + public bool[,] CollisionLRB + { + get + { + return mCollisionLRB; + } + set + { + mCollisionLRB = value; + } + } + + public bool[,] CollisionT + { + get + { + return mCollisionT; + } + set + { + mCollisionT = value; + } + } + + public Image Art + { + get + { + return mArt; + } + set + { + mArt = value; + } + } + + public class Image + { + private byte mAnimationDuration; + private byte mAnimationFrames; + private byte[][] mFrameData; + + public Image() + { + mFrameData = new byte[0][]; + } + + public Image(Stream stream) + { + BinaryReader br = new BinaryReader(stream); + mAnimationDuration = br.ReadByte(); + mAnimationFrames = br.ReadByte(); + mFrameData = new byte[mAnimationFrames][]; + for (int i = 0; i < mAnimationFrames; i++) + mFrameData[i] = br.ReadBytes(512 * 512 * 4); + } + + public void Save(Stream stream) + { + BinaryWriter bw = new BinaryWriter(stream); + bw.Write(mAnimationDuration); + bw.Write(mAnimationFrames); + for (int i = 0; i < mAnimationFrames; i++) + bw.Write(mFrameData[i]); + } + + public byte AnimationDuration + { + get + { + return mAnimationDuration; + } + set + { + mAnimationDuration = value; + } + } + + public byte AnimationFrames + { + get + { + return mAnimationFrames; + } + set + { + mAnimationFrames = value; + } + } + + public byte[][] FrameData + { + get + { + return mFrameData; + } + set + { + mFrameData = value; + } + } + } + } + } + } +} diff --git a/s2prototype/Level.cs b/s2prototype/Level.cs new file mode 100644 index 0000000..74ee48e --- /dev/null +++ b/s2prototype/Level.cs @@ -0,0 +1,488 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using Microsoft.Xna.Framework.Audio; + +namespace IntelOrca.Sonic +{ + class Level + { + private SonicGame mGame; + + private string mName; + private int mZoneIndex; + private int mActIndex; + + private int mStartX; + private int mStartY; + private Microsoft.Xna.Framework.Rectangle mPlayerBoundary; + private Microsoft.Xna.Framework.Rectangle mVisibleBoundary; + + private List mSounds = new List(); + + // Level chunks + private int mChunkColumns; + private int mChunkRows; + public byte[,] mChunkLayout; + // private List mChunks = new List(); + private Landscape mLandscape; + private string mDirectory; + + // Level objects + private List mObjectDefinitions = new List(); + private LevelObjectManager mObjects = new LevelObjectManager(); + + public Level(SonicGame game) + { + mGame = game; + } + + public void Load(string directory) + { + mDirectory = directory; + mName = "Emerald Hill"; + mZoneIndex = 1; + mActIndex = 1; + + mPlayerBoundary = new Microsoft.Xna.Framework.Rectangle(16, 0, 11264 - 16, 1024); + mVisibleBoundary = new Microsoft.Xna.Framework.Rectangle(0, 0, 10976, 1024); + + mLandscape = new Landscape(mDirectory + "\\landscape.dat"); + LoadLayout(mDirectory + "\\act1.dat"); + + // for (int i = 0; i < 256; i++) + // mChunks.Add(new Chunk()); + + // LoadChunks(directory + "\\chunks.dat", directory + "\\fgback.png", directory + "\\fgfront.png"); + } + + public void Restart() + { + mObjects = new LevelObjectManager(); + mSounds.Clear(); + + mPlayerBoundary = new Microsoft.Xna.Framework.Rectangle(16, 0, 11264 - 16, 1024); + mVisibleBoundary = new Microsoft.Xna.Framework.Rectangle(0, 0, 10976, 1024); + + mObjects.Clear(); + foreach (LevelObjectDefinition def in mObjectDefinitions) { + LevelObject obj = LevelObject.Create(mGame, this, def); + if (obj != null) + mObjects.Add(obj); + } + } + + public void Update() + { + // Clear the sounds + mSounds.Clear(); + + mObjects.Update(); + } + + public Character GetClosestCharacter(LevelObject obj, out int directionX, out int directionY, out int horizontalDistance, out int verticalDistance) + { + List characters = new List(); + foreach (LevelObject o in mObjects) + if (o is Character) + characters.Add((Character)o); + + Character closestCharacter = null; + int lowestHorizontalDistance = -1; + + directionX = 0; + directionY = 0; + horizontalDistance = 0; + verticalDistance = 0; + + foreach (Character character in characters) { + int hDist = obj.DisplacementX - character.DisplacementX; + int vDist = obj.DisplacementY - character.DisplacementY; + int ahDist = Math.Abs(hDist); + + if (closestCharacter != null && ahDist >= lowestHorizontalDistance) + continue; + + closestCharacter = character; + horizontalDistance = hDist; + verticalDistance = vDist; + + // Is player to object's left + if (hDist < 0) + directionX = 2; + + // Is player under object + if (vDist < 0) + directionY = 2; + } + + return closestCharacter; + } + + private void LoadLayout(string path) + { + FileStream fs = new FileStream(path, FileMode.Open); + BinaryReader br = new BinaryReader(fs); + + br.ReadString(); + br.ReadString(); + + // Chunk layout + mChunkColumns = br.ReadInt32(); + mChunkRows = br.ReadInt32(); + mChunkLayout = new byte[mChunkColumns, mChunkRows]; + for (int y = 0; y < mChunkRows; y++) + for (int x = 0; x < mChunkColumns; x++) + mChunkLayout[x, y] = br.ReadByte(); + + // Objects + mObjectDefinitions.Clear(); + int numObjects = br.ReadInt32(); + for (int i = 0; i < numObjects; i++) { + LevelObjectDefinition definition = new LevelObjectDefinition(); + + definition.Id = br.ReadInt32(); + definition.SubType = br.ReadInt32(); + definition.DisplacementX = br.ReadInt32(); + definition.DisplacementY = br.ReadInt32(); + definition.Respawn = br.ReadBoolean(); + definition.FlipY = br.ReadBoolean(); + definition.FlipX = br.ReadBoolean(); + + if (definition.Id == 1) { + mStartX = definition.DisplacementX; + mStartY = definition.DisplacementY; + } + + LevelObject obj = LevelObject.Create(mGame, this, definition); + if (obj != null) + mObjects.Add(obj); + + mObjectDefinitions.Add(definition); + } + + br.Close(); + fs.Close(); + } + + public void FindCeiling(int x, int y, int layer, bool lrb, bool t, out int distance, ref int angle) + { + if (IsSolid(x, y, layer, lrb, t)) { + // Check other way + for (int i = y + 1; i < y + 32; i++) { + if (IsSolid(x, i, layer, lrb, t)) + continue; + distance = y - i; + angle = GetAngle(x, i, layer); + return; + } + + distance = -32; + } + + for (int i = y - 1; i > y - 32; i--) { + if (!IsSolid(x, i, layer, lrb, t)) + continue; + distance = y - i - 1; + angle = GetAngle(x, i, layer); + return; + } + + distance = 32; + } + + public void FindFloor(int x, int y, int layer, bool lrb, bool t, out int distance, ref int angle) + { + if (IsSolid(x, y, layer, lrb, t)) { + // Check other way + for (int i = y - 1; i > y - 32; i--) { + if (IsSolid(x, i, layer, lrb, t)) + continue; + distance = i - y; + angle = GetAngle(x, i, layer); + return; + } + + distance = -32; + } + + for (int i = y + 1; i < y + 32; i++) { + if (!IsSolid(x, i, layer, lrb, t)) + continue; + distance = i - y - 1; + angle = GetAngle(x, i, layer); + return; + } + + distance = 32; + } + + public void FindWallLeft(int x, int y, int layer, bool lrb, bool t, out int distance, ref int angle) + { + if (IsSolid(x, y, layer, lrb, t)) { + // Check other way + for (int i = x + 1; i < x + 32; i++) { + if (IsSolid(i, y, layer, lrb, t)) + continue; + distance = x - i; + angle = GetAngle(i, y, layer); + return; + } + + distance = -32; + } + + for (int i = x - 1; i > x - 32; i--) { + if (!IsSolid(i, y, layer, lrb, t)) + continue; + distance = x - i - 1; + angle = GetAngle(i, y, layer); + return; + } + + distance = 32; + } + + public void FindWallRight(int x, int y, int layer, bool lrb, bool t, out int distance, ref int angle) + { + if (IsSolid(x, y, layer, lrb, t)) { + // Check other way + for (int i = x - 1; i > x - 32; i--) { + if (IsSolid(i, y, layer, lrb, t)) + continue; + distance = i - x; + angle = GetAngle(i, y, layer); + return; + } + + distance = -32; + } + + for (int i = x + 1; i < x + 32; i++) { + if (!IsSolid(i, y, layer, lrb, t)) + continue; + distance = i - x - 1; + angle = GetAngle(i, y, layer); + return; + } + + distance = 32; + } + + public bool IsSolid(int x, int y, int layer, bool lrb, bool t) + { + int chunkX = x / 128; + int chunkY = y / 128; + int blockX = (x % 128); + int blockY = (y % 128); + + if (x < 0 || y < 0) + return false; + + if (chunkX >= mChunkColumns || chunkY >= mChunkRows) + return false; + + Landscape.Chunk chunk = mLandscape.Chunks[mChunkLayout[chunkX, chunkY]]; + Landscape.Chunk.Layer clayer = (layer == 0 ? chunk.BackLayer : chunk.FrontLayer); + + bool solid = false; + if (lrb) + solid = clayer.CollisionLRB[blockX, blockY]; + if (t && !solid) + solid = clayer.CollisionT[blockX, blockY]; + return solid; + } + + private int GetAngle(int x, int y, int layer) + { + int chunkX = x / 128; + int chunkY = y / 128; + int blockX = (x % 128) / 16; + int blockY = (y % 128) / 16; + + if (x < 0 || y < 0) + return 0; + + if (chunkX >= mChunkColumns || chunkY >= mChunkRows) + return 0; + + Landscape.Chunk chunk = mLandscape.Chunks[mChunkLayout[chunkX, chunkY]]; + Landscape.Chunk.Layer clayer = (layer == 0 ? chunk.BackLayer : chunk.FrontLayer); + return clayer.Angles[blockX, blockY]; + } + + public void AddSound(SoundEffect soundEffect, int x, int y) + { + AddSound(new Sound(soundEffect, x, y)); + } + + public void AddSound(Sound sound) + { + mSounds.Add(sound); + } + + public IEnumerable Sounds + { + get + { + return mSounds; + } + } + + public int Width + { + get + { + return mChunkColumns; + } + } + + public int Height + { + get + { + return mChunkRows; + } + } + + public byte[,] LevelLayout + { + get + { + return mChunkLayout; + } + } + + public LevelObjectManager Objects + { + get + { + return mObjects; + } + } + + public string Name + { + get + { + return mName; + } + } + + public int ZoneIndex + { + get + { + return mZoneIndex; + } + } + + public int ActIndex + { + get + { + return mActIndex; + } + } + + public Microsoft.Xna.Framework.Rectangle PlayerBoundary + { + get + { + return mPlayerBoundary; + } + set + { + mPlayerBoundary = value; + } + } + + public Microsoft.Xna.Framework.Rectangle VisibleBoundary + { + get + { + return mVisibleBoundary; + } + set + { + mVisibleBoundary = value; + } + } + + public int StartX + { + get + { + return mStartX; + } + } + + public int StartY + { + get + { + return mStartY; + } + } + + public Landscape Landscape + { + get + { + return mLandscape; + } + } + + public struct Sound + { + private SoundEffect mSoundEffect; + private int mDisplacementX; + private int mDisplacementY; + + public Sound(SoundEffect soundEffect, int x, int y) + { + mSoundEffect = soundEffect; + mDisplacementX = x; + mDisplacementY = y; + } + + public int DisplacementX + { + get + { + return mDisplacementX; + } + set + { + mDisplacementX = value; + } + } + + public int DisplacementY + { + get + { + return mDisplacementY; + } + set + { + mDisplacementY = value; + } + } + + public SoundEffect SoundEffect + { + get + { + return mSoundEffect; + } + set + { + mSoundEffect = value; + } + } + } + } +} diff --git a/s2prototype/LevelConverter.cs b/s2prototype/LevelConverter.cs new file mode 100644 index 0000000..a178373 --- /dev/null +++ b/s2prototype/LevelConverter.cs @@ -0,0 +1,677 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using SonicRetro.KensSharp; + +namespace IntelOrca.Sonic +{ + class LevelConverter + { + private int mWidth = 88; + private int mHeight = 8; + private byte[,] mBGLayoutTiles; + private byte[,] mFGLayoutTiles; + + private List mLayoutBlocks = new List(); + private List mTileBlocks = new List(); + + private byte[] mArtTiles; + private byte[] mArtPalette; + + private byte[] mPrimaryCollisionIndicies; + private byte[] mSecondaryCollisionIndicies; + + private sbyte[] mCollisionArray1; + private sbyte[] mCollisionArray2; + + private sbyte[] mCurveMapping; + + private byte[] mObjects; + private byte[] mRings; + + public LevelConverter() + { + mBGLayoutTiles = new byte[mWidth, mHeight]; + mFGLayoutTiles = new byte[mWidth, mHeight]; + Load(); + } + + public void Load() + { + mArtTiles = Kosinski.Decompress(@"C:\Users\Ted\Documents\Programming\Open Source\S2\art\kosinski\EHZ_HTZ.bin"); + + byte[] pal0 = File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\art\palettes\SonicAndTails.bin"); + byte[] pal1 = File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\art\palettes\EHZ.bin"); + mArtPalette = new byte[pal0.Length + pal1.Length]; + Array.Copy(pal0, 0, mArtPalette, 0, pal0.Length); + Array.Copy(pal1, 0, mArtPalette, pal0.Length, pal1.Length); + + byte[] layout = Kosinski.Decompress(@"C:\Users\Ted\Documents\Programming\Open Source\S2\level\layout\EHZ_1.bin"); + for (int y = 0; y < mHeight; y++) { + for (int x = 0; x < mWidth; x++) { + mFGLayoutTiles[x, y] = layout[256 * y + x]; + } + } + + byte[] blockMappings = Kosinski.Decompress(@"C:\Users\Ted\Documents\Programming\Open Source\S2\mappings\128x128\EHZ_HTZ.bin"); + for (int i = 0; i < 256; i++) + mLayoutBlocks.Add(new LayoutBlock(blockMappings, i * 128)); + + blockMappings = Kosinski.Decompress(@"C:\Users\Ted\Documents\Programming\Open Source\S2\mappings\16x16\EHZ.bin"); + for (int i = 0; i < 500; i++) + mTileBlocks.Add(new TileBlock(blockMappings, i * 8)); + + mCollisionArray1 = (sbyte[])(Array)File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\collision\Collision array 1.bin"); + mCollisionArray2 = (sbyte[])(Array)File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\collision\Collision array 2.bin"); + + mPrimaryCollisionIndicies = Kosinski.Decompress(@"C:\Users\Ted\Documents\Programming\Open Source\S2\collision\EHZ and HTZ primary 16x16 collision index.bin"); + mSecondaryCollisionIndicies = Kosinski.Decompress(@"C:\Users\Ted\Documents\Programming\Open Source\S2\collision\EHZ and HTZ secondary 16x16 collision index.bin"); + for (int i = 0; i < mTileBlocks.Count; i++) + mTileBlocks[i].SetCollision(mPrimaryCollisionIndicies[i], mSecondaryCollisionIndicies[i]); + + mCurveMapping = (sbyte[])(Array)File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\collision\Curve and resistance mapping.bin"); + + mObjects = File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\level\objects\EHZ_1.bin"); + mRings = File.ReadAllBytes(@"C:\Users\Ted\Documents\Programming\Open Source\S2\level\rings\EHZ_1.bin"); + + FileStream fs = new FileStream(@"C:\Users\Ted\Documents\programming\Projects\Games\S2HD\assets\levels\ehz1\objects.dat", FileMode.Create); + BinaryWriter bw = new BinaryWriter(fs); + + // Objects + List objects = new List(); + + // Player start + // objects.Add(new { Id = 1, Subtype = 0, X = 0x0060, Y = 0x028F, Respawn = false, Xflip = false, Yflip = false }); + + for (int i = 0; i < mObjects.Length; i += 6) { + short x = BitConverter.ToInt16(new byte[] { mObjects[i + 1], mObjects[i + 0] }, 0); + short y = BitConverter.ToInt16(new byte[] { mObjects[i + 3], (byte)(mObjects[i + 2] & 0x0F) }, 0); + byte id = mObjects[i + 4]; + byte subtype = mObjects[i + 5]; + bool respawn = ((mObjects[i + 2] & 0x80) == 0); + bool yflip = ((mObjects[i + 2] & 0x40) != 0); + bool xflip = ((mObjects[i + 2] & 0x20) != 0); + + objects.Add(new { Id = (int)id, Subtype = (int)subtype, X = (int)x, Y = (int)y, Respawn = respawn, Xflip = xflip, Yflip = yflip }); + } + + for (int i = 0; i < mRings.Length; i += 4) { + short x = BitConverter.ToInt16(new byte[] { mRings[i + 1], mRings[i + 0] }, 0); + if (x == -1) + break; + short y = BitConverter.ToInt16(new byte[] { mRings[i + 3], (byte)(mRings[i + 2] & 0x0F) }, 0); + int t = mRings[i + 2] >> 4; + bool column = ((t & 8) != 0); + int c = (t & 7) + 1; + + for (int j = 0; j < c; j++) { + objects.Add(new { Id = 37, Subtype = 0, X = (int)x, Y = (int)y, Respawn = false, Xflip = false, Yflip = false }); + + if (column) + y += 24; + else + x += 24; + } + } + + int[] swapIdA = new int[] { 3, 6, 13, 17, 24, 28, 37, 38, 54, 65, 73, 75, 92, 121, 157 }; + int[] swapIdB = new int[] { 38, 131, 19, 128, 130, 129, 16, 17, 49, 48, 132, 64, 66, 18, 65 }; + + foreach (dynamic obj in objects) { + int id = 0; + for (int i = 0; i < swapIdA.Length; i++) + if (obj.Id == swapIdA[i]) + id = swapIdB[i]; + + if (id == 0) + continue; + + bw.Write(id); + bw.Write((int)obj.Subtype); + + int flags = 0; + if (obj.Xflip) + flags |= 1; + if (obj.Yflip) + flags |= 2; + if (obj.Respawn) + flags |= 4; + + bw.Write(flags); + bw.Write((int)obj.X); + bw.Write((int)obj.Y); + } + + bw.Close(); + fs.Close(); + } + + public void Convert() + { + string directory = Path.GetFullPath(@"data\levels\ehz"); + + Landscape landscape = new Landscape(); + int chunkId = 0; + foreach (LayoutBlock chunk in mLayoutBlocks) { + Landscape.Chunk lchunk = new Landscape.Chunk(); + for (int layer = 0; layer < 2; layer++) { + Landscape.Chunk.Layer lclayer = new Landscape.Chunk.Layer(); + + // Angles + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + LayoutBlock.LayoutBlockTile chunkBlock = chunk.Tiles[x, y]; + if (chunkBlock.Index >= mTileBlocks.Count) { + lclayer.Angles[x, y] = 0; + continue; + } + + TileBlock block = mTileBlocks[chunkBlock.Index]; + int ci = (layer == 0 ? block.SecondaryCollision : block.PrimaryCollision); + sbyte angle = mCurveMapping[ci]; + if (chunkBlock.X) + angle = (sbyte)-angle; + if (chunkBlock.Y) + angle = (sbyte)(-(angle + 64) - 64); + + lclayer.Angles[x, y] = angle; + } + } + + // LRB + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + int blockX = x / 16; + int blockY = y / 16; + int tileX = (x % 16) / 2; + int tileY = (y % 16) / 2; + + LayoutBlock.LayoutBlockTile chunkBlock = chunk.Tiles[blockX, blockY]; + + if (layer == 0) { + if ((chunkBlock.SS & 2) == 0) { + lclayer.CollisionLRB[x, y] = false; + continue; + } + } else { + if ((chunkBlock.TT & 2) == 0) { + lclayer.CollisionLRB[x, y] = false; + continue; + } + } + + if (chunkBlock.Index >= mTileBlocks.Count) { + lclayer.CollisionLRB[x, y] = false; + continue; + } + + int px = x % 16; + int py = y % 16; + if (chunkBlock.X) + px = 15 - px; + if (chunkBlock.Y) + py = 15 - py; + + TileBlock block = mTileBlocks[chunkBlock.Index]; + int ci = (layer == 0 ? block.SecondaryCollision : block.PrimaryCollision); + sbyte collision = mCollisionArray1[ci * 16 + px]; + bool solid; + if (collision == 0) + solid = false; + else if (collision > 0) + solid = collision >= (16 - py); + else + throw new Exception(); + + lclayer.CollisionLRB[x, y] = solid; + } + } + + // TOP + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + int blockX = x / 16; + int blockY = y / 16; + int tileX = (x % 16) / 2; + int tileY = (y % 16) / 2; + + LayoutBlock.LayoutBlockTile chunkBlock = chunk.Tiles[blockX, blockY]; + + if (layer == 0) { + if ((chunkBlock.SS & 1) == 0) { + lclayer.CollisionT[x, y] = false; + continue; + } + } else { + if ((chunkBlock.TT & 1) == 0) { + lclayer.CollisionT[x, y] = false; + continue; + } + } + + if (chunkBlock.Index >= mTileBlocks.Count) { + lclayer.CollisionT[x, y] = false; + continue; + } + + int px = x % 16; + int py = y % 16; + if (chunkBlock.X) + px = 15 - px; + if (chunkBlock.Y) + py = 15 - py; + + TileBlock block = mTileBlocks[chunkBlock.Index]; + int ci = (layer == 0 ? block.SecondaryCollision : block.PrimaryCollision); + sbyte collision = mCollisionArray1[ci * 16 + px]; + bool solid; + if (collision == 0) + solid = false; + else if (collision > 0) + solid = collision >= (16 - py); + else + throw new Exception(); + + lclayer.CollisionT[x, y] = solid; + } + } + + // Art + Landscape.Chunk.Layer.Image lcli = new Landscape.Chunk.Layer.Image(); + lcli.AnimationDuration = 0; + lcli.AnimationFrames = 1; + Color[] bits128 = GetFGChunkArt(chunkId, (layer == 0), (layer == 1)); + Color[] bits512 = new Color[512 * 512]; + for (int y = 0; y < 512; y++) + for (int x = 0; x < 512; x++) + bits512[y * 512 + x] = bits128[(y / 4) * 128 + (x / 4)]; + lcli.FrameData = new byte[1][]; + + MemoryStream ms2 = new MemoryStream(); + BinaryWriter bw2 = new BinaryWriter(ms2); + for (int i = 0; i < 512 * 512; i++) { + bw2.Write(bits512[i].R); + bw2.Write(bits512[i].G); + bw2.Write(bits512[i].B); + bw2.Write(bits512[i].A); + } + lcli.FrameData[0] = ms2.ToArray(); + ms2.Close(); + + lclayer.Art = lcli; + + if (layer == 0) + lchunk.BackLayer = lclayer; + else + lchunk.FrontLayer = lclayer; + } + + landscape.Chunks.Add(lchunk); + + chunkId++; + } + + landscape.Save(directory + "\\landscape.dat"); + + return; + + // Level + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + + // Layout + bw.Write("Emerald Hill, Act 1"); + bw.Write("Sega"); + bw.Write(88); + bw.Write(8); + for (int y = 0; y < 8; y++) + for (int x = 0; x < 88; x++) + bw.Write(mFGLayoutTiles[x, y]); + + // Objects + List objects = new List(); + + // Player start + objects.Add(new { Id = 1, Subtype = 0, X = 0x0060, Y = 0x028F, Respawn = false, Xflip = false, Yflip = false }); + + for (int i = 0; i < mObjects.Length; i += 6) { + short x = BitConverter.ToInt16(new byte[] { mObjects[i + 1], mObjects[i + 0] }, 0); + short y = BitConverter.ToInt16(new byte[] { mObjects[i + 3], (byte)(mObjects[i + 2] & 0x0F) }, 0); + byte id = mObjects[i + 4]; + byte subtype = mObjects[i + 5]; + bool respawn = ((mObjects[i + 2] & 0x80) == 0); + bool xflip = ((mObjects[i + 2] & 0x40) != 0); + bool yflip = ((mObjects[i + 2] & 0x20) != 0); + + objects.Add(new { Id = (int)id, Subtype = (int)subtype, X = (int)x, Y = (int)y, Respawn = respawn, Xflip = xflip, Yflip = yflip }); + } + + for (int i = 0; i < mRings.Length; i += 4) { + short x = BitConverter.ToInt16(new byte[] { mRings[i + 1], mRings[i + 0] }, 0); + if (x == -1) + break; + short y = BitConverter.ToInt16(new byte[] { mRings[i + 3], (byte)(mRings[i + 2] & 0x0F) }, 0); + int t = mRings[i + 2] >> 4; + bool column = ((t & 8) != 0); + int c = (t & 7) + 1; + + for (int j = 0; j < c; j++) { + objects.Add(new { Id = 37, Subtype = 0, X = (int)x, Y = (int)y, Respawn = false, Xflip = false, Yflip = false }); + + if (column) + y += 24; + else + x += 24; + } + } + + bw.Write(objects.Count); + foreach (dynamic obj in objects) { + bw.Write(obj.Id); + bw.Write(obj.Subtype); + bw.Write(obj.X); + bw.Write(obj.Y); + bw.Write(obj.Respawn); + bw.Write(obj.Xflip); + bw.Write(obj.Yflip); + } + + bw.Close(); + File.WriteAllBytes(directory + "\\act1.dat", ms.ToArray()); + } + + public Bitmap GetFGChunkImagesAsOne(bool back, bool front) + { + Bitmap[] chunkBmps = GetFGChunkImages(back, front); + + int chunksWide = Math.Min(chunkBmps.Length, 8); + int chunksHigh = (int)Math.Ceiling(chunkBmps.Length / (double)chunksWide); + + Bitmap bmp = new Bitmap(chunksWide * 128, chunksHigh * 128); + System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp); + + int i = 0; + for (int y = 0; y < chunksHigh; y++) { + for (int x = 0; x < chunksWide; x++) { + if (i >= chunkBmps.Length) + break; + + g.DrawImage(chunkBmps[i], x * 128, y * 128); + i++; + } + } + + g.Dispose(); + return bmp; + } + + public Bitmap[] GetFGChunkImages(bool back, bool front) + { + Bitmap[] bmps = new Bitmap[mLayoutBlocks.Count]; + for (int i = 0; i < bmps.Length; i++) + bmps[i] = GetFGChunkImage(i, back, front); + return bmps; + } + + public Bitmap GetFGChunkImage(int chunkId, bool back, bool front) + { + Color[] bits = GetFGChunkArt(chunkId, back, front); + Bitmap bmp = new Bitmap(128, 128); + System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp); + for (int y = 0; y < 128; y++) + for (int x = 0; x < 128; x++) + g.FillRectangle(new SolidBrush(bits[y * 128 + x]), x, y, 1, 1); + g.Dispose(); + return bmp; + } + + public Color[] GetFGChunkArt(int chunkId, bool back, bool front) + { + Color[] pixels = new Color[128 * 128]; + LayoutBlock chunk = mLayoutBlocks[chunkId]; + for (int y = 0; y < 128; y++) { + for (int x = 0; x < 128; x++) { + int sx, sy, dx, dy; + + LayoutBlock.LayoutBlockTile chunkTile = chunk.Tiles[x / 16, y / 16]; + if (chunkTile.Index >= mTileBlocks.Count) { + pixels[y * 128 + x] = Color.Transparent; + continue; + } + + dx = x % 16; + dy = y % 16; + if (chunkTile.X) + dx = 15 - dx; + if (chunkTile.Y) + dy = 15 - dy; + + dx += (x / 16) * 16; + dy += (y / 16) * 16; + + TileBlock block = mTileBlocks[chunkTile.Index]; + TileBlock.TileBlockTile blockTile = block.Tiles[(x % 16) / 8, (y % 16) / 8]; + if (blockTile.P && !front) + continue; + if (!blockTile.P && !back) + continue; + + sx = x % 8; + sy = y % 8; + if (blockTile.X) + sx = 7 - sx; + if (blockTile.Y) + sy = 7 - sy; + + pixels[dy * 128 + dx] = GetArtTilePixel(blockTile.CC, blockTile.Index, sx, sy); + } + } + + return pixels; + } + + private Color GetArtTilePixel(int palette, int tileIndex, int x, int y) + { + byte pixel = mArtTiles[tileIndex * 32 + (y * 4) + (x / 2)]; + if (x % 2 == 0) + return GetPaletteColour((pixel >> 4) + (palette * 16)); + else + return GetPaletteColour((pixel & 0x0F) + (palette * 16)); + } + + private Color GetPaletteColour(int index) + { + if (index % 16 == 0) + return Color.Transparent; + + byte ab = mArtPalette[index * 2]; + byte gr = mArtPalette[index * 2 + 1]; + return Color.FromArgb((gr & 0x0F) * 16, (gr >> 4) * 16, (ab & 0x0F) * 16); + } + + class LayoutBlock + { + private LayoutBlockTile[,] mTiles; + + public LayoutBlock(byte[] data, int index) + { + mTiles = new LayoutBlockTile[8, 8]; + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + mTiles[x, y] = new LayoutBlockTile(BitConverter.ToInt16(data, index + (y * 16) + (x * 2))); + } + + public LayoutBlockTile[,] Tiles + { + get + { + return mTiles; + } + } + + public struct LayoutBlockTile + { + // SSTT YXII IIII IIII + private short mData; + + public LayoutBlockTile(short data) + { + byte[] databytes = BitConverter.GetBytes(data); + byte tmp = databytes[0]; + databytes[0] = databytes[1]; + databytes[1] = tmp; + mData = BitConverter.ToInt16(databytes, 0); + } + + public short Data + { + get + { + return mData; + } + } + + public int SS + { + get + { + return mData >> 14; + } + } + + public int TT + { + get + { + return (mData >> 12) & 3; + } + } + + public bool Y + { + get + { + return ((mData >> 11) & 1) > 0; + } + } + + public bool X + { + get + { + return ((mData >> 10) & 1) > 0; + } + } + + public int Index + { + get + { + return mData & 0x03FF; + } + } + } + } + + class TileBlock + { + private TileBlockTile[,] mTiles; + private int mPrimaryCollision; + private int mSecondaryCollision; + + public TileBlock(byte[] data, int index) + { + mTiles = new TileBlockTile[2, 2]; + for (int y = 0; y < 2; y++) + for (int x = 0; x < 2; x++) + mTiles[x, y] = new TileBlockTile(BitConverter.ToInt16(data, index + (y * 4) + (x * 2))); + } + + public void SetCollision(int primary, int secondary) + { + mPrimaryCollision = primary; + mSecondaryCollision = secondary; + } + + public TileBlockTile[,] Tiles + { + get + { + return mTiles; + } + } + + public int PrimaryCollision + { + get + { + return mPrimaryCollision; + } + } + + public int SecondaryCollision + { + get + { + return mSecondaryCollision; + } + } + + public struct TileBlockTile + { + // PCCY XAAA AAAA AAAA + private short mData; + + public TileBlockTile(short data) + { + byte[] databytes = BitConverter.GetBytes(data); + byte tmp = databytes[0]; + databytes[0] = databytes[1]; + databytes[1] = tmp; + mData = BitConverter.ToInt16(databytes, 0); + } + + public bool P + { + get + { + return ((mData >> 15) & 1) > 0; + } + } + + public int CC + { + get + { + return (mData >> 13) & 3; + } + } + + public bool Y + { + get + { + return ((mData >> 12) & 1) > 0; + } + } + + public bool X + { + get + { + return ((mData >> 11) & 1) > 0; + } + } + + public int Index + { + get + { + return mData & 0x07FF; + } + } + } + } + } +} diff --git a/s2prototype/LevelManager.cs b/s2prototype/LevelManager.cs new file mode 100644 index 0000000..2e75eac --- /dev/null +++ b/s2prototype/LevelManager.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + class LevelManager + { + private SonicGame mGame; + private Level mLevel; + + public LevelManager(SonicGame game) + { + mGame = game; + } + + public void InitialiseLevel(int zone, int act) + { + mLevel = new Level(mGame); + + // Emerald Hill Zone, act 1 + mLevel.Load("data\\levels\\ehz"); + } + } +} diff --git a/s2prototype/LevelObject.cs b/s2prototype/LevelObject.cs new file mode 100644 index 0000000..8cf3b61 --- /dev/null +++ b/s2prototype/LevelObject.cs @@ -0,0 +1,335 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + abstract class LevelObject + { + private SonicGame mGame; + private Level mLevel; + private LevelObjectDefinition mDefinition; + + private int mDisplacementX; + private int mDisplacementY; + + private int mPartialDisplacementX; + private int mPartialDisplacementY; + + private int mVelocityX; + private int mVelocityY; + + private int mRadiusX; + private int mRadiusY; + + private int mWidthPixels; + + private int mDrawPriority; + private bool mFinished; + + public static LevelObject Create(SonicGame game, Level level, LevelObjectDefinition definition) + { + LevelObject obj; + switch (definition.Id) { + case 3: + obj = new CollisionPlaneSwitcher(game, level, definition); + break; + case 6: + obj = new EHZSpiralPathway(game, level, definition); + break; + case 13: + obj = new Signpost(game, level, definition); + break; + case 17: + obj = new LogBridge(game, level, definition); + break; + case 24: + obj = new EHZPlatform(game, level, definition); + break; + case 37: + obj = new Ring(game, level, definition); + break; + case 38: + obj = new Monitor(game, level, definition); + break; + case 54: + obj = new Spikes(game, level, definition); + break; + case 65: + obj = new Spring(game, level, definition); + break; + case 75: + obj = new Buzzer(game, level, definition); + break; + case 92: + obj = new Masher(game, level, definition); + break; + case 121: + obj = new Starpost(game, level, definition); + break; + case 157: + obj = new Coconuts(game, level, definition); + break; + default: + return null; + } + + return obj; + } + + public LevelObject(SonicGame game, Level level) + { + mGame = game; + mLevel = level; + } + + public LevelObject(SonicGame game, Level level, LevelObjectDefinition definition) + : this(game, level) + { + mDefinition = definition; + mDisplacementX = definition.DisplacementX; + mDisplacementY = definition.DisplacementY; + } + + public virtual void Update() { } + public virtual void Draw(Graphics g) { } + public virtual void Touch(LevelObject obj) { } + public virtual void Interact(LevelObject obj) { } + + /// + /// Calculates the next X position by adding the velocity to the current displacement. + /// + /// + protected int GetNextDisplacementX() + { + long xpos = ((long)mDisplacementX << 32) | (mPartialDisplacementX & 0xFFFFFFFF); + xpos += ((long)mVelocityX << 24); + return (int)(xpos >> 32); + } + + /// + /// Calculates the next Y position by adding the velocity to the current displacement. + /// + /// + protected int GetNextDisplacementY() + { + long ypos = ((long)mDisplacementY << 32) | (mPartialDisplacementY & 0xFFFFFFFF); + ypos += ((long)mVelocityY << 24); + return (int)(ypos >> 32); + } + + /// + /// Originally named 'ObjectMove' which updates the displacement by the velocity correctly. + /// + protected virtual void UpdatePosition() + { + long xpos = ((long)mDisplacementX << 32) | (mPartialDisplacementX & 0xFFFFFFFF); + xpos += ((long)mVelocityX << 24); + mDisplacementX = (int)(xpos >> 32); + mPartialDisplacementX = (int)(xpos & 0xFFFFFFFF); + + long ypos = ((long)mDisplacementY << 32) | (mPartialDisplacementY & 0xFFFFFFFF); + ypos += ((long)mVelocityY << 24); + mDisplacementY = (int)(ypos >> 32); + mPartialDisplacementY = (int)(ypos & 0xFFFFFFFF); + } + + /// + /// Originally named 'ObjectMoveAndFall' which updates the position and then applies gravity to the Y velocity. + /// + protected void UpdatePositionWithGravity() + { + UpdatePosition(); + mVelocityY += 56; + } + + public bool IsCloseToCharacter + { + get + { + int directionX, directionY, distX, distY; + mLevel.GetClosestCharacter(this, out directionX, out directionY, out distX, out distY); + + return (Math.Abs(distX) <= 640 && + Math.Abs(distY) <= 420); + } + } + + public abstract int Id + { + get; + } + + public SonicGame Game + { + get + { + return mGame; + } + } + + public Level Level + { + get + { + return mLevel; + } + } + + public int DisplacementX + { + get + { + return mDisplacementX; + } + set + { + mDisplacementX = value; + } + } + + public int DisplacementY + { + get + { + return mDisplacementY; + } + set + { + mDisplacementY = value; + } + } + + public int PartialDisplacementX + { + get + { + return mPartialDisplacementX; + } + set + { + mPartialDisplacementX = value; + } + } + + public int PartialDisplacementY + { + get + { + return mPartialDisplacementY; + } + set + { + mPartialDisplacementY = value; + } + } + + public int VelocityX + { + get + { + return mVelocityX; + } + set + { + mVelocityX = value; + } + } + + public int VelocityY + { + get + { + return mVelocityY; + } + set + { + mVelocityY = value; + } + } + + public int RadiusX + { + get + { + return mRadiusX; + } + set + { + mRadiusX = value; + } + } + + public int RadiusY + { + get + { + return mRadiusY; + } + set + { + mRadiusY = value; + } + } + + public int WidthPixels + { + get + { + return mWidthPixels; + } + set + { + mWidthPixels = value; + } + } + + public int DrawPriority + { + get + { + return mDrawPriority; + } + set + { + mDrawPriority = value; + } + } + + public bool Finished + { + get + { + return mFinished; + } + set + { + mFinished = value; + } + } + + public virtual bool AllowBalancing + { + get + { + return false; + } + } + + public LevelObjectDefinition Definition + { + get + { + return mDefinition; + } + } + + public Rectangle Bounds + { + get + { + return new Rectangle(mDisplacementX - mRadiusX, mDisplacementY - mRadiusY, mRadiusX * 2, mRadiusY * 2); + } + } + } +} diff --git a/s2prototype/LevelObjectDefinition.cs b/s2prototype/LevelObjectDefinition.cs new file mode 100644 index 0000000..1e9f8a9 --- /dev/null +++ b/s2prototype/LevelObjectDefinition.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + class LevelObjectDefinition + { + private int mId; + private int mSubType; + private int mDisplacementX; + private int mDisplacementY; + private bool mRespawn; + private bool mFlipX; + private bool mFlipY; + + public int Id + { + get + { + return mId; + } + set + { + mId = value; + } + } + + public int SubType + { + get + { + return mSubType; + } + set + { + mSubType = value; + } + } + + public int DisplacementX + { + get + { + return mDisplacementX; + } + set + { + mDisplacementX = value; + } + } + + public int DisplacementY + { + get + { + return mDisplacementY; + } + set + { + mDisplacementY = value; + } + } + + public bool Respawn + { + get + { + return mRespawn; + } + set + { + mRespawn = value; + } + } + + public bool FlipX + { + get + { + return mFlipX; + } + set + { + mFlipX = value; + } + } + + public bool FlipY + { + get + { + return mFlipY; + } + set + { + mFlipY = value; + } + } + } +} diff --git a/s2prototype/LevelObjectManager.cs b/s2prototype/LevelObjectManager.cs new file mode 100644 index 0000000..5754f35 --- /dev/null +++ b/s2prototype/LevelObjectManager.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class LevelObjectManager : IEnumerable + { + private bool mLockCollection; + private List mObjects = new List(); + private List mNewObjects = new List(); + + public void Add(LevelObject obj) + { + if (mLockCollection) + mNewObjects.Add(obj); + else + mObjects.Add(obj); + } + + public void Update() + { + mLockCollection = true; + foreach (LevelObject obj in mObjects) + if (!obj.Finished && (obj is Character)) + obj.Update(); + + foreach (LevelObject obj in mObjects) + if (!obj.Finished && !(obj is Character)) + obj.Update(); + mLockCollection = false; + + // Add new objects + AddNewObjects(); + + // Remove finished objects + RemoveFinishedObjects(); + } + + public void Draw(Graphics g, Rectangle view, int minPriority, int maxPriority) + { + mLockCollection = true; + foreach (LevelObject obj in mObjects.OrderBy(o => o.DrawPriority)) { + if (obj.DrawPriority < minPriority || obj.DrawPriority > maxPriority) + continue; + + if (obj.DisplacementX < view.X - 512 || obj.DisplacementX > view.X + view.Width + 512) + continue; + if (obj.DisplacementY < view.Y - 512 || obj.DisplacementY > view.Y + view.Width + 512) + continue; + + int tx = (obj.DisplacementX - view.X) * 4; + int ty = (obj.DisplacementY - view.Y) * 4; + + float pX = (float)BitConverter.ToUInt32(BitConverter.GetBytes(obj.PartialDisplacementX), 0) / (float)UInt32.MaxValue; + float pY = (float)BitConverter.ToUInt32(BitConverter.GetBytes(obj.PartialDisplacementY), 0) / (float)UInt32.MaxValue; + tx += (int)(pX * 4.0f); + ty += (int)(pY * 4.0f); + + g.Translate(tx, ty); + obj.Draw(g); + g.Translate(-tx, -ty); + } + mLockCollection = false; + } + + public void Clear() + { + mObjects.Clear(); + mNewObjects.Clear(); + } + + public IEnumerable GetObjectsInArea(Rectangle area) + { + return mObjects.Where(o => o.Bounds.Intersects(area) && !o.Finished); + } + + private void AddNewObjects() + { + mObjects.AddRange(mNewObjects); + mNewObjects.Clear(); + } + + private void RemoveFinishedObjects() + { + mObjects.RemoveAll(o => o.Finished); + } + + public IEnumerator GetEnumerator() + { + return mObjects.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return mObjects.GetEnumerator(); + } + } +} diff --git a/s2prototype/LevelScreen.cs b/s2prototype/LevelScreen.cs new file mode 100644 index 0000000..7147792 --- /dev/null +++ b/s2prototype/LevelScreen.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class LevelScreen : GameScreen + { + private PlayerView mPlayerView; + + public LevelScreen(SonicGame game) + : base(game) + { + mPlayerView = new PlayerView(game, game.Players[0]); + mPlayerView.Bounds = new Rectangle(0, 0, game.DisplayWidth, game.DisplayHeight); + } + + public override void Update() + { + mPlayerView.Update(); + } + + public override void Draw(Graphics g) + { + mPlayerView.Draw(g); + } + } +} diff --git a/s2prototype/LoadingScreen.cs b/s2prototype/LoadingScreen.cs new file mode 100644 index 0000000..403abe9 --- /dev/null +++ b/s2prototype/LoadingScreen.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class LoadingScreen : GameScreen + { + private Action mLoadingRoutine; + private Action mFinishedRoutine; + private Thread mLoadingThread; + + private int mUpdateCount; + + public LoadingScreen(SonicGame game) + : base(game) + { + } + + public override void Update() + { + if (mLoadingThread != null) { + if (!mLoadingThread.IsAlive) { + mLoadingThread = null; + if (mFinishedRoutine != null) + mFinishedRoutine.Invoke(); + } + } + + mUpdateCount++; + } + + public override void Draw(Graphics g) + { + int yellow; + if (mUpdateCount % 64 >= 32) + yellow = 255 - ((mUpdateCount % 32) * 4); + else + yellow = (mUpdateCount % 32) * 4; + + Color textColour = new Color(255, (byte)yellow, 0); + ResourceManager.NormalFont.DrawString(g, "LOADING", 10, 10, textColour); + } + + public void Begin() + { + if (mLoadingRoutine == null) + return; + + mLoadingThread = new Thread(new ThreadStart(mLoadingRoutine)); + mLoadingThread.Name = "Loading thread"; + mLoadingThread.Priority = ThreadPriority.Highest; + mLoadingThread.Start(); + } + + public Action LoadingRoutine + { + get + { + return mLoadingRoutine; + } + set + { + mLoadingRoutine = value; + } + } + + public Action FinishedRoutine + { + get + { + return mFinishedRoutine; + } + set + { + mFinishedRoutine = value; + } + } + } +} diff --git a/s2prototype/MusicManager.cs b/s2prototype/MusicManager.cs new file mode 100644 index 0000000..1e2653c --- /dev/null +++ b/s2prototype/MusicManager.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.Xna.Framework.Audio; + +namespace IntelOrca.Sonic +{ + class MusicManager : IDisposable + { + private float mVolume = 0.7f; + private SoundEffectInstance mCurrentlyPlayingMusic; + private SoundEffectInstance mCurrentlyPlayingJingle; + + public void StopAllMusic() + { + if (mCurrentlyPlayingMusic != null) { + mCurrentlyPlayingMusic.Stop(); + mCurrentlyPlayingMusic.Dispose(); + mCurrentlyPlayingMusic = null; + } + + if (mCurrentlyPlayingJingle != null) { + mCurrentlyPlayingJingle.Stop(); + mCurrentlyPlayingJingle.Dispose(); + mCurrentlyPlayingJingle = null; + } + } + + public void Update() + { + if (mCurrentlyPlayingJingle != null) { + if (mCurrentlyPlayingJingle.State == SoundState.Stopped) { + mCurrentlyPlayingJingle.Stop(); + mCurrentlyPlayingJingle.Dispose(); + mCurrentlyPlayingJingle = null; + + if (mCurrentlyPlayingMusic != null) + mCurrentlyPlayingMusic.Volume = mVolume; + } + } + } + + public void PlayMusic(SoundEffect soundEffect) + { + if (mCurrentlyPlayingJingle != null) { + mCurrentlyPlayingJingle.Stop(); + mCurrentlyPlayingJingle.Dispose(); + mCurrentlyPlayingJingle = null; + } + + if (mCurrentlyPlayingMusic != null) { + mCurrentlyPlayingMusic.Stop(); + mCurrentlyPlayingMusic.Dispose(); + } + + mCurrentlyPlayingMusic = soundEffect.CreateInstance(); + mCurrentlyPlayingMusic.Volume = mVolume; + mCurrentlyPlayingMusic.IsLooped = true; + mCurrentlyPlayingMusic.Play(); + } + + public void PlayJingle(SoundEffect soundEffect) + { + if (mCurrentlyPlayingJingle != null) { + mCurrentlyPlayingJingle.Stop(); + mCurrentlyPlayingJingle.Dispose(); + } + + if (mCurrentlyPlayingMusic != null) + mCurrentlyPlayingMusic.Volume = 0.0f; + + mCurrentlyPlayingJingle = soundEffect.CreateInstance(); + mCurrentlyPlayingJingle.Volume = mVolume; + mCurrentlyPlayingJingle.Play(); + } + + public void Dispose() + { + StopAllMusic(); + } + } +} diff --git a/s2prototype/Objects/Animal.cs b/s2prototype/Objects/Animal.cs new file mode 100644 index 0000000..339224b --- /dev/null +++ b/s2prototype/Objects/Animal.cs @@ -0,0 +1,119 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Animal : LevelObject + { + private int mRoutine; + private int mGroundVelocityX; + private int mGroundVelocityY; + + private int mMappingFrame; + private int mAnimationDuration; + private bool mFacingLeft; + + private bool mJumping; + + private int mSubType; + + public Animal(SonicGame game, Level level) + : base(game, level) + { + } + + public override void Draw(Graphics g) + { + int srcX = mSubType % 2 + mMappingFrame; + int srcY = mSubType / 2; + + Rectangle dst = new Rectangle(-12 * Game.DisplayScale, -16 * Game.DisplayScale, 24 * Game.DisplayScale, 32 * Game.DisplayScale); + Rectangle src = new Rectangle(srcX * 24 * Game.DisplayScale, srcY * 32 * Game.DisplayScale, 24 * Game.DisplayScale, 32 * Game.DisplayScale); + + SpriteEffects fx = SpriteEffects.None; + if (VelocityX < 0) + fx = SpriteEffects.FlipHorizontally; + + g.DrawImage(ResourceManager.AnimalsTexture, dst, src, Color.White, fx); + } + + public override void Update() + { + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + MainUpdate(); + break; + } + } + + private void Init() + { + mGroundVelocityX = -0x300; + mGroundVelocityY = -0x400; + + VelocityX = 0; + VelocityY = -0x400; + + mRoutine = 2; + } + + private void MainUpdate() + { + if (!IsCloseToCharacter) { + Finished = true; + return; + } + + if (mJumping) { + UpdatePosition(); + VelocityY += 24; + } else { + UpdatePositionWithGravity(); + } + + if (VelocityY > 0) { + int dist, angle = 0; + Level.FindFloor(DisplacementX, DisplacementY + 8, 1, false, true, out dist, ref angle); + if (dist < 0) { + // Bounce of ground + DisplacementY += dist; + VelocityX = mGroundVelocityX; + VelocityY = mGroundVelocityY; + mJumping = true; + } + } + + if (VelocityX == 0) { + mMappingFrame = 0; + } else { + mAnimationDuration--; + if (mAnimationDuration < 0) { + mAnimationDuration = 2; + mMappingFrame++; + if (mMappingFrame > 2 || mMappingFrame < 1) + mMappingFrame = 1; + } + } + } + + public override int Id + { + get { return 40; } + } + + public int SubType + { + get + { + return mSubType; + } + set + { + mSubType = value; + } + } + } +} diff --git a/s2prototype/Objects/Badnik.cs b/s2prototype/Objects/Badnik.cs new file mode 100644 index 0000000..8e98988 --- /dev/null +++ b/s2prototype/Objects/Badnik.cs @@ -0,0 +1,67 @@ +using System; + +namespace IntelOrca.Sonic +{ + abstract class Badnik : LevelObject + { + public Badnik(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + } + + public override void Touch(LevelObject obj) + { + Character character = obj as Character; + if (character == null) + return; + + // Is character is not invincible + if ((character.StatusSecondary & 2) == 0) { + if (character.Anim != CharacterAnimation.Spindash) { + if (character.Anim != CharacterAnimation.Roll) { + character.Hurt(this); + return; + } + } + } + + int[] ChainPoints = new int[] { 100, 200, 500, 1000 }; + int points = ChainPoints[Math.Min(character.Player.ChainBonusCounter, 3)]; + if (character.Player.ChainBonusCounter >= 16) + points = 10000; + + character.Player.AddPoints(points); + character.Player.ChainBonusCounter++; + + // Add animal + Animal animalObject = new Animal(Game, Level); + animalObject.DisplacementX = DisplacementX; + animalObject.DisplacementY = DisplacementY; + Level.Objects.Add(animalObject); + + // Add explosion object + Explosion explosionObject = new Explosion(Game, Level); + explosionObject.DisplacementX = DisplacementX; + explosionObject.DisplacementY = DisplacementY; + Level.Objects.Add(explosionObject); + + // This object is now finished + Finished = true; + + // Character hit enemy from below + if (character.VelocityY < 0) { + character.VelocityY += 256; + return; + } + + // Character is below enemy but falling down + if (character.DisplacementY >= DisplacementY) { + character.VelocityY -= 256; + return; + } + + // Rebound the character 1 : 1 + character.VelocityY = -character.VelocityY; + } + } +} diff --git a/s2prototype/Objects/Buzzer.cs b/s2prototype/Objects/Buzzer.cs new file mode 100644 index 0000000..af326eb --- /dev/null +++ b/s2prototype/Objects/Buzzer.cs @@ -0,0 +1,193 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Buzzer : Badnik + { + private Animation mAnimation; + private int mStatus; + private int mFlightDuration; + private int mTurnWaitDuration; + private int mFireDuration; + private bool mFiredThisRound; + + private static byte[][] AnimationData = new byte[][] { + new byte[] { 0, 0, 0xFF }, + new byte[] { 2, 1, 2, 0xFF }, + new byte[] { 3, 3, 4, 0xFF }, + new byte[] { 2, 5, 6, 0xFF }, + }; + + public Buzzer(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mAnimation = new Animation(AnimationData); + + RadiusX = 16; + RadiusY = 8; + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(mAnimation.FrameValue * 48 * Game.DisplayScale, 0 * Game.DisplayScale, 48 * Game.DisplayScale, 46 * Game.DisplayScale); + Rectangle dst = new Rectangle(-24 * Game.DisplayScale, -23 * Game.DisplayScale, 48 * Game.DisplayScale, 46 * Game.DisplayScale); + + SpriteEffects fx = (VelocityX >= 0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None); + g.DrawImage(ResourceManager.BuzzerTexture, dst, src, Color.White, fx); + } + + public override void Update() + { + if (!IsCloseToCharacter) { + // Reset object + mStatus = 0; + return; + } + + if (mStatus == 0) { + Init(); + return; + } else if (mStatus == 2) { + UpdateFireProjectile(); + } else { + if (CheckForCharacter()) + return; + + if (mTurnWaitDuration > 0) { + mTurnWaitDuration--; + if (mTurnWaitDuration == 15) { + VelocityX = -VelocityX; + mFiredThisRound = false; + } + } else { + mAnimation.Index = 1; + mFlightDuration--; + if (mFlightDuration > 0) { + UpdatePosition(); + } else { + mFlightDuration = 256; + mTurnWaitDuration = 30; + mAnimation.Index = 0; + } + } + } + + mAnimation.Update(); + } + + private void Init() + { + mFlightDuration = 256; + mStatus = 1; + VelocityX = -256; + DisplacementX = Definition.DisplacementX; + } + + private bool CheckForCharacter() + { + if (mFiredThisRound) + return false; + + int directionX, directionY, distX, distY; + Character character = Level.GetClosestCharacter(this, out directionX, out directionY, out distX, out distY); + + int d0 = DisplacementX - character.DisplacementX; + int d1 = d0; + if (d1 < 0) + d0 = -d0; + if (d0 < 40 || d0 > 48) + return false; + + mStatus = 2; + mAnimation.Index = 2; + mFireDuration = 50; + mFiredThisRound = true; + return true; + } + + private void UpdateFireProjectile() + { + mFireDuration--; + if (mFireDuration < 0) { + mStatus = 1; + return; + } + + if (mFireDuration == 20) + FireProjectile(); + } + + private void FireProjectile() + { + BuzzerProjectile projectile = new BuzzerProjectile(Game, Level); + projectile.DisplacementX = DisplacementX; + projectile.DisplacementY = DisplacementY + 24; + projectile.VelocityY = 384; + + if (VelocityX >= 0) { + projectile.DisplacementX -= 13; + projectile.VelocityX = 384; + } else { + projectile.VelocityX = -384; + projectile.DisplacementX += 13; + } + + Level.Objects.Add(projectile); + } + + public override int Id + { + get { return 75; } + } + + class BuzzerProjectile : LevelObject + { + private Animation mAnimation; + + public BuzzerProjectile(SonicGame game, Level level) + : base(game, level) + { + mAnimation = new Animation(AnimationData); + mAnimation.Index = 3; + RadiusX = 4; + RadiusY = 4; + mAnimation.Update(); + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(mAnimation.FrameValue * 48 * Game.DisplayScale, 0 * Game.DisplayScale, 48 * Game.DisplayScale, 46 * Game.DisplayScale); + Rectangle dst = new Rectangle(-24 * Game.DisplayScale, -23 * Game.DisplayScale, 48 * Game.DisplayScale, 46 * Game.DisplayScale); + + SpriteEffects fx = (VelocityX >= 0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None); + g.DrawImage(ResourceManager.BuzzerTexture, dst, src, Color.White, fx); + } + + public override void Update() + { + if (!IsCloseToCharacter) { + Finished = true; + return; + } + + UpdatePosition(); + mAnimation.Update(); + } + + public override void Touch(LevelObject obj) + { + Character character = obj as Character; + if (character == null) + return; + + character.Hurt(this); + } + + public override int Id + { + get { return 75; } + } + } + } +} diff --git a/s2prototype/Objects/Character.cs b/s2prototype/Objects/Character.cs new file mode 100644 index 0000000..9fa8012 --- /dev/null +++ b/s2prototype/Objects/Character.cs @@ -0,0 +1,2743 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + [Flags] + enum CharacterState + { + FacingLeft = 1, + Airborne = 2, + Spinning = 4, + OnObject = 8, + RollJumping = 16, + Pushing = 32, + Underwater = 64, + } + + enum CharacterAnimation + { + Walk, + Run, + Roll, + Roll2, + Push, + Wait, + Balance, + LookUp, + Duck, + Spindash, + Blink, + GetUp, + Balance2, + Stop, + Float, + Float2, + Spring, + Hang, + Dash2, + Dash3, + Hang2, + Bubble, + DeathBW, + Drown, + Death, + Hurt, + Hurt2, + Slide, + Blank, + Balance3, + Balance4, + Transform, + Lying, + LieDown, + } + + abstract class Character : LevelObject + { + private Player mPlayer; + + private ControllerState mControlState = new ControllerState(); + private ControllerState mLastControlState = new ControllerState(); + private ControllerState mNewDownControlState = new ControllerState(); + private bool mSuper; + + private CharacterState mStatus; + private int mRoutine; + private int mRoutineSecondary; + private int mAngle; + private int mGroundVelocity; + private int mAirLeft; + private int mObjControl; + private int mStatusSecondary; + private int mMoveLock; + private int mNextTilt; + private int mTilt; + private int mStickToConvex; + + private LevelObject mInteract; + private int mLayer; + private bool mJumping; + + private int mSkidSoundDuration; + + private int mTopSpeed; + private int mAcceleration; + private int mDeceleration; + + private int mPrimaryAngle; + private int mSecondaryAngle; + + private Camera mCamera = new Camera(); + + #region Constant data + + private static int[] SpindashSpeeds = new int[] { + 2048, 2176, 2304, 2432, 2560, 2688, 2816, 2944, 3072 }; + + private static byte[][] AnimationData = new byte[][] { + // 0: Walk + new byte[] { 0xFF, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0xD, 0xE, 0xFF }, + // 1: Run + new byte[] { 0xFF, 0x2D, 0x2E, 0x2F, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + // 2: Roll + new byte[] { 0xFE, 0x3D, 0x41, 0x3E, 0x41, 0x3F, 0x41, 0x40, 0x41, 0xFF }, + // 3: Roll 2 + new byte[] { 0xFE, 0x3D, 0x41, 0x3E, 0x41, 0x3F, 0x41, 0x40, 0x41, 0xFF }, + // 4: Push + new byte[] { 0xFD, 0x48, 0x49, 0x4A, 0x4B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + // 5: Wait + new byte[] { + 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 3, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 4, 4, 4, 5, 5, + 5, 4, 4, 4, 5, 5, 5, 4, 4, 4, 5, 5, 5, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 5, 5, 5, 4, 4, 4, + 5, 5, 5, 4, 4, 4, 5, 5, 5, 4, 4, 4, 5, 5, 5, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 5, 5, 5, 4, + 4, 4, 5, 5, 5, 4, 4, 4, 5, 5, 5, 4, 4, 4, 5, 5, + 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 5, 5, + 5, 4, 4, 4, 5, 5, 5, 4, 4, 4, 5, 5, 5, 4, 4, 4, + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 8, + 8, 9, 9, 9, 0xFE, 6 }, + // 6: Balance + new byte[] { 9, 0xCC, 0xCD, 0xCE, 0xCD, 0xFF }, + // 7: Look up + new byte[] { 5, 0xB, 0xC, 0xFE, 1 }, + // 8: Duck + new byte[] { 5, 0x4C, 0x4D, 0xFE, 1 }, + // 9: Spindash + new byte[] { 0, 0x42, 0x43, 0x42, 0x44, 0x42, 0x45, 0x42, 0x46, 0x42, 0x47, 0xFF }, + // 10: Blink + new byte[] { 1, 2, 0xFD, 0 }, + // 11: Get up + new byte[] { 3, 0xA, 0xFD, 0 }, + // 12: Balance 2 + new byte[] { 3, 0xC8, 0xC9, 0xCA, 0xCB, 0xFF }, + // 13: Stop, halt / skidding animation + new byte[] { 5, 0xD2, 0xD3, 0xD4, 0xD5, 0xFD, 0 }, + // 14: Float + new byte[] { 7, 0x54, 0x59, 0xFF }, + // 15: Float 2 + new byte[] { 7, 0x54, 0x55, 0x56, 0x57, 0x58, 0xFF }, + // 16: Spring + new byte[] { 0x2F, 0x5B, 0xFD, 0 }, + // 17: Hang + new byte[] { 1, 0x50, 0x51, 0xFF }, + // 18: Dash 2 + new byte[] { 0xF, 0x43, 0x43, 0x43, 0xFE, 1 }, + // 19: Dash 3 + new byte[] { 0xF, 0x43, 0x44, 0xFE, 1 }, + // 20: Hang 2 + new byte[] { 0x13, 0x6B, 0x6C, 0xFF }, + // 21: Bubble, breathe + new byte[] { 0xB, 0x5A, 0x5A, 0x11, 0x12, 0xFD, 0 }, + // 22: Death BW + new byte[] { 0x20, 0x5E, 0xFF }, + // 23: Drown + new byte[] { 0x20, 0x5D, 0xFF }, + // 24: Death + new byte[] { 0x20, 0x5C, 0xFF }, + // 25: Hurt + new byte[] { 0x40, 0x4E, 0xFF }, + // 26: Hurt2 + new byte[] { 0x40, 0x4E, 0xFF }, + // 27: Slide + new byte[] { 9, 0x4E, 0x4F, 0xFF }, + // 28: Blank + new byte[] { 0x77, 0, 0xFD, 0}, + // 29: Balance 3 + new byte[] { 0x13, 0xD0, 0xD1, 0xFF }, + // 30: Balance 4 + new byte[] { 3, 0xCF, 0xC8, 0xC9, 0xCA, 0xCB, 0xFE, 4 }, + // 31: Lying + new byte[] { 9, 8, 9, 0xFF }, + // 32: Lie down + new byte[] { 3, 7, 0xFD, 0}, + }; + + #endregion + + public Character(SonicGame game, Level level) + : base(game, level) + { + DrawPriority = 10; + + // DisplacementX = 0x0060; + // DisplacementY = 0x028F; + + // DisplacementX = 1303; + // DisplacementY = 625; + } + + public override void Draw(Graphics g) + { + // Hide rapidly if we are invulnerable + if (mInvulnerable) + if ((mInvulnerableTime % 8) > 4) + return; + + Texture2D[] spriteSheet = ResourceManager.SonicTextures; + + if (mMappingFrame == 0) + return; + + int sidx = mMappingFrame - 1; + Rectangle src = new Rectangle(0, 0, 64 * Game.DisplayScale, 70 * Game.DisplayScale); + Rectangle dst = new Rectangle(0, 0, 64 * Game.DisplayScale, 70 * Game.DisplayScale); + SpriteEffects fx = SpriteEffects.None; + if ((mStatus & CharacterState.FacingLeft) != 0) + fx = SpriteEffects.FlipHorizontally; + + if (mFlipAngle != 0) { + if ((mStatus & CharacterState.FacingLeft) != 0 && mFlipTurned == 0) + fx = SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically; + } + + float angle = (float)((double)mAngle / 128.0 * Math.PI); + if (Math.Abs(mAngle) < 32 && (mStatus & CharacterState.Airborne) == 0) + angle = 0.0f; + if ((mStatus & CharacterState.Spinning) != 0) + angle = 0.0f; + if (mGroundVelocity == 0) + angle = 0.0f; + + g.DrawImage( + spriteSheet[sidx], + dst, + src, + Color.White, + angle, + new Vector2(32 * Game.DisplayScale, 35 * Game.DisplayScale), + fx); + + // Spindash dust + if (mSpindashing) + DrawSpindashDust(g); + + if ((mStatusSecondary & 1) != 0) + DrawShield(g); + } + + public override void Update() + { + if (mControlState.Left && mControlState.Right) + mControlState.Set(4 | 8, false); + if (mControlState.Up && mControlState.Down) + mControlState.Set(1 | 2, false); + + mNewDownControlState = mControlState.GetNewDown(mLastControlState); + + // Normal: + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + UpdateControl(); + break; + case 4: + UpdateHurt(); + break; + case 6: + UpdateDying(); + break; + case 8: + Gone(); + break; + case 10: + Respawning(); + break; + } + + mLastControlState = mControlState; + } + + private void Init() + { + mCamera.X = DisplacementX; + mCamera.Y = DisplacementY; + + mRoutine = 2; + RadiusY = 19; + RadiusX = 9; + // mappings = Mapunc_Sonic + mTopSpeed = 1536; + mAcceleration = 12; + mDeceleration = 128; + //if (!last_star_pole_hit) { + if (true) { + // art tile + // Ajust2PArtPointer + mLayer = 1; + // saved_x_pos = mXPos; + // saved_y_pos = mYPos; + // saved_art_tile = art_tile + // saved_layer = layer; + } + + mFlipsRemaining = 0; + mFlipSpeed = 4; + // super_sonic_flag = false + mAirLeft = 30; + DisplacementX -= 32; + DisplacementY += 4; + // Sonic_Pos_Record_Index = 0 + // initialise sonic record pos + DisplacementX += 32; + DisplacementY -= 4; + + UpdateControl(); + } + + /// + /// Normal state for Sonic. + /// + private void UpdateControl() + { + // check if controls are locked + + if (mInteract != null) + mInteract.Interact(this); + + if (mObjControl == 0) { + switch (mStatus & (CharacterState.Spinning | CharacterState.Airborne)) { + case 0: + UpdateNormalState(); + break; + case CharacterState.Airborne: + UpdateAirborneState(); + break; + case CharacterState.Spinning: + UpdateRollState(); + break; + case CharacterState.Airborne | CharacterState.Spinning: + UpdateAirSpinState(); + break; + } + } + + if (mInvulnerable) + UpdateInvulnerability(); + + if ((mStatusSecondary & 2) != 0) + UpdateInvincibility(); + + if ((mStatusSecondary & 1) != 0) + UpdateShield(); + + if ((mStatusSecondary & 4) != 0) { + UpdateSpeedShoes(); + } + + if (mSkidSoundDuration > 0) + mSkidSoundDuration--; + + mNextTilt = mPrimaryAngle; + mTilt = mSecondaryAngle; + // if (WindTunnel_flag) { + // if (anim) anim = next_anim + //} + Animate(); + if (mObjControl >= 0) + TouchResponse(); + + CameraScroll(); + } + + private void Gone() + { + + } + + private void Respawning() + { + + } + + public override int Id + { + get + { + return 1; + } + } + + public int GroundVelocity + { + get + { + return mGroundVelocity; + } + set + { + mGroundVelocity = value; + } + } + + public ControllerState ControllerState + { + get + { + return mControlState; + } + set + { + mControlState = value; + } + } + + public int Layer + { + get + { + return mLayer; + } + set + { + mLayer = value; + } + } + + public int LayerPlus + { + get + { + return mLayer; + } + set + { + mLayer = value; + } + } + + public CharacterState Status + { + get + { + return mStatus; + } + set + { + mStatus = value; + } + } + + public LevelObject InteractionObject + { + get + { + return mInteract; + } + set + { + mInteract = value; + } + } + + public int StatusSecondary + { + get + { + return mStatusSecondary; + } + set + { + mStatusSecondary = value; + } + } + + public CharacterAnimation Anim + { + get + { + return mAnim; + } + set + { + mAnim = value; + } + } + + public Player Player + { + get + { + return mPlayer; + } + set + { + mPlayer = value; + } + } + + public int Angle + { + get + { + return mAngle; + } + set + { + mAngle = value; + } + } + + public int ObjectControl + { + get + { + return mObjControl; + } + set + { + mObjControl = value; + } + } + + public int Routine + { + get + { + return mRoutine; + } + set + { + mRoutine = value; + } + } + + public int MoveLock + { + get + { + return mMoveLock; + } + set + { + mMoveLock = value; + } + } + + #region Normal + + #region Ground standing / walking / running + + /// + /// Subroutine to reset Sonic's mode when he lands on the floor. + /// + public void ResetOnFloor() + { + if (!mSpindashing) { + mAnim = 0; + if ((mStatus & CharacterState.Spinning) != 0) { + mStatus &= ~CharacterState.Spinning; + RadiusY = 19; + RadiusX = 9; + mAnim = 0; + DisplacementY -= 5; + } + } + + mStatus &= ~CharacterState.Airborne; + mStatus &= ~CharacterState.Pushing; + mStatus &= ~CharacterState.RollJumping; + mJumping = false; + mPlayer.ChainBonusCounter = 0; + mFlipAngle = 0; + mFlipTurned = 0; + mFlipsRemaining = 0; + mLookDelayCounter = 0; + if (mAnim == CharacterAnimation.Hang2) + mAnim = 0; + } + + /// + /// Orignally called 'MdNormalChecks' which updates movement when standing / walking or running + /// + private void UpdateNormalState() + { + if (!mControlState.A && !mControlState.B && !mControlState.C) { + if (mAnim == CharacterAnimation.Blink || mAnim == CharacterAnimation.GetUp) + return; + if (mAnim == CharacterAnimation.Wait && mAnimFrame >= 30) { + if (!mControlState.Up && !mControlState.Down && !mControlState.Left && !mControlState.Right && !mControlState.A && !mControlState.B && !mControlState.C) + return; + mAnim = CharacterAnimation.Blink; + if (mAnimFrame < 172) + return; + mAnim = CharacterAnimation.GetUp; + return; + } + } + + if (CheckSpindash()) + return; + if (CheckForJump()) + return; + ApplySlopeResistance(); + UpdateNormalMovement(); + CheckForRollStart(); + KeepInLevelBoundaries(); + UpdatePosition(); + UpdateAngleAndPositionToFloor(); + ApplySlopeLocking(); + } + + private void UpdateNormalMovement() + { + if (mStatusSecondary >= 0) { + if (mMoveLock == 0) { + if (mControlState.Left) + MoveLeft(); + if (mControlState.Right) + MoveRight(); + + // Is sonic on a slope + if (((mAngle + 32) & 192) == 0) { + // Is sonic not moving + if (mGroundVelocity == 0) { + mStatus = mStatus & ~CharacterState.Pushing; + + // Use standing animation + mAnim = CharacterAnimation.Wait; + + if (!UpdateBalancing()) + UpdateLooking(); + } + } + } + + if (!mLookingUpOrDucking) + RestoreCameraBias(); + + if (!mControlState.Left && !mControlState.Right) { + if (mGroundVelocity != 0) { + if (mGroundVelocity < 0) { + if (mGroundVelocity + mAcceleration >= 0) + mGroundVelocity = 0; + else + mGroundVelocity += mAcceleration; + } else { + if (mGroundVelocity - mAcceleration <= 0) + mGroundVelocity = 0; + else + mGroundVelocity -= mAcceleration; + } + } + } + } + + // Traction + VelocityX = (SonicMaths.Cos(mAngle) * mGroundVelocity) >> 8; + VelocityY = (SonicMaths.Sin(mAngle) * mGroundVelocity) >> 8; + + CheckPushing(); + } + + /// + /// Checks if we are on the edge of a platform. Applies balancing logic if so. + /// + /// True if we are balancing. + private bool UpdateBalancing() + { + if ((mStatus & CharacterState.OnObject) != 0) { + // Balance on object + LevelObject obj = mInteract; + if (obj.AllowBalancing) { + if (!mSuper) { + if (DisplacementX <= obj.DisplacementX) { + // Left + int gap = (obj.DisplacementX - obj.RadiusX) - (DisplacementX - RadiusX); + if (gap >= 7) { + if ((mStatus & CharacterState.FacingLeft) != 0) { + if (gap >= 13) + mAnim = CharacterAnimation.Balance2; + else + mAnim = CharacterAnimation.Balance; + } else { + if (gap >= 13) { + mAnim = CharacterAnimation.Balance4; + mStatus |= CharacterState.FacingLeft; + } else { + mAnim = CharacterAnimation.Balance3; + } + } + } + } else { + // Right + int gap = (DisplacementX + RadiusX) - (obj.DisplacementX + obj.RadiusX); + if (gap >= 7) { + if ((mStatus & CharacterState.FacingLeft) == 0) { + if (gap >= 13) + mAnim = CharacterAnimation.Balance2; + else + mAnim = CharacterAnimation.Balance; + } else { + if (gap >= 13) { + mAnim = CharacterAnimation.Balance4; + mStatus &= ~CharacterState.FacingLeft; + } else { + mAnim = CharacterAnimation.Balance3; + } + } + } + } + } else { + + } + } + } else { + // Balancing checks for when you're on the edge of part of the level + int d1 = ChkFloorEdge(DisplacementX, DisplacementY); + if (d1 >= 12) { + if (mSuper) { + // SuperSonic_Balance2 + if (mNextTilt == 3) { + mStatus = mStatus & ~CharacterState.FacingLeft; + mAnim = CharacterAnimation.Balance; + mLookDelayCounter = 0; + return true; + } else if (mTilt == 3) { + mStatus |= CharacterState.FacingLeft; + mAnim = CharacterAnimation.Balance; + mLookDelayCounter = 0; + return true; + } + } + + if (mNextTilt != 3) { + // Sonic_BalanceLeft + if (mTilt == 3) { + if ((mStatus & CharacterState.FacingLeft) != 0) { + mAnim = CharacterAnimation.Balance; + d1 = ChkFloorEdge(DisplacementX + 6, DisplacementY); + if (d1 >= 12) + mAnim = CharacterAnimation.Balance2; + } else { + mAnim = CharacterAnimation.Balance3; + d1 = ChkFloorEdge(DisplacementX + 6, DisplacementY); + if (d1 >= 12) { + mAnim = CharacterAnimation.Balance4; + mStatus |= CharacterState.FacingLeft; + } + } + mLookDelayCounter = 0; + return true; + } + } else { + if ((mStatus & CharacterState.FacingLeft) == 0) { + mAnim = CharacterAnimation.Balance; + d1 = ChkFloorEdge(DisplacementX - 6, DisplacementY); + if (d1 >= 12) + mAnim = CharacterAnimation.Balance2; + } else { + mAnim = CharacterAnimation.Balance3; + d1 = ChkFloorEdge(DisplacementX - 6, DisplacementY); + if (d1 >= 12) { + mAnim = CharacterAnimation.Balance4; + mStatus = mStatus & ~CharacterState.FacingLeft; + } + } + mLookDelayCounter = 0; + return true; + } + } + } + + return false; + } + + private void MoveLeft() + { + if (mGroundVelocity > 0) { + // Turn left + int d0 = mGroundVelocity - mDeceleration; + if (d0 < 0) + d0 = -128; + mGroundVelocity = d0; + d0 = (mAngle + 32) & 192; + if (d0 != 0) + return; + + if (mGroundVelocity < 1024) + return; + + mAnim = CharacterAnimation.Stop; + mStatus &= ~CharacterState.FacingLeft; + + if (mSkidSoundDuration <= 0) { + Level.AddSound(ResourceManager.BrakeSound, DisplacementX, DisplacementY); + mSkidSoundDuration = 8; + } + + if (mAirLeft < 12) + return; + + // Do skid dust + + return; + } + + if ((mStatus & CharacterState.FacingLeft) == 0) { + mStatus &= ~CharacterState.Pushing; + mNextAnim = CharacterAnimation.Run; + } + mStatus |= CharacterState.FacingLeft; + + int newInertia = mGroundVelocity - mAcceleration; + int maxInertia = -mTopSpeed; + if (newInertia <= maxInertia) { + newInertia += mAcceleration; + if (newInertia > maxInertia) + newInertia = maxInertia; + } + + mGroundVelocity = newInertia; + mAnim = 0; + } + + private void MoveRight() + { + if (mGroundVelocity < 0) { + // Turn right + int d0 = mGroundVelocity + mDeceleration; + if (d0 > 0) + d0 = 128; + mGroundVelocity = d0; + d0 = (mAngle + 32) & 192; + if (d0 != 0) + return; + + if (mGroundVelocity > -1024) + return; + + mAnim = CharacterAnimation.Stop; + mStatus |= CharacterState.FacingLeft; + + if (mSkidSoundDuration <= 0) { + Level.AddSound(ResourceManager.BrakeSound, DisplacementX, DisplacementY); + mSkidSoundDuration = 8; + } + + if (mAirLeft < 12) + return; + + // Do skid dust + + return; + } + + if ((mStatus & CharacterState.FacingLeft) != 0) { + mStatus &= ~CharacterState.Pushing; + mNextAnim = CharacterAnimation.Run; + } + mStatus &= ~CharacterState.FacingLeft; + + int newInertia = mGroundVelocity + mAcceleration; + int maxInertia = mTopSpeed; + if (newInertia >= maxInertia) { + newInertia -= mAcceleration; + if (newInertia < maxInertia) + newInertia = maxInertia; + } + + mGroundVelocity = newInertia; + mAnim = 0; + } + + /// + /// Checks if we are pushing against a wall. + /// + private void CheckPushing() + { + if (mAngle + 64 < 0) + return; + + if (mGroundVelocity == 0) + return; + + byte ang = (byte)(mAngle + (mGroundVelocity > 0 ? -64 : 64)); + int roomInFront = CalcRoomInFront(ang); + if (roomInFront >= 0) + return; + + roomInFront *= 256; + ang += 32; + ang &= 192; + if (ang == 0) { + VelocityY += roomInFront; + } else if (ang == 64) { + VelocityX -= roomInFront; + mStatus |= CharacterState.Pushing; + mGroundVelocity = 0; + } else if (ang == 128) { + VelocityY -= roomInFront; + } else { + VelocityX += roomInFront; + mStatus |= CharacterState.Pushing; + mGroundVelocity = 0; + } + } + + /// + /// Increases or decreases the speed if not zero by the slope resistance. + /// + private void ApplySlopeResistance() + { + if ((mAngle + 96) % 256 >= 192) + return; + + int resistance = (SonicMaths.Sin(mAngle) * 32) >> 8; + if (mGroundVelocity == 0 || resistance == 0) + return; + + mGroundVelocity += resistance; + } + + /// + /// Stops Sonic from walking up a slope that is too steep by locking movement control. + /// + private void ApplySlopeLocking() + { + if (mStickToConvex != 0) + return; + + if (mMoveLock > 0) { + mMoveLock--; + return; + } + + if (((mAngle + 32) & 192) == 0) + return; + + if (Math.Abs(mGroundVelocity) > 640) + return; + + mGroundVelocity = 0; + mStatus |= CharacterState.Airborne; + mMoveLock = 30; + } + + /// + /// Originally 'AnglePos' which changes the angle and position to the angle and position of the floor. + /// + private void UpdateAngleAndPositionToFloor() + { + if ((mStatus & CharacterState.OnObject) != 0) { + mPrimaryAngle = 0; + mSecondaryAngle = 0; + SetAngle(0); + return; + } + + mPrimaryAngle = 3; + mSecondaryAngle = 3; + + if (mAngle > -96 && mAngle < -32) + WalkRightWall(); + else if (mAngle >= -32 && mAngle <= 32) + WalkFloor(); + else if (mAngle > 32 && mAngle <= 96) + WalkLeftWall(); + else + WalkCeiling(); + } + + private void WalkFloor() + { + int d0; + + int leftDistance, rightDistance; + Level.FindFloor(DisplacementX + RadiusX, DisplacementY + RadiusY, mLayer, false, true, out rightDistance, ref mPrimaryAngle); + Level.FindFloor(DisplacementX - RadiusX, DisplacementY + RadiusY, mLayer, false, true, out leftDistance, ref mSecondaryAngle); + + int d1 = SetAngleFromFloor(leftDistance, rightDistance); + if (d1 == 0) + return; + + if (d1 >= 0) { + d0 = (Math.Abs(VelocityX) >> 8) + 4; + if (d0 >= 14) + d0 = 14; + if (d1 > d0) { + if (mStickToConvex != 0) { + DisplacementY += d1; + return; + } + + mStatus |= CharacterState.Airborne; + mStatus &= ~CharacterState.Pushing; + mNextAnim = CharacterAnimation.Run; + return; + } + + DisplacementY += d1; + return; + } + + if (d1 < -14) + return; + + DisplacementY += d1; + } + + private void WalkRightWall() + { + int leftDist, rightDist, minDist, speed; + Level.FindWallRight(DisplacementX + RadiusY, DisplacementY - RadiusX, mLayer, false, true, out rightDist, ref mPrimaryAngle); + Level.FindWallRight(DisplacementX + RadiusY, DisplacementY + RadiusX, mLayer, false, true, out leftDist, ref mSecondaryAngle); + minDist = SetAngleFromFloor(leftDist, rightDist); + if (minDist == 0) + return; + + if (minDist < 0) { + if (minDist < -14) + return; + DisplacementX += minDist; + return; + } + + speed = (Math.Abs(VelocityY) >> 8) + 4; + if (speed > 14) + speed = 14; + + if (minDist <= speed) { + DisplacementX += minDist; + return; + } + + if (mStickToConvex != 0) { + DisplacementX += minDist; + return; + } + + mStatus |= CharacterState.Airborne; + mStatus &= ~CharacterState.Pushing; + mNextAnim = CharacterAnimation.Run; + } + + private void WalkCeiling() + { + int leftDist, rightDist, minDist, speed; + Level.FindCeiling(DisplacementX + RadiusX, DisplacementY - RadiusY, mLayer, false, true, out rightDist, ref mPrimaryAngle); + Level.FindCeiling(DisplacementX - RadiusX, DisplacementY - RadiusY, mLayer, false, true, out leftDist, ref mSecondaryAngle); + minDist = SetAngleFromFloor(leftDist, rightDist); + if (minDist == 0) + return; + + if (minDist < 0) { + if (minDist < -14) + return; + DisplacementY -= minDist; + return; + } + + speed = (Math.Abs(VelocityX) >> 8) + 4; + if (speed > 14) + speed = 14; + + if (minDist <= speed) { + DisplacementY -= minDist; + return; + } + + if (mStickToConvex != 0) { + DisplacementY -= minDist; + return; + } + + mStatus |= CharacterState.Airborne; + mStatus &= ~CharacterState.Pushing; + mNextAnim = CharacterAnimation.Run; + } + + private void WalkLeftWall() + { + int leftDist, rightDist, minDist, speed; + Level.FindWallLeft(DisplacementX - RadiusY, DisplacementY - RadiusX, mLayer, false, true, out rightDist, ref mPrimaryAngle); + Level.FindWallLeft(DisplacementX - RadiusY, DisplacementY + RadiusX, mLayer, false, true, out leftDist, ref mSecondaryAngle); + minDist = SetAngleFromFloor(leftDist, rightDist); + if (minDist == 0) + return; + + if (minDist < 0) { + if (minDist < -14) + return; + DisplacementX -= minDist; + return; + } + + speed = (Math.Abs(VelocityY) >> 8) + 4; + if (speed > 14) + speed = 14; + + if (minDist <= speed) { + DisplacementX -= minDist; + return; + } + + if (mStickToConvex != 0) { + DisplacementX -= minDist; + return; + } + + mStatus |= CharacterState.Airborne; + mStatus &= ~CharacterState.Pushing; + mNextAnim = CharacterAnimation.Run; + } + + #endregion + + #region Ground rolling + + /// + /// Subroutine allowing Sonic to start rolling when he's moving. + /// + private void CheckForRollStart() + { + if (mStatusSecondary < 0) + return; + + if (Math.Abs(mGroundVelocity) < 128) + return; + + if (mControlState.Left || mControlState.Right) + return; + + if (!mControlState.Down) + return; + + if ((mStatus & CharacterState.Spinning) != 0) + return; + + mStatus |= CharacterState.Spinning; + RadiusY = 14; + RadiusX = 7; + mAnim = CharacterAnimation.Roll; + DisplacementY += 5; + + Level.AddSound(ResourceManager.SpinSound, DisplacementX, DisplacementY); + + if (mGroundVelocity != 0) + return; + + mGroundVelocity = 512; + } + + /// + /// Originally 'MdRoll' which updates the movement logic when rolling. + /// + private void UpdateRollState() + { + if (!mSpindashing) + if (CheckForJump()) + return; + RollRepel(); + UpdateRollVelocity(); + KeepInLevelBoundaries(); + UpdatePosition(); + UpdateAngleAndPositionToFloor(); + ApplySlopeLocking(); + } + + /// + /// Originally 'RollSpeed' which updates the velocity. + /// + private void UpdateRollVelocity() + { + if (mStatusSecondary >= 0) { + // Apply player movement + if (mMoveLock == 0) { + if (mControlState.Left) + RollLeft(); + if (mControlState.Right) + RollRight(); + } + + // Apply ground velocity friction + if (mGroundVelocity < 0) { + mGroundVelocity += (mAcceleration >> 1); + if (mGroundVelocity > 0) + mGroundVelocity = 0; + } else if (mGroundVelocity > 0) { + mGroundVelocity -= (mAcceleration >> 1); + if (mGroundVelocity < 0) + mGroundVelocity = 0; + } + + // Check if rolling has stopped + if (mGroundVelocity == 0) { + // note: the spindash flag has a different meaning when Sonic's already rolling -- it's used to mean he's not allowed to stop rolling + if (!mSpindashing) { + // + mStatus &= ~CharacterState.Spinning; + RadiusY = 19; + RadiusX = 9; + mAnim = CharacterAnimation.Wait; + DisplacementY -= 5; + } else { + // Forced rolling + mGroundVelocity = 1024; + if ((mStatus & CharacterState.FacingLeft) != 0) + mGroundVelocity = -mGroundVelocity; + } + } + } + + RestoreCameraBias(); + + // SetRollSpeeds + VelocityY = (SonicMaths.Sin(mAngle) * mGroundVelocity) >> 8; + int newVX = (SonicMaths.Cos(mAngle) * mGroundVelocity) >> 8; + if (newVX > 4096) + newVX = 4096; + if (newVX < -4096) + newVX = -4096; + VelocityX = newVX; + + CheckPushing(); + } + + /// + /// Increases ground velocity when on a hill descent. + /// + private void RollRepel() + { + if (((mAngle + 96) & 0xFF) >= 192) + return; + + int d0 = (SonicMaths.Sin(mAngle) * 80) >> 8; + if (mGroundVelocity >= 0) { + if (d0 < 0) + d0 >>= 2; + mGroundVelocity += d0; + return; + } + + if (d0 >= 0) + d0 >>= 2; + + mGroundVelocity += d0; + } + + /// + /// Apply player left force. + /// + private void RollLeft() + { + if (mGroundVelocity > 0) { + // Brake + int d0 = mGroundVelocity - (mDeceleration >> 2); + if (d0 < 0) + d0 = -128; + mGroundVelocity = d0; + return; + } + + mStatus |= CharacterState.FacingLeft; + mAnim = CharacterAnimation.Roll; + } + + /// + /// Apply player right force. + /// + private void RollRight() + { + if (mGroundVelocity < 0) { + // Brake + int d0 = mGroundVelocity + (mDeceleration >> 2); + if (d0 > 0) + d0 = 128; + mGroundVelocity = d0; + return; + } + + mStatus &= ~CharacterState.FacingLeft; + mAnim = CharacterAnimation.Roll; + } + + #endregion + + #region Airborne + + /// + /// Updates logic for when Sonic is in a ball and airborne (he could be jumping but not necessarily). + /// + private void UpdateAirSpinState() + { + UpdateAirborneState(); + } + + /// + /// Originally 'Jump' which starts a jump if we can and we should. + /// + private bool CheckForJump() + { + int d0, d1, d2; + + if (!mNewDownControlState.A && !mNewDownControlState.B && !mNewDownControlState.C) + return false; + + d0 = mAngle + 128; + d1 = CalcRoomOverHead(d0); + if (d1 < 6) + return false; + + d2 = 1664; + if (mSuper) + d2 = 2048; + + if ((mStatus & CharacterState.Underwater) != 0) + d2 = 896; + + VelocityX += (SonicMaths.Cos(mAngle - 64) * d2) >> 8; + VelocityY += (SonicMaths.Sin(mAngle - 64) * d2) >> 8; + mStatus |= CharacterState.Airborne; + mStatus &= ~CharacterState.Pushing; + + mJumping = true; + mStickToConvex = 0; + + Level.AddSound(ResourceManager.JumpSound, DisplacementX, DisplacementY); + + RadiusY = 19; + RadiusX = 9; + if ((mStatus & CharacterState.Spinning) != 0) { + mStatus |= CharacterState.RollJumping; + return true; + } + + RadiusY = 14; + RadiusX = 7; + mAnim = CharacterAnimation.Roll; + mStatus |= CharacterState.Spinning; + DisplacementY += 5; + + return true; + } + + /// + /// Called if Sonic is airborne, but not in a ball (thus, probably not jumping). + /// + private void UpdateAirborneState() + { + UpdateJumpHeight(); + UpdateAirborneVelocity(); + KeepInLevelBoundaries(); + UpdatePositionWithGravity(); + + // If underwater + if ((mStatus & CharacterState.Underwater) != 0) + VelocityY -= 40; + + UpdateAirborneAngle(); + DoLevelCollision(); + } + + /// + /// Updates and controls velocity when airborne. + /// + private void UpdateAirborneVelocity() + { + // Prevent air control if we jumped from ball state + if ((mStatus & CharacterState.RollJumping) == 0) { + if (mControlState.Left) { + mStatus |= CharacterState.FacingLeft; + VelocityX -= mAcceleration * 2; + + // Enforce top speed + if (VelocityX < -mTopSpeed) + VelocityX = -mTopSpeed; + } else if (mControlState.Right) { + mStatus &= ~CharacterState.FacingLeft; + VelocityX += mAcceleration * 2; + + // Enforce top speed + if (VelocityX > mTopSpeed) + VelocityX = mTopSpeed; + } + } + + // Restore the camera bias + RestoreCameraBias(); + + // No drag if we are going up this quickly + if (VelocityY < -1024) + return; + + // Slow velocity X down by 1/32 + int velocityChange = VelocityX >> 5; + if (velocityChange == 0) + return; + VelocityX -= velocityChange; + + // Make sure we set velocity X to 0 if we've changed direction + if (velocityChange < 0 && VelocityX > 0) + VelocityX = 0; + else if (velocityChange >= 0 && VelocityX < 0) + VelocityX = 0; + } + + /// + /// Restores the character's angle when airborne. + /// + private void UpdateAirborneAngle() + { + sbyte angle = (sbyte)(mAngle & -1); + + if (angle < 0) + angle += 2; + else if (angle > 0) + angle -= 2; + + SetAngle(angle); + + UpdateAirborneFlip(); + } + + #endregion + + #region Jumping + + /// + /// Increases the height of the jump if A, B or C is down. + /// + private void UpdateJumpHeight() + { + if (!mJumping) { + if (mSpindashing) + return; + if (VelocityY < -4032) + VelocityY = -4032; + return; + } + + int d1 = -1024; + if ((mStatus & CharacterState.Underwater) != 0) + d1 = -512; + + if (d1 > VelocityY) + if (!mControlState.A && !mControlState.B && !mControlState.C) + VelocityY = d1; + } + + #endregion + + #region Flipping + + private int mFlipsRemaining; + private int mFlipSpeed; + private int mFlipAngle; + private int mFlipTurned; + + /// + /// Restores the character's flip angle when airborne. + /// + private void UpdateAirborneFlip() + { + if (mFlipAngle == 0) + return; + + if (mGroundVelocity < 0 && mFlipTurned != 0) + JumpFlipLeft(); + else + JumpFlipRight(); + } + + private void JumpFlipLeft() + { + int d0 = mFlipAngle - mFlipSpeed; + if (d0 >= 0) { + mFlipAngle = d0; + return; + } + + mFlipsRemaining--; + if (mFlipsRemaining >= 0) { + mFlipAngle = d0; + return; + } + + mFlipsRemaining = 0; + mFlipAngle = 0; + } + + private void JumpFlipRight() + { + int d0 = mFlipAngle + mFlipSpeed; + if (d0 >= 0) { + mFlipAngle = d0; + return; + } + + mFlipsRemaining--; + if (mFlipsRemaining >= 0) { + mFlipAngle = d0; + return; + } + + mFlipsRemaining = 0; + mFlipAngle = 0; + } + + public int FlipsRemaining + { + get + { + return mFlipsRemaining; + } + set + { + mFlipsRemaining = value; + } + } + + public int FlipSpeed + { + get + { + return mFlipSpeed; + } + set + { + mFlipSpeed = value; + } + } + + public int FlipAngle + { + get + { + return mFlipAngle; + } + set + { + mFlipAngle = value; + } + } + + #endregion + + #region Looking up / Ducking + + private bool mLookingUpOrDucking; + private int mLookDelayCounter; + + /// + /// Update ducking / looking up + /// + private void UpdateLooking() + { + mLookingUpOrDucking = false; + + if (mControlState.Up) { + // Lookup + mAnim = CharacterAnimation.LookUp; + mLookDelayCounter++; + if (mLookDelayCounter < 120) + return; + mLookDelayCounter = 120; + + if (mCamera.BiasY > -86) + mCamera.BiasY -= 2; + + mLookingUpOrDucking = true; + + } else if (mControlState.Down) { + // Duck + mAnim = CharacterAnimation.Duck; + mLookDelayCounter++; + if (mLookDelayCounter < 120) + return; + mLookDelayCounter = 120; + + if (mCamera.BiasY < 88) + mCamera.BiasY += 2; + + mLookingUpOrDucking = true; + } else { + mLookDelayCounter = 0; + } + } + + #endregion + + #endregion + + #region Hurting + + private void UpdateHurt() + { + if (mRoutineSecondary < 0) { + // Instant recovery + mRoutine = 2; + mRoutineSecondary = 0; + Animate(); + return; + } + + if (mInteract != null) + mInteract.Interact(this); + + UpdatePosition(); + if ((mStatus & CharacterState.Underwater) != 0) + VelocityY += 16; + else + VelocityY += 48; + + DoLevelCollision(); + + if ((mStatus & CharacterState.Airborne) == 0) { + VelocityY = 0; + VelocityX = 0; + mGroundVelocity = 0; + mObjControl = 0; + mAnim = 0; + mRoutine = 2; + GiveInvulnerability(); + mSpindashing = false; + } + + KeepInLevelBoundaries(); + Animate(); + if (mObjControl >= 0) + TouchResponse(); + + CameraScroll(); + } + + public void Hurt(LevelObject causeObject) + { + // Check if invincible + if ((mStatusSecondary & 2) != 0) + return; + + if (mInvulnerable) + return; + + // Check if character has a shield + if ((mStatusSecondary & 1) != 0) { + mStatusSecondary &= ~1; + Level.AddSound(ResourceManager.HurtSound, DisplacementX, DisplacementY); + } else if (mPlayer.Rings > 0) { + ScatterRings(); + } else { + Kill(causeObject); + return; + } + + mRoutine = 4; + ResetOnFloor(); + mStatus |= CharacterState.Airborne; + if ((mStatus & CharacterState.Underwater) != 0) { + VelocityY = -512; + VelocityX = -256; + } else { + VelocityY = -1024; + VelocityX = -512; + } + + // Reverse X velocity if other direction + if (DisplacementX >= causeObject.DisplacementX) + VelocityX = -VelocityX; + + mGroundVelocity = 0; + mAnim = CharacterAnimation.Hurt2; + GiveInvulnerability(); + if (causeObject is Spikes) + Level.AddSound(ResourceManager.SpikesSound, DisplacementX, DisplacementY); + } + + private void ScatterRings() + { + int angle = 72; + int speed = 4; + bool n = false; + + for (int t = 0; t < Math.Min(mPlayer.Rings, 32); t++) { + if (t == 16) { + speed = 2; + angle = 72; + } + + Ring ring = new Ring(Game, Level); + ring.Scattering = true; + ring.VelocityX = SonicMaths.Cos(angle) * speed; + ring.VelocityY = -SonicMaths.Sin(angle) * speed; + ring.DisplacementX = DisplacementX; + ring.DisplacementY = DisplacementY; + if (n) { + ring.VelocityX = -ring.VelocityX; + angle += 16; + } + + Level.Objects.Add(ring); + + n = !n; + } + + mPlayer.Rings = 0; + + Level.AddSound(ResourceManager.RingScatterSound, DisplacementX, DisplacementY); + } + + #endregion + + #region Dying + + private int mDeadDuration; + + private void UpdateDying() + { + mDeadDuration--; + if (mDeadDuration <= 0) { + mRoutine = 8; + mPlayer.Status = PlayerStatus.Dead; + return; + } + + UpdatePositionWithGravity(); + Animate(); + } + + public void Kill() + { + Kill(null); + } + + public void Kill(LevelObject causeObject) + { + mStatusSecondary = 0; + mRoutine = 6; + ResetOnFloor(); + mStatus |= CharacterState.Airborne; + VelocityY = -1792; + VelocityX = 0; + mGroundVelocity = 0; + mAnim = CharacterAnimation.Death; + DrawPriority = 1200; + mDeadDuration = 180; + + if (causeObject is Spikes) + Level.AddSound(ResourceManager.SpikesSound, DisplacementX, DisplacementY); + else + Level.AddSound(ResourceManager.HurtSound, DisplacementX, DisplacementY); + } + + #endregion + + #region Spindash + + private bool mSpindashing; + private int mSpindashCharge; + private int mSpindashAnimationFrame; + private int mSpindashAnimationFrameDuration; + private int mSpindashSoundDuration; + + private void DrawSpindashDust(Graphics g) + { + Rectangle dst, src = new Rectangle(32 * mSpindashAnimationFrame * Game.DisplayScale, 0, 32 * Game.DisplayScale, 24 * Game.DisplayScale); + + if ((mStatus & CharacterState.FacingLeft) != 0) { + dst = new Rectangle(0 * Game.DisplayScale, -4 * Game.DisplayScale, 32 * Game.DisplayScale, 24 * Game.DisplayScale); + g.DrawImage(ResourceManager.SpindashDustTexture, dst, src, Color.White, SpriteEffects.FlipHorizontally); + } else { + dst = new Rectangle(-32 * Game.DisplayScale, -4 * Game.DisplayScale, 32 * Game.DisplayScale, 24 * Game.DisplayScale); + g.DrawImage(ResourceManager.SpindashDustTexture, dst, src, Color.White); + } + } + + /// + /// Check for starting to charge a spindash. + /// + private bool CheckSpindash() + { + if (mSpindashing) + return UpdateSpindash(); + + // Check if ducking + if (mAnim != CharacterAnimation.Duck) + return false; + + if (!mNewDownControlState.A && !mNewDownControlState.B && !mNewDownControlState.C) + return false; + + mAnim = CharacterAnimation.Spindash; + + Level.AddSound(ResourceManager.SpindashChargeSound, DisplacementX, DisplacementY); + + mSpindashing = true; + mSpindashCharge = 0; + // if (mAirLeft >= 12) + // dust + KeepInLevelBoundaries(); + UpdateAngleAndPositionToFloor(); + + return true; + } + + /// + /// Subrouting to update an already-charging spindash. + /// + private bool UpdateSpindash() + { + // Decrease the spindash sound duration + if (mSpindashSoundDuration > 0) + mSpindashSoundDuration--; + + // Check if down is still pressed, otherwise release spindash + if (mControlState.Down) { + // Slowly discharge the spindash + if (mSpindashCharge != 0) { + mSpindashCharge -= mSpindashCharge >> 5; + if (mSpindashCharge < 0) + mSpindashCharge = 0; + } + + // Charge up the spindash if a, b or c is pressed + if (mNewDownControlState.A || mNewDownControlState.B || mNewDownControlState.C) + ChargeSpindash(); + + // Update spindash animation + mSpindashAnimationFrameDuration--; + if (mSpindashAnimationFrameDuration <= 0) { + mSpindashAnimationFrame = (mSpindashAnimationFrame + 1) % 7; + mSpindashAnimationFrameDuration = 2; + } + } else { + ReleaseSpindash(); + } + + RestoreCameraBias(); + + KeepInLevelBoundaries(); + UpdateAngleAndPositionToFloor(); + + return true; + } + + private void ChargeSpindash() + { + mAnim = CharacterAnimation.Spindash; + mAnimFrame = 0; + + // There must be at least a 10 turn interval before replaying spindash charge sound + if (mSpindashSoundDuration <= 0) { + Level.AddSound(ResourceManager.SpindashChargeSound, DisplacementX, DisplacementY); + mSpindashSoundDuration = 10; + } + + mSpindashCharge += 512; + if (mSpindashCharge >= 2048) + mSpindashCharge = 2048; + } + + private void ReleaseSpindash() + { + RadiusY = 14; + RadiusX = 7; + mAnim = CharacterAnimation.Roll; + DisplacementY += 5; + mSpindashing = false; + mGroundVelocity = SpindashSpeeds[mSpindashCharge >> 8]; + mCamera.ScrollDelayX = 8192 - (((mGroundVelocity - 2048) * 2) & 7936); + if ((mStatus & CharacterState.FacingLeft) != 0) + mGroundVelocity = -mGroundVelocity; + mStatus |= CharacterState.Spinning; + + Level.AddSound(ResourceManager.SpindashReleaseSound, DisplacementX, DisplacementY); + } + + #endregion + + #region Shield + + private int mShieldAnimationFrame; + + private void DrawShield(Graphics g) + { + Rectangle src, dst; + + if (mShieldAnimationFrame % 2 == 1) { + src = new Rectangle(0, 32 * Game.DisplayScale, 48 * Game.DisplayScale, 48 * Game.DisplayScale); + dst = new Rectangle(-24 * Game.DisplayScale, -24 * Game.DisplayScale, 48 * Game.DisplayScale, 48 * Game.DisplayScale); + } else { + src = new Rectangle(32 * (mShieldAnimationFrame / 2) * Game.DisplayScale, 0, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + dst = new Rectangle(-16 * Game.DisplayScale, -16 * Game.DisplayScale, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + } + + g.DrawImage(ResourceManager.ShieldTexture, dst, src, Color.White); + } + + private void UpdateShield() + { + mShieldAnimationFrame = (mShieldAnimationFrame + 1) % 10; + } + + public void GiveShield() + { + mStatusSecondary |= 1; + Level.AddSound(ResourceManager.ShieldSound, DisplacementX, DisplacementY); + } + + #endregion + + #region Speed Shoes + + private int mSpeedShoesDuration; + + private void UpdateSpeedShoes() + { + if (mSpeedShoesDuration > 0) { + mSpeedShoesDuration--; + if (mSpeedShoesDuration == 0) { + mTopSpeed = 1536; + mAcceleration = 12; + mDeceleration = 128; + if ((mStatusSecondary & 2) == 0) + mPlayer.MusicManager.PlayMusic(ResourceManager.EHZMusic); + } + } + } + + public void GiveSpeedShoes() + { + mStatusSecondary |= 4; + mSpeedShoesDuration = 60 * 20; + mTopSpeed = 0xC00; + mAcceleration = 24; + mDeceleration = 128; + if ((mStatusSecondary & 2) == 0) + mPlayer.MusicManager.PlayMusic(ResourceManager.EHZSpeedMusic); + } + + #endregion + + #region Invincibility + + private int mInvincibilityTime; + private List mInvincibilityStars = new List(); + + private void UpdateInvincibility() + { + // If invincibility time is 0, we are probably just in super mode + if (mInvincibilityTime == 0) + return; + + mInvincibilityTime--; + if (mInvincibilityTime == 0) { + mStatusSecondary &= ~2; + if ((mStatusSecondary & 4) == 0) + mPlayer.MusicManager.PlayMusic(ResourceManager.EHZMusic); + } else { + // Star creation + mInvincibilityStars.RemoveAll(o => o.Finished); + while (mInvincibilityStars.Count < 10) { + InvincibilityStar starObject = new InvincibilityStar(Game, Level); + starObject.DisplacementX = DisplacementX; + starObject.DisplacementY = DisplacementY; + Level.Objects.Add(starObject); + mInvincibilityStars.Add(starObject); + } + } + } + + public void GiveInvincibility() + { + if (mSuper) + return; + + mStatusSecondary |= 2; + mInvincibilityTime = 60 * 20; + + mPlayer.MusicManager.PlayMusic(ResourceManager.InvincibilityMusic); + } + + class InvincibilityStar : LevelObject + { + private Animation mAnimation; + private int mAngle; + private int mInitialDisplacementX; + private int mInitialDisplacementY; + private int mStatus; + private int mDuration; + + private static byte[][] AnimationData = new byte[][] { + new byte[] { 0, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 0xFF }, + }; + + public InvincibilityStar(SonicGame game, Level level) + : base(game, level) + { + mAnimation = new Animation(AnimationData); + mDuration = SonicMaths.Random(4, 18); + mAnimation.Frame = SonicMaths.Random(8); + DrawPriority = 100; + } + + public override void Draw(Graphics g) + { + if (mStatus == 0) + return; + + Rectangle src = new Rectangle(mAnimation.FrameValue * 31 * Game.DisplayScale, 0 * Game.DisplayScale, 31 * Game.DisplayScale, 31 * Game.DisplayScale); + Rectangle dst = new Rectangle(-15 * Game.DisplayScale, -15 * Game.DisplayScale, 31 * Game.DisplayScale, 31 * Game.DisplayScale); + g.DrawImage(ResourceManager.InvincibilityTexture, dst, src, Color.White); + } + + public override void Update() + { + if (mStatus == 0) + Init(); + + mAngle = (mAngle + 4) % 256; + DisplacementX = mInitialDisplacementX + (SonicMaths.Cos(mAngle) * 14 / 256); + DisplacementY = mInitialDisplacementY + (SonicMaths.Sin(mAngle) * 14 / 256); + + mAnimation.Update(); + + mDuration--; + if (mDuration <= 0) + Finished = true; + } + + private void Init() + { + mStatus = 1; + + mAngle = SonicMaths.Random(256); + mInitialDisplacementX = DisplacementX; + mInitialDisplacementY = DisplacementY; + } + + public override int Id + { + get { return 1; } + } + + public int Duration + { + get + { + return mDuration; + } + set + { + mDuration = value; + } + } + } + + #endregion + + #region Invulnerability + + private bool mInvulnerable; + private int mInvulnerableTime; + + private void UpdateInvulnerability() + { + mInvulnerableTime--; + if (mInvulnerableTime <= 0) + mInvulnerable = false; + } + + public void GiveInvulnerability() + { + mInvulnerable = true; + mInvulnerableTime = 120; + } + + public bool Invulnerable + { + get + { + return mInvulnerable; + } + } + + #endregion + + #region Collision checking + + /// + /// Prevents Sonic from leaving the boundaries of a level. + /// + private void KeepInLevelBoundaries() + { + int newDisplacementX = GetNextDisplacementX(); + if (newDisplacementX < Level.PlayerBoundary.X) { + // Sonic_Boundary_Sides + DisplacementX = Level.PlayerBoundary.X; + PartialDisplacementX = 0; + VelocityX = 0; + mGroundVelocity = 0; + } else if (newDisplacementX > Level.PlayerBoundary.X + Level.PlayerBoundary.Width) { + // Sonic_Boundary_Sides + DisplacementX = Level.PlayerBoundary.X + Level.PlayerBoundary.Width; + PartialDisplacementX = 0; + VelocityX = 0; + mGroundVelocity = 0; + } + + // Sonic_Boundary_CheckBottom + if (DisplacementY >= Level.PlayerBoundary.Y + Level.PlayerBoundary.Height) { + Kill(); + } + } + + /// + /// Performs level chunk collision checking when airborne. + /// + private void DoLevelCollision() + { + int angle, dist, vy, floorAngle; + + angle = SonicMaths.Atan2(VelocityX, VelocityY); + angle = (angle - 32) & 192; + if (angle == 64) { + HitLeftWall(); + return; + } + + if (angle == 128) { + HitCeilingAndWalls(); + return; + } + + if (angle == 192) { + HitRightWall(); + return; + } + + dist = CheckLeftWallDist(DisplacementX, DisplacementY, out floorAngle); + if (dist <= 0) { + dist -= DisplacementX; + VelocityX = 0; + } + + dist = CheckRightWallDist(DisplacementX, DisplacementY, out floorAngle); + if (dist <= 0) { + dist += DisplacementX; + VelocityX = 0; + } + + dist = CheckFloorDist(out angle, out floorAngle); + if (dist >= 0) + return; + vy = -((VelocityY >> 8) + 8); + if (dist < vy && angle < vy) + return; + + // Character has landed on the ground + DisplacementY += dist; + SetAngle(floorAngle); + ResetOnFloor(); + angle = (floorAngle + 32) & 64; + if (angle == 0) { + angle = (floorAngle + 16) & 32; + if (angle != 0) { + VelocityY >>= 1; + } else { + VelocityY = 0; + mGroundVelocity = VelocityX; + return; + } + } else { + VelocityX = 0; + if (VelocityY > 4032) + VelocityY = 4032; + } + + if (floorAngle > 0) + mGroundVelocity = VelocityY; + else + mGroundVelocity = -VelocityY; + } + + private void HitLeftWall() + { + int d3; + int dist = CheckLeftWallDist(DisplacementX, DisplacementY, out d3); + if (dist > 0) { + HitCeiling(); + return; + } + + DisplacementX -= dist; + VelocityX = 0; + mGroundVelocity = VelocityY; + } + + private void HitCeiling() + { + int d3; + int dist = CheckCeilingDist(out d3); + if (dist > 0) { + HitFloor(); + return; + } + + DisplacementY -= dist; + if (VelocityY > 0) + return; + VelocityY = 0; + } + + private void HitFloor() + { + int dist, angle, d0; + if (VelocityY < 0) + return; + dist = CheckFloorDist(out d0, out angle); + if (dist > 0) + return; + + DisplacementY += dist; + SetAngle(angle); + ResetOnFloor(); + VelocityY = 0; + mGroundVelocity = VelocityX; + } + + private void HitCeilingAndWalls() + { + int angle, d3; + int dist = CheckLeftWallDist(DisplacementX, DisplacementY, out d3); + if (dist < 0) { + DisplacementX -= dist; + VelocityX = 0; + } + + dist = CheckRightWallDist(DisplacementX, DisplacementY, out d3); + if (dist < 0) { + DisplacementX += dist; + VelocityX = 0; + } + + dist = CheckCeilingDist(out angle); + if (dist > 0) + return; + + dist -= DisplacementY; + if (((angle + 32) & 64) == 0) { + VelocityY = 0; + return; + } + + SetAngle(angle); + ResetOnFloor(); + if (angle > 0) + mGroundVelocity = VelocityY; + else + mGroundVelocity = -VelocityY; + } + + private void HitRightWall() + { + int d3; + int dist = CheckRightWallDist(DisplacementX, DisplacementY, out d3); + if (dist > 0) { + HitCeiling(); + return; + } + + DisplacementX += dist; + VelocityX = 0; + mGroundVelocity = VelocityY; + } + + private int CheckLeftWallDist(int x, int y, out int angle) + { + int dist; + Level.FindWallLeft(x - 10, y, mLayer, true, false, out dist, ref mPrimaryAngle); + if ((mPrimaryAngle & 1) != 0) + angle = -64; + else + angle = mPrimaryAngle; + return dist; + } + + private int CheckRightWallDist(int x, int y, out int angle) + { + int dist; + Level.FindWallRight(x + 10, y, mLayer, true, false, out dist, ref mPrimaryAngle); + if ((mPrimaryAngle & 1) != 0) + angle = -64; + else + angle = mPrimaryAngle; + return dist; + } + + private int CheckCeilingDist(out int angle) + { + int leftDist, rightDist, minDist; + Level.FindCeiling(DisplacementX + RadiusX, DisplacementY - RadiusY, mLayer, true, false, out rightDist, ref mPrimaryAngle); + Level.FindCeiling(DisplacementX + RadiusX, DisplacementY - RadiusY, mLayer, true, false, out leftDist, ref mSecondaryAngle); + + // loc_1ECC6: + if (leftDist > rightDist) { + angle = mPrimaryAngle; + minDist = rightDist; + // alternativeDistance = leftDist; + } else { + angle = mSecondaryAngle; + minDist = leftDist; + // alternativeDistance = rightDist; + } + + if ((angle & 1) != 0) + angle = 0; + + return minDist; + } + + private int CheckFloorDist(out int alternativeDistance, out int angle) + { + int leftDist, rightDist, minDist; + Level.FindFloor(DisplacementX + RadiusX, DisplacementY + RadiusY, mLayer, false, true, out rightDist, ref mPrimaryAngle); + Level.FindFloor(DisplacementX - RadiusX, DisplacementY + RadiusY, mLayer, false, true, out leftDist, ref mSecondaryAngle); + + // loc_1ECC6: + if (leftDist > rightDist) { + angle = mPrimaryAngle; + minDist = rightDist; + alternativeDistance = leftDist; + } else { + angle = mSecondaryAngle; + minDist = leftDist; + alternativeDistance = rightDist; + } + + if ((angle & 1) != 0) + angle = 0; + + return minDist; + } + + private int ChkFloorEdge(int d3, int d0) + { + int d1; + int d2 = RadiusY + DisplacementY; + mPrimaryAngle = 0; + Level.FindFloor(d3, d2, mLayer, false, true, out d1, ref mPrimaryAngle); + d3 = mPrimaryAngle; + if ((d3 & 1) != 0) + d3 = 0; + + return d1; + } + + /// + /// Subroutine to calculate how much space is in front of Sonic or Tails on the ground. + /// + private int CalcRoomInFront(int angle) + { + int d1, d2, d3; + + d3 = GetNextDisplacementX(); + d2 = GetNextDisplacementY(); + + // d2 = (d2 << 16) | (d2 >> 16); + // d3 = (d3 << 16) | (d2 >> 16); + mPrimaryAngle = angle; + mSecondaryAngle = angle; + d1 = angle; + angle += 32; + if (angle < 0) { + angle = d1; + if (angle < 0) + angle = 1; + angle = 32; + } else { + angle = d1; + if (angle < 0) + angle = 1; + angle += 31; + } + + angle &= 192; + if (angle == 0) { + d2 += 10; + Level.FindFloor(d3, d2, mLayer, true, false, out d1, ref mPrimaryAngle); + d2 = 0; + d3 = mPrimaryAngle; + if ((d3 & 1) != 0) + d3 = d2; + } + + if (angle == 128) { + d1 = CheckSlopeDist(d3, d2); + return d1; + } + + d1 &= 56; + if (d1 == 0) + d2 += 8; + + if (angle == 64) + d1 = CheckLeftWallDist(d3, d2, out angle); + else + d1 = CheckRightWallDist(d3, d2, out angle); + return d1; + } + + private int CheckSlopeDist(int x, int y) + { + int d; + Level.FindFloor(x, y, mLayer, true, false, out d, ref mPrimaryAngle); + return d; + } + + private int SetAngleFromFloor(int leftDistance, int rightDistance) + { + int minDistance = leftDistance; + int d2 = mSecondaryAngle; + if (leftDistance > rightDistance) { + d2 = mPrimaryAngle; + minDistance = rightDistance; + } + + if ((d2 & 1) != 0) { + if (mAngle >= -96 && mAngle < -32) + SetAngle(-64); + else if (mAngle >= -32 && mAngle < 32) + SetAngle(0); + else if (mAngle >= 32 && mAngle < 96) + SetAngle(64); + else + SetAngle(-128); + return minDistance; + } + + int d0 = d2 - mAngle; + d0 = ((d0 & 128) != 0 ? d0 | -256 : d0 & 256); + if (d0 < 0) + d0 = -d0; + if (d0 >= 32) { + if (mAngle >= -96 && mAngle < -32) + SetAngle(-64); + else if (mAngle >= -32 && mAngle < 32) + SetAngle(0); + else if (mAngle >= 32 && mAngle < 96) + SetAngle(64); + else + SetAngle(-128); + return minDistance; + } + + SetAngle(d2); + return minDistance; + } + + private void SetAngle(int angle) + { + Debug.Assert(angle >= -128 && angle < 127, "Angle is not valid for signed-byte"); + mAngle = angle; + } + + private void TouchResponse() + { + foreach (LevelObject obj in Level.Objects.GetObjectsInArea(new Rectangle(DisplacementX - RadiusX - 1, DisplacementY - RadiusY, (RadiusX + 1) * 2, RadiusY * 2))) + if (obj != this) + obj.Touch(this); + } + + private int CheckLeftCeilingDist() + { + int leftDistance, rightDistance; + + Level.FindWallLeft(DisplacementX - RadiusY, DisplacementY - RadiusX, mLayer, true, false, out leftDistance, ref mPrimaryAngle); + Level.FindWallRight(DisplacementX - RadiusY, DisplacementY + RadiusX, mLayer, true, false, out rightDistance, ref mSecondaryAngle); + + return 0; + } + + private int CalcRoomOverHead(int d0) + { + CheckCeilingDist(out d0); + + return 8; + } + + #endregion + + #region Animation + + private CharacterAnimation mAnim; + private CharacterAnimation mNextAnim; + private int mAnimFrame; + private int mAnimFrameDuration; + private int mMappingFrame; + + private void Animate() + { + if (mAnim != mNextAnim) { + mNextAnim = mAnim; + mAnimFrame = 0; + mAnimFrameDuration = 0; + mStatus &= ~CharacterState.Pushing; + } + + if ((int)mAnim >= AnimationData.Length) + return; + + byte[] animationScript = AnimationData[(int)mAnim]; + if (animationScript[0] > 127) { + AnimateWalkRun(); + return; + } + + mAnimFrameDuration--; + if (mAnimFrameDuration >= 0) + return; + + mAnimFrameDuration = animationScript[0]; + AnimateGeneric(animationScript); + } + + private void AnimateGeneric(byte[] animationScript) + { + byte asf = animationScript[mAnimFrame + 1]; + switch (asf) { + case 0xFF: + mMappingFrame = animationScript[1]; + mAnimFrame = 1; + break; + case 0xFE: + mAnimFrame -= animationScript[mAnimFrame + 2]; + mMappingFrame = animationScript[mAnimFrame + 1]; + mAnimFrame++; + break; + case 0xFD: + mAnim = (CharacterAnimation)animationScript[mAnimFrame + 2]; + break; + default: + mMappingFrame = asf; + mAnimFrame++; + break; + } + } + + private void AnimateWalkRun() + { + byte[] animationScript = AnimationData[(int)mAnim]; + + if (animationScript[0] != 0xFF) { + AnimateRoll(); + return; + } + + if (mFlipAngle != 0) { + AnimateTumble(); + return; + } + + // stuff + // stuff + + if ((mStatus & CharacterState.Pushing) != 0) { + AnimatePush(); + return; + } + + int d2 = Math.Abs(mGroundVelocity); + if (mStatusSecondary < 0) + d2 *= 2; + + if (d2 >= 1536) + animationScript = AnimationData[1]; + else + animationScript = AnimationData[0]; + + byte asf = animationScript[mAnimFrame + 1]; + if (asf == 0xFF) { + mAnimFrame = 0; + asf = animationScript[1]; + } + + mMappingFrame = asf; + mAnimFrameDuration--; + if (mAnimFrameDuration >= 0) + return; + + d2 = -d2; + d2 += 2048; + if (d2 < 0) + d2 = 0; + d2 >>= 8; + mAnimFrameDuration = d2; + mAnimFrame++; + } + + private void AnimatePush() + { + mAnimFrameDuration--; + if (mAnimFrameDuration >= 0) + return; + + int d2 = mGroundVelocity; + if (d2 >= 0) + d2 = -d2; + d2 += 2048; + if (d2 < 0) + d2 = 0; + d2 >>= 6; + mAnimFrameDuration = d2; + AnimateGeneric(AnimationData[4]); + } + + private void AnimateTumble() + { + if ((mStatus & CharacterState.FacingLeft) != 0) { + int d0 = mFlipAngle; + if (mFlipTurned != 0) + d0 += 11; + else + d0 = -d0 + 143; + + d0 = ((d0 & 0xFF) / 22) + 95; + mMappingFrame = d0; + mAnimFrameDuration = 0; + } else { + mMappingFrame = (((mFlipAngle + 11) & 0xFF) / 22) + 95; + mAnimFrameDuration = 0; + } + } + + private void AnimateRoll() + { + byte[] animationScript = AnimationData[(int)mAnim]; + + mAnimFrameDuration--; + if (mAnimFrameDuration >= 0) + return; + + if (animationScript[0] != 0xFE) { + // AnimatePush(); + return; + } + + int d2 = Math.Abs(mGroundVelocity); + if (mGroundVelocity >= 1536) + animationScript = AnimationData[3]; + else + animationScript = AnimationData[2]; + + d2 = -d2; + d2 += 1024; + if (d2 < 0) + d2 = 0; + d2 >>= 8; + + mAnimFrameDuration = d2; + AnimateGeneric(animationScript); + } + + #endregion + + #region Camera + + private void RestoreCameraBias() + { + if (mCamera.BiasY < 0) + mCamera.BiasY += 2; + else if (mCamera.BiasY > 0) + mCamera.BiasY -= 2; + } + + private void CameraScroll() + { + HorizontalScrolling(); + VerticalScrolling(); + } + + private void HorizontalScrolling() + { + // Delay scrolling if there is a delay set + if (mCamera.ScrollDelayX > 0) { + mCamera.ScrollDelayX -= 512; + return; + } else { + mCamera.ScrollDelayX = 0; + } + + // Is character past camera x or behind camera x - 16 + if (DisplacementX > mCamera.X) { + // Scroll right + int difference = DisplacementX - mCamera.X; + if (difference > 16) + difference = 16; + mCamera.X += difference; + } else if (DisplacementX < mCamera.X - 16) { + // Scroll left + int difference = mCamera.X - 16 - DisplacementX; + if (difference > 16) + difference = 16; + mCamera.X -= difference; + } + } + + private void VerticalScrolling() + { + if ((mStatus & CharacterState.Airborne) == 0) { + // On ground + int destY = DisplacementY + mCamera.BiasY; + int maxChange = (Math.Abs(VelocityY) > 6 ? 16 : 6); + if (Math.Abs(destY - mCamera.Y) < maxChange) + mCamera.Y = destY; + else if (destY > mCamera.Y) + mCamera.Y += maxChange; + else + mCamera.Y -= maxChange; + } else { + // In air + int topBorder = mCamera.Y - 48; + int bottomBorder = mCamera.Y + 16; + if (DisplacementY < topBorder) { + mCamera.Y -= Math.Min(topBorder - DisplacementY, 16); + } else if (DisplacementY > bottomBorder) { + mCamera.Y += Math.Min(DisplacementY - bottomBorder, 16); + } + } + } + + public Camera Camera + { + get + { + return mCamera; + } + set + { + mCamera = value; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/s2prototype/Objects/Coconuts.cs b/s2prototype/Objects/Coconuts.cs new file mode 100644 index 0000000..4d7e5d2 --- /dev/null +++ b/s2prototype/Objects/Coconuts.cs @@ -0,0 +1,238 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Coconuts : Badnik + { + enum CoconutsState + { + Init, + Resting, + Climbing, + Throwing, + } + + private Animation mAnimation; + private CoconutsState mStatus; + private int mThrowingStatus; + private bool mFacingRight; + private int mStateDuration; + private int mClimbIndex; + private int mThrowWaitDuration; + + private static int[] ClimbData = new int[] { + -256, 0x20, + 256, 0x18, + -256, 0x10, + 256, 0x28, + -256, 0x20, + 256, 0x10, + }; + + private static byte[][] AnimationData = new byte[][] { + new byte[] { 5, 0, 1, 0xFF }, + new byte[] { 9, 1, 2, 1, 0xFF }, + }; + + public Coconuts(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mAnimation = new Animation(AnimationData); + + RadiusX = 6; + RadiusY = 8; + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(mAnimation.FrameValue * 32 * Game.DisplayScale, 0 * Game.DisplayScale, 32 * Game.DisplayScale, 48 * Game.DisplayScale); + Rectangle dst = new Rectangle(-16 * Game.DisplayScale, -24 * Game.DisplayScale, 32 * Game.DisplayScale, 48 * Game.DisplayScale); + + SpriteEffects effects = SpriteEffects.None; + if (mFacingRight) + effects = SpriteEffects.FlipHorizontally; + + g.DrawImage(ResourceManager.CoconutsTexture, dst, src, Color.White, effects); + } + + public override void Update() + { + if (!IsCloseToCharacter) + return; + + switch (mStatus) { + case CoconutsState.Init: + Init(); + break; + case CoconutsState.Resting: + Rest(); + break; + case CoconutsState.Climbing: + Climbing(); + break; + case CoconutsState.Throwing: + Throwing(); + break; + } + } + + private void Init() + { + // Start rest + mStateDuration = 16; + mStatus = CoconutsState.Resting; + } + + private void Rest() + { + // a1 = address of closest player character + // d0 = 0 if right from player, 2 if left + // d1 = 0 if above player, 2 if under + // d2 = horizontal distance to closest character + // d3 = vertical distance to closest character + + int directionX = 0; + int directionY = 0; + int horizDist = 12; + int vertDist = 80; + Level.GetClosestCharacter(this, out directionX, out directionY, out horizDist, out vertDist); + + // Set direction coconuts is facing + mFacingRight = (directionX != 0); + + // Is player in range + if (horizDist + 96 < 192 && Math.Abs(vertDist) < 100) { + if (mThrowWaitDuration == 0) { + mStatus = CoconutsState.Throwing; + mAnimation.FrameValue = 1; + mStateDuration = 8; + mThrowWaitDuration = 32; + return; + } + mThrowWaitDuration--; + } + + mStateDuration--; + + // If rest has finished, start climb + if (mStateDuration < 0) { + mStatus = CoconutsState.Climbing; + SetClimbVelocity(); + } + } + + private void Climbing() + { + mStateDuration--; + + // Has climb finished + if (mStateDuration == 0) { + mStatus = CoconutsState.Resting; + mStateDuration = 16; + return; + } + + // Update climb position + UpdatePosition(); + mAnimation.Update(); + } + + private void Throwing() + { + if (mThrowingStatus == 0) { + mStateDuration--; + if (mStateDuration < 0) { + mThrowingStatus += 2; + mStateDuration = 8; + mAnimation.FrameValue = 2; + FireCoconut(); + } + } else if (mThrowingStatus == 2) { + mStateDuration--; + if (mStateDuration < 0) { + mThrowingStatus = 0; + mStatus = CoconutsState.Climbing; + mStateDuration = 8; + SetClimbVelocity(); + } + } + } + + private void SetClimbVelocity() + { + if (mClimbIndex >= 12) + mClimbIndex = 0; + VelocityY = ClimbData[mClimbIndex]; + mStateDuration = ClimbData[mClimbIndex + 1]; + mClimbIndex += 2; + } + + private void FireCoconut() + { + CoconutProjectile coconut = new CoconutProjectile(Game, Level); + coconut.DisplacementX = DisplacementX; + coconut.DisplacementY = DisplacementY - 13; + + if (mFacingRight) { + coconut.DisplacementX -= 11; + coconut.VelocityX = 256; + } else { + coconut.DisplacementX += 11; + coconut.VelocityX = -256; + } + + coconut.VelocityY = -256; + Level.Objects.Add(coconut); + } + + public override int Id + { + get { return 157; } + } + + class CoconutProjectile : LevelObject + { + public CoconutProjectile(SonicGame game, Level level) + : base(game, level) + { + RadiusX = 4; + RadiusY = 4; + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(96 * Game.DisplayScale, 0 * Game.DisplayScale, 12 * Game.DisplayScale, 13 * Game.DisplayScale); + Rectangle dst = new Rectangle(-6 * Game.DisplayScale, -6 * Game.DisplayScale, 12 * Game.DisplayScale, 13 * Game.DisplayScale); + + g.DrawImage(ResourceManager.CoconutsTexture, dst, src, Color.White); + } + + public override void Update() + { + if (!IsCloseToCharacter) { + Finished = true; + return; + } + + VelocityY += 32; + UpdatePosition(); + } + + public override void Touch(LevelObject obj) + { + Character character = obj as Character; + if (character == null) + return; + + character.Hurt(this); + } + + public override int Id + { + get { return 152; } + } + } + } +} diff --git a/s2prototype/Objects/CollisionPlaneSwitcher.cs b/s2prototype/Objects/CollisionPlaneSwitcher.cs new file mode 100644 index 0000000..2c0db76 --- /dev/null +++ b/s2prototype/Objects/CollisionPlaneSwitcher.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + class CollisionPlaneSwitcher : LevelObject + { + private int mRoutine; + private int mHeight; + private int mSubType; + private bool mFlipX; + private Dictionary mCurrentSides = new Dictionary(); + + private static int[] Heights = new int[] { 32, 64, 128, 256 }; + + public CollisionPlaneSwitcher(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mSubType = definition.SubType; + mFlipX = definition.FlipX; + } + + public override void Update() + { + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + MainX(); + break; + case 4: + MainY(); + break; + } + } + + private void Init() + { + if ((mSubType & 4) == 0) + InitCheckX(); + else + InitCheckY(); + } + + private void InitCheckX() + { + mRoutine = 2; + mHeight = Heights[mSubType & 3]; + MainX(); + } + + private void InitCheckY() + { + mRoutine = 4; + mHeight = Heights[mSubType & 3]; + MainY(); + } + + private void MainX() + { + CharacterCheck(); + + foreach (KeyValuePair kvp in mCurrentSides.ToArray()) { + if (kvp.Value == 0) { + // Check if character is still left of the layer switcher + if (DisplacementX > kvp.Key.DisplacementX) + return; + + // Character is now right of the layer switcher + mCurrentSides[kvp.Key] = 1; + + // Check if character is between the top and bottom of the layer switcher + if (kvp.Key.DisplacementY < DisplacementY - mHeight) + return; + if (kvp.Key.DisplacementY >= DisplacementY + mHeight) + return; + + // Make sure character isn't airborne + if ((mSubType & 128) != 0) + if ((kvp.Key.Status & CharacterState.Airborne) != 0) + return; + + if (!mFlipX) { + if ((mSubType & 8) != 0) { + kvp.Key.Layer = 0; + } else { + kvp.Key.Layer = 1; + } + } + } else { + // Check if character is still right of the layer switcher + if (DisplacementX < kvp.Key.DisplacementX) + return; + + // Character is now left of the layer switcher + mCurrentSides[kvp.Key] = 0; + + int top = DisplacementY - mHeight; + int bottom = DisplacementY + mHeight; + if (kvp.Key.DisplacementY < top) + return; + if (kvp.Key.DisplacementY >= bottom) + return; + + // Make sure character isn't airborne + if ((mSubType & 128) != 0) + if ((kvp.Key.Status & CharacterState.Airborne) != 0) + return; + + if (!mFlipX) { + if ((mSubType & 16) != 0) { + kvp.Key.Layer = 0; + } else { + kvp.Key.Layer = 1; + } + } + } + } + } + + private void MainY() + { + + } + + private void CharacterCheck() + { + foreach (LevelObject obj in Level.Objects) { + Character character = obj as Character; + if (character == null) + continue; + + if (!mCurrentSides.ContainsKey(character)) { + if (DisplacementX < character.DisplacementX) + mCurrentSides.Add(character, 1); + else + mCurrentSides.Add(character, 0); + } + } + + } + + public override int Id + { + get { return 3; } + } + } +} diff --git a/s2prototype/Objects/EHZPlatform.cs b/s2prototype/Objects/EHZPlatform.cs new file mode 100644 index 0000000..a4a91a1 --- /dev/null +++ b/s2prototype/Objects/EHZPlatform.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class EHZPlatform : Platform + { + private int mStatus; + private int mInitialDisplacementX; + private int mInitialDisplacementY; + private int mSubType; + + public EHZPlatform(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + OscillateNumInit(); + + RadiusX = 32; + RadiusY = 8; + + mSubType = definition.SubType; + } + + public override void Draw(Graphics g) + { + g.DrawImage(ResourceManager.EHZPlatformTexture, new Vector2(-32 * Game.DisplayScale, -12 * Game.DisplayScale), Color.White); + // sb.Draw(ResourceManager.MarkerTexture, new Vector2(DisplacementX - 7 - Game.Camera.X, DisplacementY - 7 - Game.Camera.Y), Color.White); + } + + public override void Update() + { + if (mStatus == 0) { + mInitialDisplacementX = DisplacementX; + mInitialDisplacementY = DisplacementY; + mStatus = 1; + return; + } + + OscillateNumDo(); + + if (mSubType == 1) { + int targetX = mInitialDisplacementX + ((mOscillationData[18] >> 8) - 64); + int targetY; + VelocityX = (targetX - DisplacementX) * 256; + + if (InteractingCharacters.Count > 0) + targetY = mInitialDisplacementY + 4; + else + targetY = mInitialDisplacementY; + + if (targetY < DisplacementY) + VelocityY = -84; + else if (targetY > DisplacementY) + VelocityY = 84; + else + VelocityY = 0; + } else if (mSubType == 2) { + int targetY = mInitialDisplacementY + ((mOscillationData[18] >> 8) - 64); + VelocityY = (targetY - DisplacementY) * 256; + } + + UpdatePosition(); + } + + public override int Id + { + get { return 24; } + } + + public override bool AllowBalancing + { + get + { + return true; + } + } + + private void OscillateNumInit() + { + Array.Copy(OscData, mOscillationData, OscData.Length); + } + + private void OscillateNumDo() + { + ushort newValue; + int numElements; + + numElements = OscData2.Length / 2; + + for (int i = 0; i < numElements; i++) { + int j = i * 2; + int k = i * 3; + + if (mOscillationData[k + 2] == 0) { + newValue = (ushort)(mOscillationData[k + 1] + OscData2[j]); + mOscillationData[k] += newValue; + mOscillationData[k + 1] = newValue; + if ((OscData2[j + 1] & 0xFF) <= (mOscillationData[k] >> 8)) + mOscillationData[k + 2] = 1; + } else { + newValue = (ushort)(mOscillationData[k + 1] - OscData2[j]); + mOscillationData[k] += newValue; + mOscillationData[k + 1] = newValue; + if ((OscData2[j + 1] & 0xFF) > (mOscillationData[k] >> 8)) + mOscillationData[k + 2] = 0; + } + } + } + + private ushort[] mOscillationData = new ushort[16 * 3]; + private static ushort[] OscData = new ushort[] { + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x80, 0, 0, + 0x3848, 0xEE, 1, + 0x2080, 0xB4, 1, + 0x3080, 0x10E, 1, + 0x5080, 0x1C2, 1, + 0x7080, 0x276, 1, + 0x80, 0, 0, + 0x4000, 0xFE, 1, + }; + + private static ushort[] OscData2 = new ushort[] { + 2, 0x10, + 2, 0x18, + 2, 0x20, + 2, 0x30, + 4, 0x20, + 8, 8, + 8, 0x40, + 4, 0x40, + 2, 0x38, + 2, 0x28, + 2, 0x20, + 3, 0x30, + 5, 0x50, + 7, 0x70, + 2, 0x40, + 2, 0x40, + }; + } +} diff --git a/s2prototype/Objects/EHZSpiralPathway.cs b/s2prototype/Objects/EHZSpiralPathway.cs new file mode 100644 index 0000000..233355e --- /dev/null +++ b/s2prototype/Objects/EHZSpiralPathway.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + class EHZSpiralPathway : LevelObject + { + #region Constant data + + private static int[] FlipAngleTable = new int[] { + 0x00, 0x00, 0x01, 0x01, + 0x16, 0x16, 0x16, 0x16, + 0x2C, 0x2C, 0x2C, 0x2C, + 0x42, 0x42, 0x42, 0x42, + 0x58, 0x58, 0x58, 0x58, + 0x6E, 0x6E, 0x6E, 0x6E, + 0x84, 0x84, 0x84, 0x84, + 0x9A, 0x9A, 0x9A, 0x9A, + 0xB0, 0xB0, 0xB0, 0xB0, + 0xC6, 0xC6, 0xC6, 0xC6, + 0xDC, 0xDC, 0xDC, 0xDC, + 0xF2, 0xF2, 0xF2, 0xF2, + 0x01, 0x01, 0x00, 0x00, + }; + + private static int[] CosineTable = new int[] { + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 27, + 27, 27, 27, 26, 26, 26, 25, 25, 25, 24, 24, 24, 23, 23, 22, 22, + 21, 21, 20, 20, 19, 18, 18, 17, 16, 16, 15, 14, 14, 13, 12, 12, + 11, 10, 10, 9, 8, 8, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, + 0, -1, -2, -2, -3, -4, -4, -5, -6, -7, -7, -8, -9, -9,-10,-10, + -11,-11,-12,-12,-13,-14,-14,-15,-15,-16,-16,-17,-17,-18,-18,-19, + -19,-19,-20,-21,-21,-22,-22,-23,-23,-24,-24,-25,-25,-26,-26,-27, + -27,-28,-28,-28,-29,-29,-30,-30,-30,-31,-31,-31,-32,-32,-32,-33, + -33,-33,-33,-34,-34,-34,-35,-35,-35,-35,-35,-35,-35,-35,-36,-36, + -36,-36,-36,-36,-36,-36,-36,-37,-37,-37,-37,-37,-37,-37,-37,-37, + -37,-37,-37,-37,-37,-37,-37,-37,-37,-37,-37,-37,-37,-37,-37,-37, + -37,-37,-37,-37,-36,-36,-36,-36,-36,-36,-36,-35,-35,-35,-35,-35, + -35,-35,-35,-34,-34,-34,-33,-33,-33,-33,-32,-32,-32,-31,-31,-31, + -30,-30,-30,-29,-29,-28,-28,-28,-27,-27,-26,-26,-25,-25,-24,-24, + -23,-23,-22,-22,-21,-21,-20,-19,-19,-18,-18,-17,-16,-16,-15,-14, + -14,-13,-12,-11,-11,-10, -9, -8, -7, -7, -6, -5, -4, -3, -2, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 10, 11, 12, 13, + 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, + 21, 22, 22, 23, 23, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, + 29, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + }; + + #endregion + + public EHZSpiralPathway(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + } + + public override void Update() + { + foreach (LevelObject obj in Level.Objects) { + Character character = obj as Character; + if (character == null) + continue; + + UpdateCharacter(character); + } + } + + private void UpdateCharacter(Character character) + { + if (character.InteractionObject == this) { + CharacterContinueProgress(character); + return; + } + + // Ignore if character is airborne + if ((character.Status & CharacterState.Airborne) != 0) + return; + + // Check if character is on object + int x = character.DisplacementX - DisplacementX; + if (character.VelocityX < 0) { + if (x < 192) + return; + if (x > 208) + return; + } else { + if (x > -192) + return; + if (x < -208) + return; + } + + int y = character.DisplacementY - DisplacementY - 10; + if (y >= 48) + return; + + CharacterStart(character); + } + + private void CharacterStart(Character character) + { + character.InteractionObject = this; + character.Angle = 0; + character.VelocityY = 0; + character.GroundVelocity = character.VelocityX; + if ((character.Status & CharacterState.Airborne) != 0) { + character.ResetOnFloor(); + } + + character.Status |= CharacterState.OnObject; + character.Status &= ~CharacterState.Airborne; + } + + private void CharacterContinueProgress(Character character) + { + int d0; + + if (Math.Abs(character.GroundVelocity) < 1536) { + CharacterFallOff(character); + return; + } + + if ((character.Status & CharacterState.Airborne) != 0) { + CharacterFallOff(character); + return; + } + + d0 = character.DisplacementX - DisplacementX + 208; + if (d0 < 0) { + CharacterFallOff(character); + return; + } + + if (d0 >= 416) { + CharacterFallOff(character); + return; + } + + CharacterMove(character); + } + + private void CharacterFallOff(Character character) + { + character.InteractionObject = null; + character.Status &= ~CharacterState.OnObject; + character.FlipsRemaining = 0; + character.FlipSpeed = 4; + } + + private void CharacterMove(Character character) + { + if ((character.Status & CharacterState.OnObject) == 0) + return; + + int d0 = character.DisplacementX - DisplacementX + 208; + character.DisplacementY = DisplacementY + CosineTable[d0] - character.RadiusY + 19; + character.FlipAngle = FlipAngleTable[(d0 >> 3) & 63]; + } + + public override int Id + { + get { return 6; } + } + } +} diff --git a/s2prototype/Objects/Explosion.cs b/s2prototype/Objects/Explosion.cs new file mode 100644 index 0000000..76f6a0c --- /dev/null +++ b/s2prototype/Objects/Explosion.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Explosion : LevelObject + { + private int mStatus; + private int mAnimFrameDuration; + private int mMappingFrame; + + public Explosion(SonicGame game, Level level) + : base(game, level) + { + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(mMappingFrame * 32 * Game.DisplayScale, 0 * Game.DisplayScale, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + Rectangle dst = new Rectangle(-16 * Game.DisplayScale, -16 * Game.DisplayScale, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + + g.DrawImage(ResourceManager.ExplosionTexture, dst, src, Color.White); + } + + private void Init() + { + DrawPriority = 80; + mAnimFrameDuration = 3; + mStatus = 1; + + // Play explosion sound + Level.AddSound(ResourceManager.BadnikExplosionSound, DisplacementX, DisplacementY); + } + + public override void Update() + { + if (mStatus == 0) + Init(); + + mAnimFrameDuration--; + if (mAnimFrameDuration < 0) { + mAnimFrameDuration = 7; + mMappingFrame++; + if (mMappingFrame == 5) + Finished = true; + } + } + + public override int Id + { + get { return 39; } + } + } +} diff --git a/s2prototype/Objects/LogBridge.cs b/s2prototype/Objects/LogBridge.cs new file mode 100644 index 0000000..30af63c --- /dev/null +++ b/s2prototype/Objects/LogBridge.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class LogBridge : Platform + { + private int mStatus; + private int[] mLogOffsets; + private int[] mLogDestOffsets; + private int[] mLogTotalOffsets; + + private int mSubType; + + public LogBridge(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mSubType = definition.SubType; + } + + public override void Draw(Graphics g) + { + if (mStatus == 0) + return; + + int bridgeWidth = mSubType * 16; + DrawPillar(g, -(bridgeWidth / 2) - 16, -12); + DrawPillar(g, (bridgeWidth / 2), -12); + + for (int i = 0; i < mSubType; i++) + DrawLog(g, i); + } + + private void DrawPillar(Graphics g, int x, int y) + { + Rectangle dst = new Rectangle((x - 8) * Game.DisplayScale, (y - 8) * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + Rectangle src = new Rectangle(0, 0, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + g.DrawImage(ResourceManager.LogBridgeTexture, dst, src, Color.White); + } + + private void DrawLog(Graphics g, int index) + { + int x = - RadiusX + (index * 16); + DrawLog(g, x, mLogOffsets[index]); + } + + private void DrawLog(Graphics g, int x, int y) + { + Rectangle dst = new Rectangle((x - 8) * Game.DisplayScale, (y - 8) * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + Rectangle src = new Rectangle(16 * Game.DisplayScale, 0 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + g.DrawImage(ResourceManager.LogBridgeTexture, dst, src, Color.White); + } + + private void Init() + { + RadiusX = (mSubType * 16) / 2; + RadiusY = 8; + + mLogOffsets = new int[mSubType]; + mLogDestOffsets = new int[mSubType]; + mLogTotalOffsets = new int[mSubType]; + + int offset = 2; + for (int i = 0; i < mSubType / 2; i++) { + mLogTotalOffsets[i] = offset; + mLogTotalOffsets[mSubType - i - 1] = offset; + offset += 2; + } + + if (mSubType % 2 == 1) + mLogTotalOffsets[mSubType / 2] = 2 * (mSubType / 2) + 2; + + mStatus = 1; + } + + public override void Update() + { + if (mStatus == 0) + Init(); + + // Reset log offsets + for (int i = 0; i < mSubType; i++) + mLogDestOffsets[i] = 0; + + // Get character closest to centre of bridge and X of that + int closestX = -1; + foreach (Character character in InteractingCharacters) + if (Math.Abs(character.DisplacementX - DisplacementX) < closestX || closestX == -1) + closestX = character.DisplacementX; + + int logIndex = GetLogIndex(closestX); + if (logIndex != -1) + DepressAt(logIndex); + + // Update log offsets + for (int i = 0; i < mSubType; i++) { + if (mLogOffsets[i] < mLogDestOffsets[i]) + mLogOffsets[i]++; + else if (mLogOffsets[i] > mLogDestOffsets[i]) + mLogOffsets[i]--; + } + } + + public override void Interact(LevelObject obj) + { + base.Interact(obj); + + Character character = obj as Character; + if (character == null) + return; + + if (InteractingCharacters.Contains(character)) { + if ((character.Status & CharacterState.Airborne) != 0) + return; + + int logIndex = GetLogIndex(character.DisplacementX); + if (logIndex != -1) + character.DisplacementY = DisplacementY + mLogOffsets[logIndex] - RadiusY - character.RadiusY; + } + } + + private void DepressAt(int logIndex) + { + int lowestOffset = mLogTotalOffsets[logIndex]; + mLogDestOffsets[logIndex] = lowestOffset; + + int logsLeft = logIndex; + float yInterval = lowestOffset / (logsLeft + 1.0f); + float y = lowestOffset - yInterval; + + for (int i = logIndex - 1; i >= 0; i--) { + mLogDestOffsets[i] = (int)y; + y -= yInterval; + } + + int logsRight = mSubType - logIndex - 1; + yInterval = lowestOffset / (logsRight + 1.0f); + y = lowestOffset - yInterval; + + for (int i = logIndex + 1; i < mSubType; i++) { + mLogDestOffsets[i] = (int)y; + y -= yInterval; + } + + + //int lowestOffset = mLogTotalOffsets[logIndex]; + //mLogOffsets[logIndex] = lowestOffset; + + //int logsLeft = logIndex; + //double angleInterval = (Math.PI / 4) / (logsLeft + 1); + //double angle = angleInterval; + //for (int i = 0; i < logIndex; i++) { + // int offset = (int)(Math.Sin(angle) * lowestOffset); + // mLogOffsets[i] = offset; + // angle += angleInterval; + //} + + //int logsRight = SubType - logIndex - 1; + //angleInterval = (Math.PI / 4) / (logsRight + 1); + //angle = (Math.PI / 4) - angleInterval; + //for (int i = logIndex + 1; i < SubType; i++) { + // int offset = (int)(Math.Sin(angle) * lowestOffset); + // mLogOffsets[i] = offset; + // angle -= angleInterval; + //} + } + + private int GetLogIndex(int x) + { + int xoffset = x - (DisplacementX - RadiusX); + int logIndex = xoffset / 16; + if (logIndex >= 0 && logIndex < mSubType) + return logIndex; + else + return -1; + } + + public override int Id + { + get { return 17; } + } + } +} diff --git a/s2prototype/Objects/Masher.cs b/s2prototype/Objects/Masher.cs new file mode 100644 index 0000000..6eea057 --- /dev/null +++ b/s2prototype/Objects/Masher.cs @@ -0,0 +1,71 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Masher : Badnik + { + private Animation mAnimation; + + private int mStatus; + private int mInitialY; + + private static byte[][] AnimationData = new byte[][] { + new byte[] { 7, 0, 1, 0xFF }, + new byte[] { 3, 0, 1, 0xFF }, + new byte[] { 7, 0, 0xFF }, + }; + + public Masher(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mAnimation = new Animation(AnimationData); + + RadiusX = 12; + RadiusY = 16; + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(mAnimation.FrameValue * 32 * Game.DisplayScale, 0 * Game.DisplayScale, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + Rectangle dst = new Rectangle(-15 * Game.DisplayScale, -15 * Game.DisplayScale, 30 * Game.DisplayScale, 30 * Game.DisplayScale); + + g.DrawImage(ResourceManager.MasherTexture, dst, src, Color.White); + } + + private void Init() + { + VelocityY = -400; + mInitialY = DisplacementY; + mStatus = 1; + } + + public override void Update() + { + if (!IsCloseToCharacter) + return; + + if (mStatus == 0) + Init(); + + mAnimation.Update(); + UpdatePosition(); + VelocityY += 24; + if (mInitialY < DisplacementY) { + DisplacementY = mInitialY; + VelocityY = -1280; + } + mAnimation.Index = 1; + if (DisplacementY - 192 < DisplacementY) { + mAnimation.Index = 0; + if (VelocityY >= 0) + mAnimation.Index = 2; + } + } + + public override int Id + { + get { return 92; } + } + } +} diff --git a/s2prototype/Objects/Monitor.cs b/s2prototype/Objects/Monitor.cs new file mode 100644 index 0000000..ffdc16a --- /dev/null +++ b/s2prototype/Objects/Monitor.cs @@ -0,0 +1,333 @@ +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class Monitor : SolidObject + { + private int mRoutine; + private int mRoutineSecondary; + + private bool mBroken; + private Character mBraker; + + private int mAnimationFrame; + private int mAnimationFrameDuration; + + private int mSubType; + + public Monitor(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + RadiusX = 16; + RadiusY = 16; + + mRoutine = 2; + mSubType = definition.SubType; + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(0 * Game.DisplayScale, 0 * Game.DisplayScale, 30 * Game.DisplayScale, 30 * Game.DisplayScale); + Rectangle dst = new Rectangle(-15 * Game.DisplayScale, -15 * Game.DisplayScale, 30 * Game.DisplayScale, 30 * Game.DisplayScale); + + if (mBroken) + src.X = 60 * Game.DisplayScale; + else if (mAnimationFrame == 1) + src.X = 30 * Game.DisplayScale; + + g.DrawImage(ResourceManager.MonitorTexture, dst, src, Color.White); + + if (!mBroken && mAnimationFrame == 0) { + int[] typePositions = new int[] { 0, 4, 4, 0, 1, 2, 0, 3, 0, 0, 0 }; + src.X = 14 * typePositions[mSubType] * Game.DisplayScale; + src.Y = 30 * Game.DisplayScale; + src.Width = 14 * Game.DisplayScale; + src.Height = 12 * Game.DisplayScale; + + dst.X += 8 * Game.DisplayScale; + dst.Y += 6 * Game.DisplayScale; + dst.Width = 14 * Game.DisplayScale; + dst.Height = 12 * Game.DisplayScale; + + g.DrawImage(ResourceManager.MonitorTexture, dst, src, Color.White); + } + } + + public override void Update() + { + switch (mRoutine) { + case 2: + UpdateMain(); + break; + case 4: + UpdateBreak(); + break; + } + + UpdateAnimation(); + } + + private void UpdateAnimation() + { + mAnimationFrameDuration--; + if (mAnimationFrameDuration < 0) { + mAnimationFrame = (mAnimationFrame + 1) % 2; + if (mAnimationFrame == 0) + mAnimationFrameDuration = 3; + else + mAnimationFrameDuration = 1; + } + } + + private void UpdateMain() + { + if (mRoutineSecondary != 0) { + UpdatePositionWithGravity(); + + int dist, angle = 0; + Level.FindFloor(DisplacementX, DisplacementY + RadiusY, 1, false, true, out dist, ref angle); + if (dist < 0) { + DisplacementY += dist; + VelocityY = 0; + mRoutineSecondary = 0; + } + } + + DoCharacterTouchResponse(26, 15, 16, DisplacementX); + } + + private void UpdateBreak() + { + mRoutine = 6; + + // Clear standing status for any other characters on monitor + ClearCharacterTouchStatus(); + + // Spawn icon + MonitorContents iconObject = new MonitorContents(Game, Level); + iconObject.DisplacementX = DisplacementX; + iconObject.DisplacementY = DisplacementY; + iconObject.Character = mBraker; + iconObject.SubType = mSubType; + Level.Objects.Add(iconObject); + + // Spawn smoke + Explosion explosion = new Explosion(Game, Level); + explosion.DisplacementX = DisplacementX; + explosion.DisplacementY = DisplacementY; + Level.Objects.Add(explosion); + + // Monitor is now broken + mBroken = true; + } + + protected override SolidObjectTouch GetCharacterTouch(SolidObjectTouch currentTouchStatus, Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + if ((currentTouchStatus & SolidObjectTouch.Standing) != 0) + return CheckOverEdge(currentTouchStatus, character, width, jumpRadiusY, walkRadiusY, x); + + if (character.Anim != CharacterAnimation.Roll) + return loc_199F0(currentTouchStatus, character, width, jumpRadiusY, walkRadiusY, x); + + return currentTouchStatus; + } + + private SolidObjectTouch CheckOverEdge(SolidObjectTouch currentTouchStatus, Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + int d0; + int d2 = width * 2; + if ((character.Status & CharacterState.Airborne) == 0) { + d0 = character.DisplacementX - DisplacementX + width; + if (d0 >= 0) + if (d0 < d2) + return CharStandOn(currentTouchStatus, character, width, d2, walkRadiusY, x); + } + + character.Status &= ~CharacterState.OnObject; + character.Status |= CharacterState.Airborne; + currentTouchStatus &= ~SolidObjectTouch.Standing; + // d4 = 0; + return currentTouchStatus; + } + + private SolidObjectTouch CharStandOn(SolidObjectTouch currentTouchStatus, Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + MoveCharacterOnPlatform(character, x, walkRadiusY); + // d4 = 0; + return currentTouchStatus; + } + + public override void Touch(LevelObject obj) + { + if (mBroken) + return; + + Character character = obj as Character; + if (character == null) + return; + + if (character.VelocityY < 0) { + if (character.DisplacementY - 16 < DisplacementY) + return; + character.VelocityY = -character.VelocityY; + VelocityY = -384; + if (mRoutineSecondary != 0) + return; + mRoutineSecondary = 4; + } else { + // if sidekick, return + + if (character.Anim != CharacterAnimation.Roll) + return; + + character.VelocityY = -character.VelocityY; + if (mRoutine != 4) { + mRoutine = 4; + mBraker = character; + } + } + } + + public override bool AllowBalancing + { + get + { + return true; + } + } + + public override int Id + { + get { return 38; } + } + + class MonitorContents : LevelObject + { + private int mRoutine; + private int mAnimFrameDuration; + private Character mCharacter; + private int mSubType; + + public MonitorContents(SonicGame game, Level level) + : base(game, level) + { + } + + public override void Draw(Graphics g) + { + // Draw box + Rectangle src = new Rectangle(7 * Game.DisplayScale, 5 * Game.DisplayScale, 16 * Game.DisplayScale, 14 * Game.DisplayScale); + Rectangle dst = new Rectangle(-8 * Game.DisplayScale, -7 * Game.DisplayScale, 14 * Game.DisplayScale, 12 * Game.DisplayScale); + + g.DrawImage(ResourceManager.MonitorTexture, dst, src, Color.White); + + // Draw contents + dst.X += Game.DisplayScale; + dst.Y += Game.DisplayScale; + dst.Width -= 2 * Game.DisplayScale; + dst.Height -= 2 * Game.DisplayScale; + + int[] typePositions = new int[] { 0, 4, 4, 0, 1, 2, 0, 3, 0, 0, 0 }; + src.X = 14 * typePositions[mSubType] * Game.DisplayScale; + src.Y = 30 * Game.DisplayScale; + src.Width = 14 * Game.DisplayScale; + src.Height = 12 * Game.DisplayScale; + + g.DrawImage(ResourceManager.MonitorTexture, dst, src, Color.White); + } + + public override void Update() + { + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + UpdateRaise(); + break; + case 4: + UpdateWait(); + break; + } + } + + private void Init() + { + mRoutine = 2; + VelocityY = -768; + } + + private void UpdateRaise() + { + if (VelocityY <= 0) { + UpdatePosition(); + VelocityY += 24; + return; + } + + mRoutine = 4; + mAnimFrameDuration = 29; + DoAction(); + } + + private void UpdateWait() + { + mAnimFrameDuration--; + if (mAnimFrameDuration < 0) + Finished = true; + } + + private void DoAction() + { + switch (mSubType) { + case 1: + case 2: + mCharacter.Player.AddLife(); + break; + case 4: + Level.AddSound(ResourceManager.RingSound, DisplacementX, DisplacementY); + mCharacter.Player.AddRings(10); + break; + case 5: + mCharacter.GiveSpeedShoes(); + break; + case 6: + mCharacter.GiveShield(); + break; + case 7: + mCharacter.GiveInvincibility(); + break; + } + } + + public override int Id + { + get { return 39; } + } + + public Character Character + { + get + { + return mCharacter; + } + set + { + mCharacter = value; + } + } + + public int SubType + { + get + { + return mSubType; + } + set + { + mSubType = value; + } + } + } + } +} diff --git a/s2prototype/Objects/Platform.cs b/s2prototype/Objects/Platform.cs new file mode 100644 index 0000000..d70cdc1 --- /dev/null +++ b/s2prototype/Objects/Platform.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; + +namespace IntelOrca.Sonic +{ + abstract class Platform : LevelObject + { + private List mInteractingCharacters = new List(); + private int mDiffX; + private int mDiffY; + + public Platform(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + } + + public override void Touch(LevelObject obj) + { + Character character = obj as Character; + if (character == null) + return; + + if (mInteractingCharacters.Contains(character)) + return; + + if (character.VelocityY < 0) + return; + + // Top + if (character.DisplacementY + character.RadiusY > DisplacementY - RadiusY && + character.DisplacementY + character.RadiusY < DisplacementY) { + LandCharacter(character); + } + } + + public override void Interact(LevelObject obj) + { + Character character = obj as Character; + if (character == null) + return; + + // Check if in the air + if ((character.Status & CharacterState.Airborne) == 0) { + // Check if still on object + int d0 = character.DisplacementX - DisplacementX + character.RadiusX + RadiusX; + if (d0 >= 0 && d0 < (character.RadiusX + RadiusX) * 2) { + UpdateCharacter(character); + return; + } + } + + UnlandCharacter(character); + } + + protected override void UpdatePosition() + { + mDiffX = DisplacementX; + mDiffY = DisplacementY; + base.UpdatePosition(); + mDiffX = DisplacementX - mDiffX; + mDiffY = DisplacementY - mDiffY; + } + + protected void UpdateCharacter(Character character) + { + character.DisplacementX += mDiffX; + character.DisplacementY += mDiffY; + } + + protected void LandCharacter(Character character) + { + // character.ResetOnFloor(); + character.VelocityY = 0; + character.DisplacementY = DisplacementY - RadiusY - character.RadiusY; + character.GroundVelocity = character.VelocityX; + + if ((character.Status & CharacterState.Airborne) != 0) + character.ResetOnFloor(); + + character.Status &= ~CharacterState.Airborne; + character.Status |= CharacterState.OnObject; + character.InteractionObject = this; + mInteractingCharacters.Add(character); + } + + protected void UnlandCharacter(Character character) + { + character.InteractionObject = null; + character.Status &= ~CharacterState.OnObject; + mInteractingCharacters.Remove(character); + } + + protected List InteractingCharacters + { + get + { + return mInteractingCharacters; + } + } + } +} diff --git a/s2prototype/Objects/Ring.cs b/s2prototype/Objects/Ring.cs new file mode 100644 index 0000000..9376614 --- /dev/null +++ b/s2prototype/Objects/Ring.cs @@ -0,0 +1,117 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Ring : LevelObject + { + private Animation mAnimation; + private bool mCollected; + private bool mScattering; + private int mTimeLeft; + + private static byte[][] AnimationData = new byte[][] { + new byte[] { 8, 0, 1, 2, 3, 0xFF }, + new byte[] { 5, 4, 5, 6, 7, 0xFF }, + }; + + public Ring(SonicGame game, Level level) + : base(game, level) + { + mAnimation = new Animation(AnimationData); + + mTimeLeft = 256; + + // DrawPriority = 50; + RadiusX = 6; + RadiusY = 6; + } + + public Ring(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mAnimation = new Animation(AnimationData); + + mTimeLeft = 256; + + // DrawPriority = 50; + RadiusX = 6; + RadiusY = 6; + } + + public override void Draw(Graphics g) + { + Rectangle dst = new Rectangle(-8 * Game.DisplayScale, -8 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + Rectangle src = new Rectangle((mAnimation.FrameValue % 4) * 16 * Game.DisplayScale, 0 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + + if (mAnimation.FrameValue > 3) + src.Y += 16 * Game.DisplayScale; + + g.DrawImage(ResourceManager.RingTexture, dst, src, Color.White); + } + + public override void Update() + { + if (mScattering) { + UpdatePosition(); + VelocityY += 24; + + int dist, angle = 0; + Level.FindFloor(DisplacementX, DisplacementY + 8, 1, false, true, out dist, ref angle); + if (dist < 0) { + // Bounce of ground + DisplacementY += dist; + VelocityY -= VelocityY >> 2; + VelocityY = -VelocityY; + } + + mTimeLeft--; + if (mTimeLeft < 0) + Finished = true; + } + + mAnimation.Update(); + if (mCollected && mAnimation.Frame == 4) + Finished = true; + } + + public override void Touch(LevelObject obj) + { + Character character = obj as Character; + if (character == null) + return; + + if (mScattering) + if (mTimeLeft > 256 - 64) + return; + + if (!mCollected) { + Level.AddSound(ResourceManager.RingSound, DisplacementX, DisplacementY); + + character.Player.AddRings(1); + + mCollected = true; + mAnimation.Index = 1; + mScattering = false; + DrawPriority = 50; + } + } + + public override int Id + { + get { return 37; } + } + + public bool Scattering + { + get + { + return mScattering; + } + set + { + mScattering = value; + } + } + } +} diff --git a/s2prototype/Objects/Signpost.cs b/s2prototype/Objects/Signpost.cs new file mode 100644 index 0000000..51e06f1 --- /dev/null +++ b/s2prototype/Objects/Signpost.cs @@ -0,0 +1,182 @@ +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class Signpost : LevelObject + { + private int mRoutine; + + private int mSpinAnimation; + private int mPersonShowing; + + private bool mWaitingForPlayer; + private int mSpinWait; + private int mSpinsRemaining; + + private int mSparkleOffsetIndex; + private int mNextSparkleDuration; + + private static int[] SparkleOffsets = new int[] { + -24,-16, + 8, 8, + -16, 0, + 24, -8, + 0, -8, + 16, 0, + -24, 8, + 24, 16, + }; + + public Signpost(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + } + + public override void Draw(Graphics g) + { + int mappingFrameX = mSpinAnimation / 2; + int mappingFrameY = mPersonShowing; + if (mappingFrameX >= 3) { + mappingFrameX = 1; + mappingFrameY = 4; + } + + Rectangle dst = new Rectangle(-24 * Game.DisplayScale, -24 * Game.DisplayScale, 48 * Game.DisplayScale, 48 * Game.DisplayScale); + Rectangle src = new Rectangle(mappingFrameX * 48 * Game.DisplayScale, mappingFrameY * 48 * Game.DisplayScale, 48 * Game.DisplayScale, 48 * Game.DisplayScale); + + dst.Y += 8 * Game.DisplayScale; + + g.DrawImage(ResourceManager.SignpostTexture, dst, src, Color.White); + } + + public override void Update() + { + switch (mRoutine) { + case 0: + Init(); + break; + case 1: + UpdateWait(); + break; + case 2: + UpdateSpin(); + break; + case 3: + UpdateSettled(); + break; + } + } + + private void Init() + { + mWaitingForPlayer = true; + mSpinWait = 8; + mSpinAnimation = 2; + mPersonShowing = 0; + mRoutine = 1; + mSpinsRemaining = 14; + } + + private void UpdateWait() + { + if (mWaitingForPlayer) { + if (Game.Players[0].MainCharacter.DisplacementX < DisplacementX) + return; + Game.Players[0].Status = PlayerStatus.Finished; + mWaitingForPlayer = false; + Level.AddSound(ResourceManager.SignpostSound, DisplacementX, DisplacementY); + } else { + mSpinWait--; + if (mSpinWait <= 0) { + mRoutine = 2; + mSpinAnimation = 4; + } + } + + UpdateSparkles(); + } + + private void UpdateSpin() + { + mSpinAnimation++; + if (mSpinsRemaining <= 0 && mSpinAnimation == 2) { + mRoutine = 3; + return; + } + + if (mSpinAnimation > 7) { + mSpinsRemaining--; + if (mSpinsRemaining <= 0) { + // Show main character + mSpinAnimation = 0; + mPersonShowing = 2; + } else { + // Next person to show + mSpinAnimation = 0; + mPersonShowing = (mPersonShowing + 1) % 3; + } + } + + UpdateSparkles(); + } + + private void UpdateSettled() + { + // Show level score breakdown + } + + private void UpdateSparkles() + { + mNextSparkleDuration--; + if (mNextSparkleDuration <= 0) { + mNextSparkleDuration = 12; + mSparkleOffsetIndex = (mSparkleOffsetIndex + 1) % 8; + + Sparkle sparkleObject = new Sparkle(Game, Level); + sparkleObject.DisplacementX = DisplacementX + SparkleOffsets[mSparkleOffsetIndex * 2]; + sparkleObject.DisplacementY = DisplacementY + SparkleOffsets[mSparkleOffsetIndex * 2 + 1]; + Level.Objects.Add(sparkleObject); + } + } + + public override int Id + { + get { return 13; } + } + + class Sparkle : LevelObject + { + private int mAnimationFrameDuration = 6; + private int mMappingFrame; + + public Sparkle(SonicGame game, Level level) + : base(game, level) + { + } + + public override void Draw(Graphics g) + { + Rectangle dst = new Rectangle(-8 * Game.DisplayScale, -8 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + Rectangle src = new Rectangle(mMappingFrame * 16 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + g.DrawImage(ResourceManager.RingTexture, dst, src, Color.White); + } + + public override void Update() + { + mAnimationFrameDuration--; + if (mAnimationFrameDuration <= 0) { + mMappingFrame++; + if (mMappingFrame == 4) { + Finished = true; + return; + } + } + } + + public override int Id + { + get { return 13; } + } + } + } +} diff --git a/s2prototype/Objects/SolidObject.cs b/s2prototype/Objects/SolidObject.cs new file mode 100644 index 0000000..c007652 --- /dev/null +++ b/s2prototype/Objects/SolidObject.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + [Flags] + enum SolidObjectTouch + { + NoTouch = 0, + Standing = 1, + Pushing = 2, + Top = 4, + Bottom = 8, + Side = 16, + } + + abstract class SolidObject : LevelObject + { + private Dictionary mCharacterTouchStatus = new Dictionary(); + + public SolidObject(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + } + + protected void ClearCharacterTouchStatus() + { + foreach (KeyValuePair kvp in mCharacterTouchStatus) { + if ((kvp.Value & SolidObjectTouch.Standing) != 0) { + kvp.Key.Status |= CharacterState.Airborne; + kvp.Key.Status &= ~CharacterState.OnObject; + } + } + } + + protected void DoCharacterTouchResponse(int width, int jumpRadiusY, int walkRadiusY, int x) + { + foreach (LevelObject obj in Level.Objects) { + Character character = obj as Character; + if (character != null) + DoCharacterTouchResponse(character, width, jumpRadiusY, walkRadiusY, x); + } + } + + protected void DoCharacterTouchResponse(Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + SolidObjectTouch touchStatus = SolidObjectTouch.NoTouch; + if (mCharacterTouchStatus.ContainsKey(character)) + touchStatus = mCharacterTouchStatus[character]; + + // Clear these as these will be re-worked out if necessary + touchStatus &= ~SolidObjectTouch.Top; + touchStatus &= ~SolidObjectTouch.Bottom; + touchStatus &= ~SolidObjectTouch.Side; + + mCharacterTouchStatus[character] = GetCharacterTouch(touchStatus, character, width, jumpRadiusY, walkRadiusY, x); + } + + protected virtual SolidObjectTouch GetCharacterTouch(SolidObjectTouch currentTouchStatus, Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + int d0, d2; + + if ((currentTouchStatus & SolidObjectTouch.Standing) != 0) { + d2 = width * 2; + if ((character.Status & CharacterState.Airborne) != 0) { + character.Status &= ~CharacterState.OnObject; + currentTouchStatus &= ~SolidObjectTouch.Standing; + // d4 = 0; + return currentTouchStatus; + } + + d0 = character.DisplacementX - DisplacementX + width; + if (d0 < 0) { + character.Status |= CharacterState.Airborne; + character.Status &= ~CharacterState.OnObject; + currentTouchStatus &= ~SolidObjectTouch.Standing; + // d4 = 0; + return currentTouchStatus; + } + + if (d0 < d2) { + MoveCharacterOnPlatform(character, x, walkRadiusY); + // d4 = 0; + return currentTouchStatus; + } + } + + // SolidObject_cont: + + return loc_199F0(currentTouchStatus, character, width, jumpRadiusY, walkRadiusY, x); + } + + protected SolidObjectTouch loc_199F0(SolidObjectTouch currentTouchStatus, Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + int d0, d1, d3, d4, d5; + + d0 = character.DisplacementX - DisplacementX + width; + if (d0 < 0) + return loc_19AC4(currentTouchStatus, character); + + d3 = width * 2; + if (d0 > d3) + return loc_19AC4(currentTouchStatus, character); + + jumpRadiusY += character.RadiusY; + d3 = character.DisplacementY - DisplacementY + 4 + jumpRadiusY; + if (d3 < 0) + return loc_19AC4(currentTouchStatus, character); + + d3 &= 0x7FF; + d4 = jumpRadiusY * 2; + if (d3 >= d4) + return loc_19AC4(currentTouchStatus, character); + + + // loc_19A2E: + if (character.ObjectControl < 0) + return loc_19AC4(currentTouchStatus, character); + + if (character.Routine >= 6) { + // d4 = 0; + return currentTouchStatus; + } + + d5 = d0; + if (width < d0) { + width *= 2; + d0 -= width; + d5 = -d0; + } + + width = d3; + if (jumpRadiusY < d3) { + d3 -= 4; + d3 -= d4; + width = -d3; + } + + if (d5 > width) { + if (d3 < 0) { + if (character.VelocityY == 0) { + if ((character.Status & CharacterState.Airborne) != 0) { + currentTouchStatus |= SolidObjectTouch.Bottom; + // d4 = -2 + return currentTouchStatus; + } + + d0 = Math.Abs(d4); + if (d4 < 16) + return loc_19A6A(currentTouchStatus, character, width, d0); + + character.Kill(); + currentTouchStatus |= SolidObjectTouch.Bottom; + // d4 = -2 + return currentTouchStatus; + } + + if (character.VelocityY < 0 && d3 < 0) { + character.DisplacementY -= d3; + character.VelocityY = 0; + } + + currentTouchStatus |= SolidObjectTouch.Bottom; + // d4 = -2 + return currentTouchStatus; + } + + if (d3 < 16) { + d3 -= 4; + d1 = RadiusX + character.DisplacementX - DisplacementX; // width_pixels + if (d1 < 0) { + // d4 = 0; + return currentTouchStatus; + } + + if (d1 >= RadiusX * 2) { // width_pixels + // d4 = 0; + return currentTouchStatus; + } + + if (character.VelocityY < 0) { + // d4 = 0; + return currentTouchStatus; + } + + character.DisplacementY -= d3 - 1; + currentTouchStatus = loc_19E14(currentTouchStatus, character); + currentTouchStatus |= SolidObjectTouch.Top; + d4 = -1; + return currentTouchStatus; + } + + // if (id == LauncherSpring) { + // if (d3 < 20) + // goto loc_19B56 + // } + + return loc_19AC4(currentTouchStatus, character); + } + + return loc_19A6A(currentTouchStatus, character, width, d0); + } + + private SolidObjectTouch loc_19A6A(SolidObjectTouch currentTouchStatus, Character character, int width, int d0) + { + if (width <= 4) + return loc_19AB6(currentTouchStatus, character); + + if (d0 == 0) + return loc_19A90(currentTouchStatus, character, d0); + + if (d0 < 0) { + if (character.VelocityX >= 0) + return loc_19A90(currentTouchStatus, character, d0); + } else { + if (character.VelocityX < 0) + return loc_19A90(currentTouchStatus, character, d0); + } + + character.GroundVelocity = 0; + character.VelocityX = 0; + + return loc_19A90(currentTouchStatus, character, d0); + } + + private SolidObjectTouch loc_19AB6(SolidObjectTouch currentTouchStatus, Character character) + { + currentTouchStatus &= ~SolidObjectTouch.Pushing; + character.Status &= ~CharacterState.Pushing; + currentTouchStatus |= SolidObjectTouch.Side; + // d4 = 1; + return currentTouchStatus; + } + + private SolidObjectTouch loc_19A90(SolidObjectTouch currentTouchStatus, Character character, int d0) + { + character.DisplacementX -= d0; + if ((character.Status & CharacterState.Airborne) != 0) + return loc_19AB6(currentTouchStatus, character); + + currentTouchStatus |= SolidObjectTouch.Pushing; + currentTouchStatus |= SolidObjectTouch.Side; + character.Status |= CharacterState.Pushing; + // d4 = 1; + return currentTouchStatus; + } + + private SolidObjectTouch loc_19AC4(SolidObjectTouch currentTouchStatus, Character character) + { + if ((currentTouchStatus & SolidObjectTouch.Pushing) == 0) { + // d4 = 0; + return currentTouchStatus; + } + + // Is character not rolling? + if (character.Anim != CharacterAnimation.Roll) { + // Set character animation to running + character.Anim = CharacterAnimation.Run; + } + + currentTouchStatus &= ~SolidObjectTouch.Pushing; + character.Status &= ~CharacterState.Pushing; + // d4 = 0; + return currentTouchStatus; + } + + private SolidObjectTouch loc_19E14(SolidObjectTouch currentTouchStatus, Character character) + { + if ((character.Status & CharacterState.OnObject) != 0) { + // character.InteractionObject + } + + character.InteractionObject = this; + character.Angle = 0; + character.VelocityY = 0; + character.GroundVelocity = character.VelocityX; + if ((character.Status & CharacterState.Airborne) != 0) + character.ResetOnFloor(); + + character.Status |= CharacterState.OnObject; + character.Status &= ~CharacterState.Airborne; + currentTouchStatus |= SolidObjectTouch.Standing; + return currentTouchStatus; + } + + protected void MoveCharacterOnPlatform(Character character, int x, int radiusY) + { + if (character.ObjectControl < 0) + return; + if (character.Routine >= 6) + return; + character.DisplacementY = DisplacementY - radiusY - character.RadiusY; + character.DisplacementX -= DisplacementX - x; + } + + protected Dictionary CharacterTouchStatus + { + get + { + return mCharacterTouchStatus; + } + set + { + mCharacterTouchStatus = value; + } + } + } +} diff --git a/s2prototype/Objects/Sonic.cs b/s2prototype/Objects/Sonic.cs new file mode 100644 index 0000000..9b9c2d5 --- /dev/null +++ b/s2prototype/Objects/Sonic.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + class Sonic : Character + { + public Sonic(SonicGame game, Level level) + : base(game, level) + { + } + } +} diff --git a/s2prototype/Objects/Spikes.cs b/s2prototype/Objects/Spikes.cs new file mode 100644 index 0000000..a1ed1c7 --- /dev/null +++ b/s2prototype/Objects/Spikes.cs @@ -0,0 +1,188 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Spikes : SolidObject + { + private int mRoutine; + private int mMappingFrame; + + private int mInitialDisplacementX; + private int mInitialDisplacementY; + private int mMovementOffset; + private int mMoveDirection; + private int mMoveWaitDuration; + + private int mSubType; + private bool mFlipX; + private bool mFlipY; + + private int[] SizeData = new int[] { + 16, 16, + 32, 32, + 48, 16, + 64, 16, + 16, 16, + 16, 32, + 16, 48, + 16, 64, + }; + + public Spikes(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mSubType = definition.SubType; + mFlipX = definition.FlipX; + mFlipY = definition.FlipY; + } + + public override void Draw(Graphics g) + { + Rectangle src = new Rectangle(0, 0, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + Rectangle dst = new Rectangle(-RadiusX * Game.DisplayScale, -RadiusY * Game.DisplayScale, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + + SpriteEffects fx = SpriteEffects.None; + if (mFlipX) + fx |= SpriteEffects.FlipHorizontally; + if (mFlipY) + fx |= SpriteEffects.FlipVertically; + + for (int y = 0; y < RadiusY / 16; y++) { + for (int x = 0; x < RadiusX / 16; x++) { + dst = new Rectangle((-RadiusX + (x * 32)) * Game.DisplayScale, (-RadiusY + (y * 32)) * Game.DisplayScale, 32 * Game.DisplayScale, 32 * Game.DisplayScale); + g.DrawImage(ResourceManager.SpikesTexture, dst, src, Color.White, fx); + } + } + } + + public override void Update() + { + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + UpdatePointingUp(); + break; + case 4: + UpdatePointingLeftRight(); + break; + case 6: + UpdatePointingDown(); + break; + } + } + + private void Init() + { + int d0; + + mRoutine += 2; + d0 = mSubType & 0xF0; + mSubType &= 0x0F; + + RadiusX = SizeData[d0 >> 3]; + RadiusY = SizeData[d0 >> 3 + 1]; + mMappingFrame = d0 >> 4; + if (d0 >> 4 >= 4) + mRoutine = 4; + if (mFlipY) + mRoutine = 6; + mInitialDisplacementX = DisplacementX; + mInitialDisplacementY = DisplacementY; + } + + private void UpdatePointingUp() + { + UpdateMovement(); + + DoCharacterTouchResponse(RadiusX + 11, RadiusY, RadiusY + 1, DisplacementX); + foreach (KeyValuePair kvp in CharacterTouchStatus) + if ((kvp.Value & SolidObjectTouch.Standing) != 0) + HurtCharacter(kvp.Key); + } + + private void UpdatePointingLeftRight() + { + UpdateMovement(); + + DoCharacterTouchResponse(RadiusX + 11, RadiusY, RadiusY + 1, DisplacementX); + foreach (KeyValuePair kvp in CharacterTouchStatus) + if ((kvp.Value & SolidObjectTouch.Pushing) != 0) + HurtCharacter(kvp.Key); + } + + private void UpdatePointingDown() + { + UpdateMovement(); + + DoCharacterTouchResponse(RadiusX + 11, RadiusY, RadiusY + 1, DisplacementX); + foreach (KeyValuePair kvp in CharacterTouchStatus) + if ((kvp.Value & SolidObjectTouch.Bottom) != 0) + HurtCharacter(kvp.Key); + } + + private void HurtCharacter(Character character) + { + if ((character.StatusSecondary & 2) != 0) + return; + if (character.Invulnerable) + return; + if (character.Routine >= 4) + return; + + character.DisplacementY = character.DisplacementY - (character.VelocityY << 8); + character.Hurt(this); + } + + private void UpdateMovement() + { + switch (mSubType) { + case 1: + UpdateMovement2(); + DisplacementY = (mMovementOffset >> 8) + mInitialDisplacementY; + break; + case 2: + UpdateMovement2(); + DisplacementX = (mMovementOffset >> 8) + mInitialDisplacementY; + break; + } + } + + private void UpdateMovement2() + { + if (mMoveWaitDuration == 0) { + if (mMoveDirection != 0) { + mMovementOffset -= 2048; + if (mMovementOffset >= 0) + return; + mMovementOffset = 0; + mMoveDirection = 0; + mMoveWaitDuration = 60; + } else { + mMovementOffset += 2048; + if (mMovementOffset < 8192) + return; + mMovementOffset = 8192; + mMoveDirection = 1; + mMoveWaitDuration = 60; + } + } else { + mMoveWaitDuration--; + if (mMoveWaitDuration != 0) + return; + + // test render flags + + Level.AddSound(ResourceManager.SpikesMoveSound, DisplacementX, DisplacementY); + } + } + + public override int Id + { + get { return 54; } + } + } +} diff --git a/s2prototype/Objects/Spring.cs b/s2prototype/Objects/Spring.cs new file mode 100644 index 0000000..d91d74c --- /dev/null +++ b/s2prototype/Objects/Spring.cs @@ -0,0 +1,429 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class Spring : SolidObject + { + private int mRoutine; + private int mMappingFrame; + private int mUnk30; + private int mAnimationDuration; + + private int mSubType; + private bool mFlipX; + + public Spring(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mSubType = definition.SubType; + mFlipX = definition.FlipX; + } + + public override void Draw(Graphics g) + { + int type = (mSubType >> 3) & 0x0E; + bool diagonal = (type == 6 || type == 8); + int size = 32; + if (diagonal) + size = 44; + + Rectangle dst = new Rectangle(-(size / 2) * Game.DisplayScale, -(size / 2) * Game.DisplayScale, size * Game.DisplayScale, size * Game.DisplayScale); + Rectangle src = new Rectangle(mMappingFrame * size * Game.DisplayScale, 0, size * Game.DisplayScale, size * Game.DisplayScale); + + SpriteEffects fx = SpriteEffects.None; + + if ((mSubType & 2) != 0) + src.X += 3 * size * Game.DisplayScale; + + switch (type) { + case 0: + dst.Y -= 7 * Game.DisplayScale; + break; + case 2: + dst.X += 7 * Game.DisplayScale; + src.Y += 32 * Game.DisplayScale; + break; + case 4: + dst.Y += 8 * Game.DisplayScale; + fx = SpriteEffects.FlipVertically; + break; + case 6: + src.Y += 64 * Game.DisplayScale; + if (mFlipX) + dst.X -= 5 * Game.DisplayScale; + else + dst.X += 5 * Game.DisplayScale; + dst.Y -= 5 * Game.DisplayScale; + break; + case 8: + fx = SpriteEffects.FlipVertically; + break; + } + + + if (mFlipX) + fx = SpriteEffects.FlipHorizontally; + + g.DrawImage(ResourceManager.SpringTexture, dst, src, Color.White, fx); + } + + public override void Update() + { + if (mAnimationDuration >= 0) { + if (mAnimationDuration > 8) + mMappingFrame = 1; + else + mMappingFrame = 2; + mAnimationDuration--; + } else { + mMappingFrame = 1; + } + + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + UpdateUp(); + break; + case 4: + UpdateHorizontal(); + break; + case 6: + UpdateDown(); + break; + case 8: + UpdateDiagonallyUp(); + break; + case 10: + UpdateDiagonallyDown(); + break; + } + } + + private void Init() + { + mRoutine = 10; + switch ((mSubType >> 3) & 0x0E) { + case 0: + mRoutine = 2; + RadiusX = 16; + break; + case 2: + mRoutine = 4; + RadiusX = 8; + break; + case 4: + mRoutine = 6; + RadiusX = 16; + break; + case 6: + mRoutine = 8; + RadiusX = 16; + break; + case 8: + mRoutine = 10; + RadiusX = 16; + break; + } + + if ((mSubType & 2) == 0) + mUnk30 = -4096; + else + mUnk30 = -2560; + + mMappingFrame = 1; + } + + private void UpdateUp() + { + DoCharacterTouchResponse(27, 8, 16, DisplacementX); + + foreach (KeyValuePair kvp in CharacterTouchStatus.ToArray()) { + if ((kvp.Value & SolidObjectTouch.Standing) == 0) + continue; + + SpringUp(kvp.Key); + } + } + + private void UpdateHorizontal() + { + DoCharacterTouchResponse(19, 14, 15, DisplacementX); + + foreach (KeyValuePair kvp in CharacterTouchStatus.ToArray()) { + if ((kvp.Value & SolidObjectTouch.Pushing) == 0) + continue; + + SpringHorizontally(kvp.Key); + } + + loc_18BC6(); + } + + private void UpdateDown() + { + DoCharacterTouchResponse(27, 8, 16, DisplacementX); + + foreach (KeyValuePair kvp in CharacterTouchStatus.ToArray()) { + if ((kvp.Value & SolidObjectTouch.Bottom) == 0) + continue; + + SpringDown(kvp.Key); + } + } + + private void UpdateDiagonallyUp() + { + DoCharacterTouchResponse(27, 16, 16, DisplacementX); + + foreach (KeyValuePair kvp in CharacterTouchStatus.ToArray()) { + if ((kvp.Value & (SolidObjectTouch.Standing | SolidObjectTouch.Side)) == 0) + continue; + + SpringDiagonallyUp(kvp.Key); + } + } + + private void UpdateDiagonallyDown() + { + + } + + private void SpringUp(Character character) + { + mMappingFrame = 0; + mAnimationDuration = 10; + + character.VelocityY = mUnk30; + character.DisplacementY += 8; + character.Status |= CharacterState.Airborne; + character.Status &= ~CharacterState.OnObject; + character.Anim = CharacterAnimation.Spring; + character.Routine = 2; + + if (mSubType < 0) + character.VelocityX = 0; + + if ((mSubType & 1) != 0) { + character.GroundVelocity = 1; + character.FlipAngle = 1; + character.Anim = 0; + character.FlipsRemaining = 0; + character.FlipSpeed = 4; + if ((mSubType & 2) == 0) + character.FlipsRemaining = 1; + if ((character.Status & CharacterState.FacingLeft) != 0) { + character.FlipAngle = -character.FlipAngle; + character.GroundVelocity = -character.GroundVelocity; + } + } + + if ((mSubType & 0x0C) == 4) { + character.Layer = 12; + character.LayerPlus = 13; + } + + if ((mSubType & 0x0C) == 8) { + character.Layer = 14; + character.LayerPlus = 15; + } + + Level.AddSound(ResourceManager.BounceSound, DisplacementX, DisplacementY); + } + + private void SpringHorizontally(Character character) + { + mMappingFrame = 0; + mAnimationDuration = 10; + + if (!mFlipX) { + character.VelocityX = -mUnk30; + character.DisplacementX -= 8; + character.Status &= ~CharacterState.FacingLeft; + } else { + character.VelocityX = mUnk30; + character.DisplacementX += 8; + character.Status |= CharacterState.FacingLeft; + } + + character.MoveLock = 15; + character.GroundVelocity = character.VelocityX; + if ((character.Status & CharacterState.Spinning) == 0) + character.Anim = 0; + + if (mSubType < 0) + character.VelocityY = 0; + + if ((mSubType & 1) != 0) { + character.GroundVelocity = 1; + character.FlipAngle = 1; + character.Anim = 0; + character.FlipsRemaining = 1; + character.FlipSpeed = 8; + if ((mSubType & 2) == 0) + character.FlipsRemaining = 3; + if ((character.Status & CharacterState.FacingLeft) != 0) { + character.FlipAngle = -character.FlipAngle; + character.GroundVelocity = -character.GroundVelocity; + } + } + + if ((mSubType & 0x0C) == 4) { + character.Layer = 12; + character.LayerPlus = 13; + } + + if ((mSubType & 0x0C) == 8) { + character.Layer = 14; + character.LayerPlus = 15; + } + + CharacterTouchStatus[character] &= ~SolidObjectTouch.Pushing; + character.Status &= ~CharacterState.Pushing; + Level.AddSound(ResourceManager.BounceSound, DisplacementX, DisplacementY); + } + + private void SpringDown(Character character) + { + mMappingFrame = 0; + mAnimationDuration = 10; + + character.VelocityY = -mUnk30; + character.DisplacementY -= 8; + character.Status |= CharacterState.Airborne; + character.Status &= ~CharacterState.OnObject; + character.Anim = CharacterAnimation.Spring; + character.Routine = 2; + + if (mSubType < 0) + character.VelocityX = 0; + + if ((mSubType & 1) != 0) { + character.GroundVelocity = 1; + character.FlipAngle = 1; + character.Anim = 0; + character.FlipsRemaining = 0; + character.FlipSpeed = 4; + if ((mSubType & 2) == 0) + character.FlipsRemaining = 1; + if ((character.Status & CharacterState.FacingLeft) != 0) { + character.FlipAngle = -character.FlipAngle; + character.GroundVelocity = -character.GroundVelocity; + } + } + + if ((mSubType & 0x0C) == 4) { + character.Layer = 12; + character.LayerPlus = 13; + } + + if ((mSubType & 0x0C) == 8) { + character.Layer = 14; + character.LayerPlus = 15; + } + + Level.AddSound(ResourceManager.BounceSound, DisplacementX, DisplacementY); + } + + private void SpringDiagonallyUp(Character character) + { + // Check if on the spring and not just the ledge + if (!mFlipX) { + if (character.DisplacementX <= DisplacementX - 4) + return; + } else { + if (character.DisplacementX > DisplacementX + 4) + return; + } + + if (character.DisplacementY > DisplacementY + 4) + return; + + mMappingFrame = 0; + mAnimationDuration = 10; + + if (!mFlipX) { + character.VelocityX = -mUnk30; + character.DisplacementX -= 6; + character.Status &= ~CharacterState.FacingLeft; + } else { + character.VelocityX = mUnk30; + character.DisplacementX += 6; + character.Status |= CharacterState.FacingLeft; + } + + character.VelocityY = mUnk30; + + character.Status |= CharacterState.Airborne; + character.Status &= ~CharacterState.OnObject; + character.Anim = CharacterAnimation.Spring; + character.Routine = 2; + + if ((mSubType & 1) != 0) { + character.GroundVelocity = 1; + character.FlipAngle = 1; + character.Anim = 0; + character.FlipsRemaining = 1; + character.FlipSpeed = 8; + if ((mSubType & 2) == 0) + character.FlipsRemaining = 3; + if ((character.Status & CharacterState.FacingLeft) != 0) { + character.FlipAngle = -character.FlipAngle; + character.GroundVelocity = -character.GroundVelocity; + } + } + + if ((mSubType & 0x0C) == 4) { + character.Layer = 12; + character.LayerPlus = 13; + } + + if ((mSubType & 0x0C) == 8) { + character.Layer = 14; + character.LayerPlus = 15; + } + + Level.AddSound(ResourceManager.BounceSound, DisplacementX, DisplacementY); + } + + private void SpringDiagonallyDown(Character character) + { + + } + + private void loc_18BC6() + { + + } + + protected override SolidObjectTouch GetCharacterTouch(SolidObjectTouch currentTouchStatus, Character character, int width, int jumpRadiusY, int walkRadiusY, int x) + { + if ((currentTouchStatus & SolidObjectTouch.Standing) == 0) + return loc_199F0(currentTouchStatus, character, width, jumpRadiusY, walkRadiusY, x); + + if ((character.Status & CharacterState.Airborne) == 0) { + int d0 = character.DisplacementX - DisplacementX + width; + if (d0 >= 0 && d0 < width * 2) { + MoveCharacterOnPlatform(character, x, walkRadiusY); + // d4 = 0; + return currentTouchStatus; + } + } + + character.Status &= ~CharacterState.OnObject; + character.Status |= CharacterState.Airborne; + currentTouchStatus &= ~SolidObjectTouch.Standing; + // d4 = 0; + return currentTouchStatus; + } + + public override int Id + { + get { return 65; } + } + } +} diff --git a/s2prototype/Objects/Starpost.cs b/s2prototype/Objects/Starpost.cs new file mode 100644 index 0000000..a590b29 --- /dev/null +++ b/s2prototype/Objects/Starpost.cs @@ -0,0 +1,122 @@ +using System; +using Microsoft.Xna.Framework; + +namespace IntelOrca.Sonic +{ + class Starpost : LevelObject + { + private int mIndex; + + private int mRoutine; + private int mStarOffsetDisplacementX; + private int mStarOffsetDisplacementY; + private int mAngle; + private int mDongleDuration; + + private int mMappingFrame; + private int mAnimationFrameDuration; + + public Starpost(SonicGame game, Level level, LevelObjectDefinition definition) + : base(game, level, definition) + { + mIndex = definition.SubType; + } + + public override void Draw(Graphics g) + { + Rectangle dst = new Rectangle(-8 * Game.DisplayScale, -24 * Game.DisplayScale, 16 * Game.DisplayScale, 48 * Game.DisplayScale); + Rectangle src = new Rectangle(0 * Game.DisplayScale, 32 * Game.DisplayScale, 16 * Game.DisplayScale, 48 * Game.DisplayScale); + g.DrawImage(ResourceManager.StarpostTexture, dst, src, Color.White); + + dst = new Rectangle((-8 + mStarOffsetDisplacementX) * Game.DisplayScale, (-24 - 5 + mStarOffsetDisplacementY) * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + src = new Rectangle(0 * Game.DisplayScale, mMappingFrame * 16 * Game.DisplayScale, 16 * Game.DisplayScale, 16 * Game.DisplayScale); + g.DrawImage(ResourceManager.StarpostTexture, dst, src, Color.White); + } + + public override void Update() + { + switch (mRoutine) { + case 0: + Init(); + break; + case 2: + foreach (Player player in Game.Players) + if (CheckActivation(player.MainCharacter)) + Activate(player); + break; + case 4: + UpdateDongle(); + break; + case 6: + mAnimationFrameDuration--; + if (mAnimationFrameDuration <= 0) { + mMappingFrame = (mMappingFrame + 1) % 2; + mAnimationFrameDuration = 4; + } + break; + } + + UpdateStarLocation(); + } + + private void Init() + { + mDongleDuration = 32; + mRoutine = 2; + + if (Game.Players[0].LastStarpostIndex >= mIndex) + mRoutine = 6; + } + + private void UpdateDongle() + { + mDongleDuration--; + if (mDongleDuration < 0) { + mAngle = 0; + mRoutine = 6; + return; + } + + UpdateStarLocation(); + + mAngle -= 16; + } + + private void UpdateStarLocation() + { + mStarOffsetDisplacementY = SonicMaths.Sin(mAngle - 64) * 11 >> 8; + mStarOffsetDisplacementX = SonicMaths.Cos(mAngle - 64) * 11 >> 8; + } + + private void Activate(Player player) + { + mRoutine = 4; + Level.AddSound(ResourceManager.StarpostSound, DisplacementX, DisplacementY); + + player.LastStarpostIndex = mIndex; + player.LastStarpostTime = player.Time; + } + + private bool CheckActivation(Character character) + { + if (Math.Abs(character.DisplacementX - DisplacementX) >= 8) + return false; + if (Math.Abs(character.DisplacementY - DisplacementY) >= 40) + return false; + return true; + } + + public int Index + { + get + { + return mIndex; + } + } + + public override int Id + { + get { return 121; } + } + } +} diff --git a/s2prototype/Player.cs b/s2prototype/Player.cs new file mode 100644 index 0000000..6a2fe01 --- /dev/null +++ b/s2prototype/Player.cs @@ -0,0 +1,261 @@ + +namespace IntelOrca.Sonic +{ + enum PlayerStatus + { + NotReady, + Playing, + Finished, + Dead, + } + + class Player + { + private SonicGame mGame; + private Character mMainCharacter; + private Character mSideKick; + private int mScore; + private int mTime; + private int mRings; + private int mLifeCount; + private int mNextExtraLifeScore = 5000; + private int mNextExtraLifeRings = 100; + private int mChainBonusCounter; + private PlayerStatus mStatus; + + private int mLastStarpostTime; + private int mLastStarpostIndex; + + private MusicManager mMusicManager = new MusicManager(); + + public Player(SonicGame game) + { + mGame = game; + } + + public void Update() + { + if (mStatus == PlayerStatus.Playing) + mTime++; + mMainCharacter.ControllerState = mGame.ControllerA; + + mMusicManager.Update(); + } + + public void AddPoints(int points) + { + mScore += points; + if (mScore > 9999999) + mScore = 9999999; + if (mScore < mNextExtraLifeScore) + return; + mNextExtraLifeScore += 50000; + AddLife(); + } + + public void AddRings(int rings) + { + mRings += rings; + + if (mRings > 999) + mRings = 999; + + if (mRings < mNextExtraLifeRings) + return; + + mNextExtraLifeRings += 100; + AddLife(); + } + + public void AddLife() + { + mLifeCount++; + + mMusicManager.PlayJingle(ResourceManager.LifeMusic); + } + + public void Restart() + { + mStatus = PlayerStatus.NotReady; + mRings = 0; + mTime = mLastStarpostTime; + } + + public void SetupCharacter() + { + mMainCharacter = new Sonic(mGame, mGame.Level); + mMainCharacter.Player = this; + + if (mLastStarpostIndex == 0) { + mMainCharacter.DisplacementX = mGame.Level.StartX; + mMainCharacter.DisplacementY = mGame.Level.StartY; + } else { + bool mFoundStarpost = false; + foreach (LevelObject obj in mGame.Level.Objects) { + if (!(obj is Starpost)) + continue; + + Starpost starpost = (Starpost)obj; + if (starpost.Index != mLastStarpostIndex) + continue; + + mMainCharacter.DisplacementX = starpost.DisplacementX; + mMainCharacter.DisplacementY = starpost.DisplacementY; + mFoundStarpost = true; + break; + } + + if (!mFoundStarpost) { + mMainCharacter.DisplacementX = mGame.Level.StartX; + mMainCharacter.DisplacementY = mGame.Level.StartY; + } + } + } + + public void LoseRings() + { + mRings = 0; + mNextExtraLifeRings = 100; + } + + public Character MainCharacter + { + get + { + return mMainCharacter; + } + set + { + mMainCharacter = value; + } + } + + public Character SideKick + { + get + { + return mSideKick; + } + set + { + mSideKick = value; + } + } + + public int Score + { + get + { + return mScore; + } + set + { + mScore = value; + } + } + + public int Time + { + get + { + return mTime; + } + set + { + mTime = value; + } + } + + public int Rings + { + get + { + return mRings; + } + set + { + mRings = value; + } + } + + public int LifeCount + { + get + { + return mLifeCount; + } + set + { + mLifeCount = value; + } + } + + public int NextExtraLifeScore + { + get + { + return mNextExtraLifeScore; + } + set + { + mNextExtraLifeScore = value; + } + } + + public int ChainBonusCounter + { + get + { + return mChainBonusCounter; + } + set + { + mChainBonusCounter = value; + } + } + + public PlayerStatus Status + { + get + { + return mStatus; + } + set + { + mStatus = value; + } + } + + public MusicManager MusicManager + { + get + { + return mMusicManager; + } + } + + public int LastStarpostIndex + { + get + { + return mLastStarpostIndex; + } + set + { + mLastStarpostIndex = value; + } + } + + public int LastStarpostTime + { + get + { + return mLastStarpostTime; + } + set + { + mLastStarpostTime = value; + } + } + } +} diff --git a/s2prototype/PlayerView.cs b/s2prototype/PlayerView.cs new file mode 100644 index 0000000..c3b15b2 --- /dev/null +++ b/s2prototype/PlayerView.cs @@ -0,0 +1,152 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class PlayerView + { + private SonicGame mGame; + private Level mLevel; + private Player mPlayer; + private Camera mCamera; + private TitleCard mTitleCard; + private Rectangle mBounds; + + private int mUpdateCount; + + private Texture2D mCollisionTexture; + + public PlayerView(SonicGame game, Player player) + { + mGame = game; + mPlayer = player; + mCamera = mPlayer.MainCharacter.Camera; + mLevel = mGame.Level; + mTitleCard = new TitleCard(mGame, mLevel); + } + + public void Update() + { + if (mTitleCard != null) { + mTitleCard.Update(); + if (mTitleCard.AllowStart) + mPlayer.Status = PlayerStatus.Playing; + } + + PlaySoundsInView(); + + mUpdateCount++; + } + + private void PlaySoundsInView() + { + // Get camera view + Rectangle view = mCamera.GetViewBounds(mLevel.VisibleBoundary, mBounds.Width / mGame.DisplayScale, mBounds.Height / mGame.DisplayScale); + + foreach (Level.Sound sound in mLevel.Sounds) + if (view.Contains(sound.DisplacementX, sound.DisplacementY)) + sound.SoundEffect.Play(); + } + + public void Draw(Graphics g) + { + g.DrawImage(ResourceManager.PlainTexture, mBounds, Color.Blue); + + // Get camera view + Rectangle view = mCamera.GetViewBounds(mLevel.VisibleBoundary, mBounds.Width / mGame.DisplayScale, mBounds.Height / mGame.DisplayScale); + DrawChunks(g, view, false); + // DrawCollision(g, view); + mLevel.Objects.Draw(g, view, 0, 999); + DrawChunks(g, view, true); + mLevel.Objects.Draw(g, view, 1000, 4999); + DrawHUD(g); + + if (!mTitleCard.IsFinished) + mTitleCard.Draw(g, mBounds); + } + + private void DrawChunks(Graphics g, Rectangle view, bool front) + { + int leftChunk = view.X / 128; + int topChunk = view.Y / 128; + int leftOffset = view.X % 128; + int topOffset = view.Y % 128; + int chunksWide = (view.Width / 128) + 2; + int chunksHigh = (view.Height / 128) + 2; + + for (int y = 0; y < chunksHigh; y++) { + for (int x = 0; x < chunksWide; x++) { + if (leftChunk + x >= mLevel.Width || topChunk + y >= 8 || leftChunk + x < 0 || topChunk + y < 0) + continue; + + int chunkId = mLevel.LevelLayout[leftChunk + x, topChunk + y]; + Texture2D tex = (!front ? ResourceManager.ChunkTexturesBack[chunkId] : ResourceManager.ChunkTexturesFront[chunkId]); + g.DrawImage(tex, new Rectangle((x * 128 - leftOffset) * 4, (y * 128 - topOffset) * 4, 128 * 4, 128 * 4), Color.White); + } + } + } + + private void DrawCollision(Graphics g, Rectangle view) + { + if (mCollisionTexture != null) + mCollisionTexture.Dispose(); + + mCollisionTexture = new Texture2D(g.GraphicsDevice, view.Width, view.Height); + Color[] bits = new Color[view.Width * view.Height]; + for (int y = 0; y < view.Height; y++) { + for (int x = 0; x < view.Width; x++) { + bool t = mLevel.IsSolid(x + view.X, y + view.Y, mPlayer.MainCharacter.Layer, false, true); + bool lrb = mLevel.IsSolid(x + view.X, y + view.Y, mPlayer.MainCharacter.Layer, true, false); + if (t && lrb) + bits[y * view.Width + x] = Color.Black; + else if (t && !lrb) + bits[y * view.Width + x] = Color.White; + else if (!t && lrb) + bits[y * view.Width + x] = Color.Yellow; + else + bits[y * view.Width + x] = Color.Transparent; + } + } + mCollisionTexture.SetData(bits); + + g.DrawImage(mCollisionTexture, mBounds, Color.White); + } + + private void DrawHUD(Graphics g) + { + Color timeColour = Color.Yellow; + Color ringsColour = Color.Yellow; + if (mUpdateCount % 16 >= 8) { + if (mPlayer.Time >= 60 * 60 * 9) + timeColour = Color.Red; + if (mPlayer.Rings == 0) + ringsColour = Color.Red; + } + + ResourceManager.NormalFont.DrawString(g, "SCORE", 16 * 4, 9 * 4, Color.Yellow); + ResourceManager.NormalFont.DrawString(g, "TIME", 17 * 4, 25 * 4, timeColour); + ResourceManager.NormalFont.DrawString(g, "RINGS", 16 * 4, 41 * 4, ringsColour); + + string scoreText = mPlayer.Score.ToString(); + string timeText = String.Format("{0}:{1:00}", mPlayer.Time / 60 / 60, (mPlayer.Time / 60) % 60); + string ringsText = mPlayer.Rings.ToString(); + + ResourceManager.NormalFont.DrawString(g, scoreText, 111 * 4 - ResourceManager.NormalFont.MeasureStringWidth(scoreText), 9 * 4); + ResourceManager.NormalFont.DrawString(g, timeText, 87 * 4 - ResourceManager.NormalFont.MeasureStringWidth(timeText), 25 * 4); + ResourceManager.NormalFont.DrawString(g, ringsText, 87 * 4 - ResourceManager.NormalFont.MeasureStringWidth(ringsText), 41 * 4); + } + + public Rectangle Bounds + { + get + { + return mBounds; + } + set + { + mBounds = value; + } + } + } +} diff --git a/s2prototype/Program.cs b/s2prototype/Program.cs new file mode 100644 index 0000000..51fe9ce --- /dev/null +++ b/s2prototype/Program.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IntelOrca.Sonic +{ + static class Program + { + /// + /// The main entry point for the application. + /// + static void Main(string[] args) + { + for (int i = -128; i < 127; i++) { + int d0 = i + 32; + d0 &= 192; + Debug.WriteLine("{0} -> {1}", i, d0); + } + + // MakeSonicSS(); + // MakeTransparent("data\\graphics\\starpost.png", Color.FromArgb(255, 0, 255)); + // return; + + // LevelConverter conv = new LevelConverter(); + // conv.Convert(); + // return; + + using (SonicGame game = new SonicGame()) + game.Run(); + } + + static void MakeTransparent(string filename, Color transparent) + { + Bitmap img = (Bitmap)Bitmap.FromFile(filename); + img.MakeTransparent(transparent); + img.Save(filename + ".transparent.png"); + } + + static void MakeSonicSS() + { + List sprites = new List(); + + Color ttc = Color.FromArgb(0, 52, 76); + for (int i = 1; i <= 213; i++) { + string path = String.Format(@"C:\Users\Ted\Desktop\Sonic\sonic_s2_{0:000}.BMP", i); + Bitmap original = (Bitmap)Bitmap.FromFile(path); + original.MakeTransparent(ttc); + + Bitmap bmp = new Bitmap(64 * 4, 70 * 4); + System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp); + g.DrawImage(original, new Rectangle(0, 0, 64 * 4, 70 * 4), new Rectangle(0, 0, 64, 70), GraphicsUnit.Pixel); + g.Dispose(); + + original.Dispose(); + + sprites.Add(bmp); + } + + FileStream fs = new FileStream(@"C:\Users\Ted\Desktop\sonic.dat", FileMode.Create); + BinaryWriter bw = new BinaryWriter(fs); + bw.Write(sprites.Count); + for (int i = 0; i < sprites.Count; i++) { + int startPosition = (int)fs.Position; + bw.Write(0); + sprites[i].Save(fs, ImageFormat.Png); + int endPosition = (int)fs.Position; + + fs.Position = startPosition; + bw.Write(endPosition - startPosition - 4); + fs.Position = endPosition; + } + + fs.Close(); + } + } +} diff --git a/s2prototype/ResourceManager.cs b/s2prototype/ResourceManager.cs new file mode 100644 index 0000000..9eb9c5c --- /dev/null +++ b/s2prototype/ResourceManager.cs @@ -0,0 +1,149 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + static class ResourceManager + { + public static GraphicsDevice GraphicsDevice; + + public static Texture2D PlainTexture; + + public static Texture2D MarkerTexture; + public static Texture2D FontsTexture; + public static Texture2D[] SonicTextures; + public static Texture2D SpindashDustTexture; + public static Texture2D ShieldTexture; + public static Texture2D InvincibilityTexture; + public static Texture2D RingTexture; + public static Texture2D MonitorTexture; + public static Texture2D LogBridgeTexture; + public static Texture2D EHZPlatformTexture; + public static Texture2D MasherTexture; + public static Texture2D CoconutsTexture; + public static Texture2D BuzzerTexture; + public static Texture2D SpikesTexture; + public static Texture2D SpringTexture; + public static Texture2D ExplosionTexture; + public static Texture2D AnimalsTexture; + public static Texture2D SignpostTexture; + public static Texture2D StarpostTexture; + + public static List ChunkTexturesBack; + public static List ChunkTexturesFront; + + public static SoundEffect BadnikExplosionSound; + public static SoundEffect BrakeSound; + public static SoundEffect JumpSound; + public static SoundEffect RingSound; + public static SoundEffect SpinSound; + public static SoundEffect SpindashChargeSound; + public static SoundEffect SpindashReleaseSound; + public static SoundEffect ShieldSound; + public static SoundEffect RingScatterSound; + public static SoundEffect SpikesSound; + public static SoundEffect SpikesMoveSound; + public static SoundEffect BounceSound; + public static SoundEffect HurtSound; + public static SoundEffect SignpostSound; + public static SoundEffect StarpostSound; + + public static SoundEffect EHZMusic; + public static SoundEffect EHZSpeedMusic; + public static SoundEffect InvincibilityMusic; + public static SoundEffect LifeMusic; + + public static Font NormalFont; + + public static void LoadResources() + { + MarkerTexture = LoadTexture("data\\graphics\\marker.png"); + FontsTexture = LoadTexture("data\\graphics\\fonts.png"); + SonicTextures = LoadTextures("data\\graphics\\sonic.dat"); + SpindashDustTexture = LoadTexture("data\\graphics\\spindash_dust.png"); + ShieldTexture = LoadTexture("data\\graphics\\shield.png"); + InvincibilityTexture = LoadTexture("data\\graphics\\invincibility.png"); + RingTexture = LoadTexture("data\\graphics\\ring.png"); + MonitorTexture = LoadTexture("data\\graphics\\monitor.png"); + LogBridgeTexture = LoadTexture("data\\graphics\\logbridge.png"); + EHZPlatformTexture = LoadTexture("data\\graphics\\ehzplatform.png"); + MasherTexture = LoadTexture("data\\graphics\\masher.png"); + CoconutsTexture = LoadTexture("data\\graphics\\coconuts.png"); + BuzzerTexture = LoadTexture("data\\graphics\\buzzer.png"); + SpikesTexture = LoadTexture("data\\graphics\\spikes.png"); + SpringTexture = LoadTexture("data\\graphics\\spring.png"); + ExplosionTexture = LoadTexture("data\\graphics\\explosion.png"); + AnimalsTexture = LoadTexture("data\\graphics\\animals.png"); + SignpostTexture = LoadTexture("data\\graphics\\signpost.png"); + StarpostTexture = LoadTexture("data\\graphics\\starpost.png"); + + BadnikExplosionSound = LoadSound("data\\sounds\\badnik_explosion.wav"); + BrakeSound = LoadSound("data\\sounds\\brake.wav"); + JumpSound = LoadSound("data\\sounds\\jump.wav"); + RingSound = LoadSound("data\\sounds\\ring.wav"); + SpinSound = LoadSound("data\\sounds\\spin.wav"); + SpindashChargeSound = LoadSound("data\\sounds\\spindash_charge.wav"); + SpindashReleaseSound = LoadSound("data\\sounds\\spindash_release.wav"); + ShieldSound = LoadSound("data\\sounds\\shield.wav"); + RingScatterSound = LoadSound("data\\sounds\\ring_scatter.wav"); + SpikesSound = LoadSound("data\\sounds\\spikes.wav"); + SpikesMoveSound = LoadSound("data\\sounds\\spikes_move.wav"); + BounceSound = LoadSound("data\\sounds\\bounce.wav"); + HurtSound = LoadSound("data\\sounds\\hurt.wav"); + SignpostSound = LoadSound("data\\sounds\\signpost.wav"); + StarpostSound = LoadSound("data\\sounds\\starpost.wav"); + + EHZMusic = LoadSound("data\\music\\ehz.wav"); + EHZSpeedMusic = LoadSound("data\\music\\ehz_speed.wav"); + InvincibilityMusic = LoadSound("data\\music\\invincibility.wav"); + LifeMusic = LoadSound("data\\music\\life.wav"); + + NormalFont = new Font(FontsTexture); + } + + public static void FreeResources() + { + + } + + public static Texture2D CreateTexture(int width, int height, Color colour) + { + Texture2D texture = new Texture2D(GraphicsDevice, width, height); + Color[] bits = new Color[width * height]; + for (int i = 0; i < bits.Length; i++) + bits[i] = colour; + texture.SetData(bits); + return texture; + } + + private static Texture2D LoadTexture(string path) + { + using (Stream s = new FileStream(path, FileMode.Open)) + return Texture2D.FromStream(GraphicsDevice, s); + } + + private static Texture2D[] LoadTextures(string path) + { + Texture2D[] textures; + using (Stream s = new FileStream(path, FileMode.Open)) { + BinaryReader br = new BinaryReader(s); + textures = new Texture2D[br.ReadInt32()]; + for (int i = 0; i < textures.Length; i++) { + int length = br.ReadInt32(); + using (MemoryStream ms = new MemoryStream(br.ReadBytes(length))) + textures[i] = Texture2D.FromStream(GraphicsDevice, ms); + } + } + return textures; + } + + private static SoundEffect LoadSound(string path) + { + using (Stream s = new FileStream(path, FileMode.Open)) + return SoundEffect.FromStream(s); + } + } +} diff --git a/s2prototype/SonicGame.cs b/s2prototype/SonicGame.cs new file mode 100644 index 0000000..15dce8d --- /dev/null +++ b/s2prototype/SonicGame.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace IntelOrca.Sonic +{ + class SonicGame : Game + { + private GraphicsDeviceManager mGraphicsManager; + private SpriteBatch mSpriteBatch; + private GameScreen mCurrentScreen; + private ControllerState mControllerA; + + private Level mLevel; + private List mPlayers = new List(); + private bool mLevelStarted; + + // Step debugging + private bool mStepping; + private bool mStepped; + private bool mStepNow; + + private RenderTarget2D mDisplaySurface; + private int mDisplayWidth = 1920; + private int mDisplayHeight = 1080; + private int mDisplayScale = 4; + + private int mFrameRate = 0; + private int mFrameCounter = 0; + private TimeSpan mElapsedTime = TimeSpan.Zero; + + public SonicGame() + { + mGraphicsManager = new GraphicsDeviceManager(this); + mGraphicsManager.PreferredBackBufferWidth = 960; + mGraphicsManager.PreferredBackBufferHeight = 540; + mGraphicsManager.GraphicsProfile = GraphicsProfile.HiDef; + } + + protected override void LoadContent() + { + base.LoadContent(); + + // Create the display surface + mDisplaySurface = new RenderTarget2D(GraphicsDevice, mDisplayWidth, mDisplayHeight); + + // Create the sprite batch + mSpriteBatch = new SpriteBatch(GraphicsDevice); + + // Initialise the resource manager + ResourceManager.GraphicsDevice = GraphicsDevice; + ResourceManager.PlainTexture = ResourceManager.CreateTexture(mDisplayWidth, mDisplayHeight, Color.White); + ResourceManager.LoadResources(); + + StartEmeraldHillZoneAct1(); + } + + protected override bool BeginDraw() + { + GraphicsDevice.SetRenderTarget(mDisplaySurface); + return base.BeginDraw(); + } + + protected override void Draw(GameTime gameTime) + { + mFrameCounter++; + Window.Title = "s2prototype, fps: " + mFrameRate; + + GraphicsDevice.Clear(Color.Black); + mSpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied); + + Graphics graphics = new Graphics(mSpriteBatch); + if (mCurrentScreen != null) + mCurrentScreen.Draw(graphics); + + mSpriteBatch.End(); + } + + protected override void EndDraw() + { + GraphicsDevice.SetRenderTarget(null); + mSpriteBatch.Begin(); + mSpriteBatch.Draw(mDisplaySurface, GraphicsDevice.Viewport.Bounds, Color.White); + mSpriteBatch.End(); + base.EndDraw(); + } + + protected override void Update(GameTime gameTime) + { + mElapsedTime += gameTime.ElapsedGameTime; + if (mElapsedTime > TimeSpan.FromSeconds(1)) { + mElapsedTime -= TimeSpan.FromSeconds(1); + mFrameRate = mFrameCounter; + mFrameCounter = 0; + } + + + ControllerState state = new ControllerState(); + state.Set(1, Keyboard.GetState().IsKeyDown(Keys.Up)); + state.Set(2, Keyboard.GetState().IsKeyDown(Keys.Down)); + state.Set(4, Keyboard.GetState().IsKeyDown(Keys.Left)); + state.Set(8, Keyboard.GetState().IsKeyDown(Keys.Right)); + state.Set(32, Keyboard.GetState().IsKeyDown(Keys.A)); + state.Set(64, Keyboard.GetState().IsKeyDown(Keys.S)); + state.Set(128, Keyboard.GetState().IsKeyDown(Keys.D)); + mControllerA = state; + + // Step debugging + if (Keyboard.GetState().IsKeyDown(Keys.OemPipe)) { + if (!mStepping) { + mStepping = true; + mStepped = true; + } else if (!mStepped) { + mStepNow = true; + mStepped = true; + } + } else { + mStepped = false; + } + + if (Keyboard.GetState().IsKeyDown(Keys.Escape)) { + mStepping = false; + } + + if (mStepping && !mStepNow) + return; + mStepNow = false; + + if (mCurrentScreen != null) + mCurrentScreen.Update(); + + if (mLevelStarted) { + mLevel.Update(); + + foreach (Player player in mPlayers) + player.Update(); + + if (mPlayers[0].Status == PlayerStatus.Dead) + RestartLevel(); + } else if (mPlayers.Count > 0) { + if (mPlayers.All(p => p.Status != PlayerStatus.NotReady)) + mLevelStarted = true; + } + } + + private void StartEmeraldHillZoneAct1() + { + LoadingScreen loadingScreen = new LoadingScreen(this); + loadingScreen.LoadingRoutine = new Action(LoadEmeraldHillZoneAct1); + loadingScreen.FinishedRoutine = BeginLevel; + loadingScreen.Begin(); + + mCurrentScreen = loadingScreen; + } + + private void LoadEmeraldHillZoneAct1() + { + mLevelStarted = false; + mLevel = new Level(this); + mLevel.Load("data\\levels\\ehz"); + + ResourceManager.ChunkTexturesBack = new List(); + ResourceManager.ChunkTexturesFront = new List(); + for (int i = 0; i < mLevel.Landscape.Chunks.Count; i++) { + ResourceManager.ChunkTexturesBack.Add(GetChunkTexture(i, 0)); + ResourceManager.ChunkTexturesFront.Add(GetChunkTexture(i, 1)); + } + + GC.Collect(); + } + + private void RestartLevel() + { + mPlayers[0].MusicManager.StopAllMusic(); + + mLevel.Restart(); + mPlayers[0].Restart(); + BeginLevel(); + } + + private void BeginLevel() + { + mLevelStarted = false; + + Player mainPlayer; + if (mPlayers.Count == 0) + mPlayers.Add(new Player(this)); + + mainPlayer = mPlayers[0]; + mainPlayer.SetupCharacter(); + mainPlayer.MainCharacter.Player = mainPlayer; + mLevel.Objects.Add(mainPlayer.MainCharacter); + + mPlayers = new List(); + mPlayers.Add(mainPlayer); + + // Initial update to get started + mLevel.Update(); + + LevelScreen levelScreen = new LevelScreen(this); + mCurrentScreen = levelScreen; + + mPlayers[0].MusicManager.PlayMusic(ResourceManager.EHZMusic); + } + + private Texture2D GetChunkTexture(int id, int layer) + { + byte[] data; + if (layer == 0) + data = mLevel.Landscape.Chunks[id].BackLayer.Art.FrameData[0]; + else + data = mLevel.Landscape.Chunks[id].FrontLayer.Art.FrameData[0]; + + + Texture2D texture2 = new Texture2D(GraphicsDevice, 512, 512); + texture2.SetData(data); + + if (layer == 0) + mLevel.Landscape.Chunks[id].BackLayer.Art.FrameData = null; + else + mLevel.Landscape.Chunks[id].FrontLayer.Art.FrameData = null; + return texture2; + } + + public int DisplayWidth + { + get + { + return mDisplayWidth; + } + } + + public int DisplayHeight + { + get + { + return mDisplayHeight; + } + } + + public int DisplayScale + { + get + { + return mDisplayScale; + } + } + + public Level Level + { + get + { + return mLevel; + } + } + + public List Players + { + get + { + return mPlayers; + } + } + + public ControllerState ControllerA + { + get + { + return mControllerA; + } + } + } +} diff --git a/s2prototype/SonicMaths.cs b/s2prototype/SonicMaths.cs new file mode 100644 index 0000000..09dd55d --- /dev/null +++ b/s2prototype/SonicMaths.cs @@ -0,0 +1,126 @@ +using System; + +namespace IntelOrca.Sonic +{ + static class SonicMaths + { + public static readonly sbyte[] AngleData = new sbyte[] { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, + 0x1A, 0x1A, 0x1A, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, + 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, + 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x00 + }; + + public static readonly byte[] SineData = new byte[] { + 0x00, 0x00, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x19, 0x00, 0x1F, 0x00, 0x25, 0x00, 0x2B, + 0x00, 0x31, 0x00, 0x38, 0x00, 0x3E, 0x00, 0x44, 0x00, 0x4A, 0x00, 0x50, 0x00, 0x56, 0x00, 0x5C, + 0x00, 0x61, 0x00, 0x67, 0x00, 0x6D, 0x00, 0x73, 0x00, 0x78, 0x00, 0x7E, 0x00, 0x83, 0x00, 0x88, + 0x00, 0x8E, 0x00, 0x93, 0x00, 0x98, 0x00, 0x9D, 0x00, 0xA2, 0x00, 0xA7, 0x00, 0xAB, 0x00, 0xB0, + 0x00, 0xB5, 0x00, 0xB9, 0x00, 0xBD, 0x00, 0xC1, 0x00, 0xC5, 0x00, 0xC9, 0x00, 0xCD, 0x00, 0xD1, + 0x00, 0xD4, 0x00, 0xD8, 0x00, 0xDB, 0x00, 0xDE, 0x00, 0xE1, 0x00, 0xE4, 0x00, 0xE7, 0x00, 0xEA, + 0x00, 0xEC, 0x00, 0xEE, 0x00, 0xF1, 0x00, 0xF3, 0x00, 0xF4, 0x00, 0xF6, 0x00, 0xF8, 0x00, 0xF9, + 0x00, 0xFB, 0x00, 0xFC, 0x00, 0xFD, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x01, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFC, + 0x00, 0xFB, 0x00, 0xF9, 0x00, 0xF8, 0x00, 0xF6, 0x00, 0xF4, 0x00, 0xF3, 0x00, 0xF1, 0x00, 0xEE, + 0x00, 0xEC, 0x00, 0xEA, 0x00, 0xE7, 0x00, 0xE4, 0x00, 0xE1, 0x00, 0xDE, 0x00, 0xDB, 0x00, 0xD8, + 0x00, 0xD4, 0x00, 0xD1, 0x00, 0xCD, 0x00, 0xC9, 0x00, 0xC5, 0x00, 0xC1, 0x00, 0xBD, 0x00, 0xB9, + 0x00, 0xB5, 0x00, 0xB0, 0x00, 0xAB, 0x00, 0xA7, 0x00, 0xA2, 0x00, 0x9D, 0x00, 0x98, 0x00, 0x93, + 0x00, 0x8E, 0x00, 0x88, 0x00, 0x83, 0x00, 0x7E, 0x00, 0x78, 0x00, 0x73, 0x00, 0x6D, 0x00, 0x67, + 0x00, 0x61, 0x00, 0x5C, 0x00, 0x56, 0x00, 0x50, 0x00, 0x4A, 0x00, 0x44, 0x00, 0x3E, 0x00, 0x38, + 0x00, 0x31, 0x00, 0x2B, 0x00, 0x25, 0x00, 0x1F, 0x00, 0x19, 0x00, 0x12, 0x00, 0x0C, 0x00, 0x06, + 0x00, 0x00, 0xFF, 0xFA, 0xFF, 0xF4, 0xFF, 0xEE, 0xFF, 0xE7, 0xFF, 0xE1, 0xFF, 0xDB, 0xFF, 0xD5, + 0xFF, 0xCF, 0xFF, 0xC8, 0xFF, 0xC2, 0xFF, 0xBC, 0xFF, 0xB6, 0xFF, 0xB0, 0xFF, 0xAA, 0xFF, 0xA4, + 0xFF, 0x9F, 0xFF, 0x99, 0xFF, 0x93, 0xFF, 0x8B, 0xFF, 0x88, 0xFF, 0x82, 0xFF, 0x7D, 0xFF, 0x78, + 0xFF, 0x72, 0xFF, 0x6D, 0xFF, 0x68, 0xFF, 0x63, 0xFF, 0x5E, 0xFF, 0x59, 0xFF, 0x55, 0xFF, 0x50, + 0xFF, 0x4B, 0xFF, 0x47, 0xFF, 0x43, 0xFF, 0x3F, 0xFF, 0x3B, 0xFF, 0x37, 0xFF, 0x33, 0xFF, 0x2F, + 0xFF, 0x2C, 0xFF, 0x28, 0xFF, 0x25, 0xFF, 0x22, 0xFF, 0x1F, 0xFF, 0x1C, 0xFF, 0x19, 0xFF, 0x16, + 0xFF, 0x14, 0xFF, 0x12, 0xFF, 0x0F, 0xFF, 0x0D, 0xFF, 0x0C, 0xFF, 0x0A, 0xFF, 0x08, 0xFF, 0x07, + 0xFF, 0x05, 0xFF, 0x04, 0xFF, 0x03, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, + 0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x03, 0xFF, 0x04, + 0xFF, 0x05, 0xFF, 0x07, 0xFF, 0x08, 0xFF, 0x0A, 0xFF, 0x0C, 0xFF, 0x0D, 0xFF, 0x0F, 0xFF, 0x12, + 0xFF, 0x14, 0xFF, 0x16, 0xFF, 0x19, 0xFF, 0x1C, 0xFF, 0x1F, 0xFF, 0x22, 0xFF, 0x25, 0xFF, 0x28, + 0xFF, 0x2C, 0xFF, 0x2F, 0xFF, 0x33, 0xFF, 0x37, 0xFF, 0x3B, 0xFF, 0x3F, 0xFF, 0x43, 0xFF, 0x47, + 0xFF, 0x4B, 0xFF, 0x50, 0xFF, 0x55, 0xFF, 0x59, 0xFF, 0x5E, 0xFF, 0x63, 0xFF, 0x68, 0xFF, 0x6D, + 0xFF, 0x72, 0xFF, 0x78, 0xFF, 0x7D, 0xFF, 0x82, 0xFF, 0x88, 0xFF, 0x8B, 0xFF, 0x93, 0xFF, 0x99, + 0xFF, 0x9F, 0xFF, 0xA4, 0xFF, 0xAA, 0xFF, 0xB0, 0xFF, 0xB6, 0xFF, 0xBC, 0xFF, 0xC2, 0xFF, 0xC8, + 0xFF, 0xCF, 0xFF, 0xD5, 0xFF, 0xDB, 0xFF, 0xE1, 0xFF, 0xE7, 0xFF, 0xEE, 0xFF, 0xF4, 0xFF, 0xFA, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x19, 0x00, 0x1F, 0x00, 0x25, 0x00, 0x2B, + 0x00, 0x31, 0x00, 0x38, 0x00, 0x3E, 0x00, 0x44, 0x00, 0x4A, 0x00, 0x50, 0x00, 0x56, 0x00, 0x5C, + 0x00, 0x61, 0x00, 0x67, 0x00, 0x6D, 0x00, 0x73, 0x00, 0x78, 0x00, 0x7E, 0x00, 0x83, 0x00, 0x88, + 0x00, 0x8E, 0x00, 0x93, 0x00, 0x98, 0x00, 0x9D, 0x00, 0xA2, 0x00, 0xA7, 0x00, 0xAB, 0x00, 0xB0, + 0x00, 0xB5, 0x00, 0xB9, 0x00, 0xBD, 0x00, 0xC1, 0x00, 0xC5, 0x00, 0xC9, 0x00, 0xCD, 0x00, 0xD1, + 0x00, 0xD4, 0x00, 0xD8, 0x00, 0xDB, 0x00, 0xDE, 0x00, 0xE1, 0x00, 0xE4, 0x00, 0xE7, 0x00, 0xEA, + 0x00, 0xEC, 0x00, 0xEE, 0x00, 0xF1, 0x00, 0xF3, 0x00, 0xF4, 0x00, 0xF6, 0x00, 0xF8, 0x00, 0xF9, + 0x00, 0xFB, 0x00, 0xFC, 0x00, 0xFD, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF + }; + + private static Random RandomGenerator = new Random(); + + public static int Sin(int angle) + { + int offset = (angle & 0xFF) * 2; + byte a = SineData[offset]; + byte b = SineData[offset + 1]; + return (short)((a << 8) | b); + } + + public static int Cos(int angle) + { + int offset = (angle & 0xFF) * 2; + byte a = SineData[offset + 128]; + byte b = SineData[offset + 128 + 1]; + return (short)((a << 8) | b); + } + + /// + /// Subroutine to calculate arctangent of y / x + /// + public static int Atan2(int x, int y) + { + int angle, absx, absy; + + // If x and y are 0, return down angle + if (x == 0 && y == 0) + return 64; + + absx = Math.Abs(x); + absy = Math.Abs(y); + if (absy < absx) + angle = SonicMaths.AngleData[(absy * 8) / absx]; + else + angle = 64 - SonicMaths.AngleData[(absx * 8) / absy]; + + if (x <= 0) + angle = 128 - angle; + if (y <= 0) + angle = 256 - angle; + + if ((angle & 0x80) != 0) + return BitConverter.ToInt32(new byte[] { (byte)angle, 0xFF, 0xFF, 0xFF }, 0); + return angle; + } + + public static int Random(int maxValue) + { + return RandomGenerator.Next(maxValue); + } + + public static int Random(int minValue, int maxValue) + { + return RandomGenerator.Next(minValue, maxValue); + } + } +} diff --git a/s2prototype/TitleCard.cs b/s2prototype/TitleCard.cs new file mode 100644 index 0000000..942d0e0 --- /dev/null +++ b/s2prototype/TitleCard.cs @@ -0,0 +1,138 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace IntelOrca.Sonic +{ + class TitleCard + { + private SonicGame mGame; + private Level mLevel; + + private int mUpdateCount; + private bool mAllowStart; + private bool mFinished; + + private bool mShowGame; + + private int mBlueRectangleStatus; + private float mBlueRectanglePosition; + + private int mYellowRectangleStatus; + private float mYellowRectanglePosition; + + private int mRedRectangleStatus; + private float mRedRectanglePosition; + + private int mInitialisedWidth; + private int mInitialisedHeight; + private Texture2D mRectangleTexture; + + public TitleCard(SonicGame game, Level level) + { + mGame = game; + mLevel = level; + } + + public void Start() + { + mUpdateCount = 0; + mAllowStart = false; + mFinished = false; + } + + public void Update() + { + if (mInitialisedWidth == 0 || mInitialisedHeight == 0) + return; + + if (mUpdateCount > 60) + mBlueRectangleStatus = 1; + + if (mUpdateCount > 68) + mYellowRectangleStatus = 1; + + if (mUpdateCount > 78) + mRedRectangleStatus = 1; + + if (mUpdateCount > 170) + mRedRectangleStatus = 2; + + if (mUpdateCount > 180) { + mShowGame = true; + mYellowRectangleStatus = 2; + } + + if (mUpdateCount > 190) + mBlueRectangleStatus = 2; + + if (mUpdateCount > 195) + mAllowStart = true; + + if (mBlueRectangleStatus == 1 && mBlueRectanglePosition < 1.0f) + mBlueRectanglePosition += 0.08f; + else if (mBlueRectangleStatus == 2 && mBlueRectanglePosition > 0.0f) + mBlueRectanglePosition -= 0.1f; + + if (mYellowRectangleStatus == 1 && mYellowRectanglePosition < 1.0f) + mYellowRectanglePosition += 0.08f; + else if (mYellowRectangleStatus == 2 && mYellowRectanglePosition > 0.0f) + mYellowRectanglePosition -= 0.1f; + + if (mRedRectangleStatus == 1 && mRedRectanglePosition < 1.0f) + mRedRectanglePosition += 0.15f; + else if (mYellowRectangleStatus == 2 && mRedRectanglePosition > 0.0f) + mRedRectanglePosition -= 0.25f; + + mBlueRectanglePosition = Math.Min(1.0f, Math.Max(0.0f, mBlueRectanglePosition)); + mYellowRectanglePosition = Math.Min(1.0f, Math.Max(0.0f, mYellowRectanglePosition)); + mRedRectanglePosition = Math.Min(1.0f, Math.Max(0.0f, mRedRectanglePosition)); + + mUpdateCount++; + if (mUpdateCount > 240) + mFinished = true; + } + + public void Draw(Graphics g, Rectangle view) + { + Init(g, view.Width, view.Height); + + if (!mShowGame) + g.DrawImage(mRectangleTexture, new Vector2(0, 0), Color.Black); + g.DrawImage(mRectangleTexture, new Vector2(0, -mInitialisedHeight + (mBlueRectanglePosition * mInitialisedHeight)), new Color(36, 72, 216)); + g.DrawImage(mRectangleTexture, new Vector2(mInitialisedWidth - (mYellowRectanglePosition * mInitialisedWidth), mInitialisedHeight * 0.65f), new Color(252, 252, 0)); + g.DrawImage(mRectangleTexture, new Vector2(-mInitialisedWidth + (mRedRectanglePosition * mInitialisedWidth * 0.3f), 0), new Color(252, 0, 0)); + } + + private void Init(Graphics g, int width, int height) + { + if (mInitialisedWidth == width && mInitialisedHeight == height) + return; + + mInitialisedWidth = width; + mInitialisedHeight = height; + + mRectangleTexture = new Texture2D(g.GraphicsDevice, width, height); + Color[] bits = new Color[width * height]; + for (int i = 0; i < bits.Length; i++) + bits[i] = new Color(255, 255, 255); + mRectangleTexture.SetData(bits); + } + + public bool AllowStart + { + get + { + return mAllowStart; + } + } + + public bool IsFinished + { + get + { + return mFinished; + } + } + } +} diff --git a/s2prototype/s2prototype.csproj b/s2prototype/s2prototype.csproj new file mode 100644 index 0000000..963878d --- /dev/null +++ b/s2prototype/s2prototype.csproj @@ -0,0 +1,95 @@ + + + + + Debug + AnyCPU + {5E2B7BF4-6F4C-4DCC-93A8-DAA31AF01848} + WinExe + Properties + IntelOrca.Sonic + s2prototype + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file