PageRenderTime 30ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/Poing2/PlatformObject.cs

http://github.com/BCProgramming/BASeBlock
C# | 890 lines | 513 code | 245 blank | 132 comment | 104 complexity | feeba68b2d94c5057b825a4bdf46d0ea MD5 | raw file
Possible License(s): BSD-3-Clause
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Drawing;
  5. using System.Drawing.Imaging;
  6. using System.Linq;
  7. using System.Text;
  8. using BASeCamp.BASeBlock.Blocks;
  9. using BASeCamp.BASeBlock.Events;
  10. namespace BASeCamp.BASeBlock
  11. {
  12. //this interface is implemented by blocks that want to "see" when a PlatformObject touches them.
  13. public interface iPlatformBlockExtension
  14. {
  15. /// <summary>
  16. /// called when a PlatformObject starts to "track" the block. This means it is standing on it, typically.
  17. /// </summary>
  18. /// <param name="gstate"></param>
  19. /// <param name="objStand"></param>
  20. /// <param name="standingon">true if the block is now tracking. false otherwise</param>
  21. void Standon(BCBlockGameState gstate, PlatformObject objStand,bool standingon);
  22. }
  23. //generalized class, used to represent any object that acts like a "platform" character; that is, they land on tops of blocks, fall, jump, etc.
  24. public abstract class PlatformObject : GameEnemy, iLocatable, IMovingObject, iBumpable
  25. {
  26. protected struct PlatformTrackingData
  27. {
  28. /// <summary>
  29. /// block we are tracking as a platform.
  30. /// </summary>
  31. public Block TrackBlock;
  32. /// <summary>
  33. /// position of this tracked object at our last frame.
  34. /// </summary>
  35. public PointF PreviousFramePosition;
  36. }
  37. protected bool TrackPlatform = true; //true to track when blocks (platforms) move beneath the character.
  38. protected PlatformTrackingData ptd = new PlatformTrackingData();
  39. protected bool Nocollisions = false;
  40. private PointF _Velocity;
  41. public PointF Velocity { get { return _Velocity; } set { _Velocity = value; } }
  42. protected float FallSpeedLimit = 8;
  43. public static PointF DefaultGravityEffect = new PointF(0, 60f);
  44. public static PointF GetScaledGravityEffect(float factor)
  45. {
  46. return new PointF(DefaultGravityEffect.X * factor, DefaultGravityEffect.Y * factor);
  47. }
  48. protected PointF _GravityEffect = DefaultGravityEffect;
  49. public PointF GravityEffect { get { return _GravityEffect; } set { _GravityEffect = value; } }
  50. /// <summary>
  51. /// alias for DrawSize
  52. /// </summary>
  53. public SizeF useSize { get { return base.DrawSize; } set { base.DrawSize = useSize; } }
  54. public PlatformObject(PointF pPosition, PointF pVelocity, Dictionary<String, String[]> pStateFrameData, int pFrameDelay, ImageAttributes puseattributes)
  55. : base(pPosition, pStateFrameData, pFrameDelay, puseattributes)
  56. {
  57. Location = pPosition;
  58. Velocity = pVelocity;
  59. OnDeath += PlatformObject_OnDeath;
  60. }
  61. void PlatformObject_OnDeath(Object sender,EnemyDeathEventArgs e)
  62. {
  63. //when we die make sure that we stop tracking...
  64. if (ptd.TrackBlock is iPlatformBlockExtension)
  65. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(e.StateObject, this, false);
  66. }
  67. public virtual void BulletHit(BCBlockGameState gstate, Projectile hitbullet)
  68. {
  69. }
  70. protected virtual void standingon(BCBlockGameState gamestate, List<Block> standingon, Block Mainblock)
  71. {
  72. }
  73. public virtual void Bump(Block bumpedby)
  74. {
  75. onground = false;
  76. ongroundlastframe = false;
  77. Debug.Print(this.GetType().Name + " bumped by " + bumpedby.GetType().Name);
  78. Velocity = new PointF(Velocity.X, -5);
  79. Location = new PointF(Location.X, Location.Y - 5);
  80. }
  81. public virtual void TouchLeft(BCBlockGameState gamestate, List<Block> touched, Block mainblock)
  82. {
  83. Velocity = new PointF(Velocity.X * -1, Velocity.Y);
  84. }
  85. //TouchPaddle(gamestate,ref AddObjects,ref removeobjects,gamestate.PlayerPaddle)
  86. /// <summary>
  87. /// called when the object touches the paddle.
  88. /// </summary>
  89. /// <param name="gamestate"></param>
  90. /// <param name="PlayerPaddle"></param>
  91. /// <returns>True to proceed with "stopping" any downward velocity. False otherwise.</returns>
  92. public virtual bool TouchPaddle(BCBlockGameState gamestate, Paddle PlayerPaddle)
  93. {
  94. return true;
  95. if (ptd.TrackBlock is iPlatformBlockExtension)
  96. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, false);
  97. ptd.TrackBlock = PlayerPaddle;
  98. ptd.PreviousFramePosition = PlayerPaddle.Location;
  99. if (ptd.TrackBlock is iPlatformBlockExtension)
  100. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, true);
  101. return true;
  102. }
  103. public virtual void TouchRight(BCBlockGameState gamestate, List<Block> touched, Block mainblock)
  104. {
  105. Velocity = new PointF(Velocity.X * -1, Velocity.Y);
  106. }
  107. public virtual void TouchTop(BCBlockGameState gamestate, List<Block> touched, Block mainblock)
  108. {
  109. //
  110. BCBlockGameState.Block_Hit(gamestate, mainblock);
  111. }
  112. /// <summary>
  113. /// overridden in child classes when the object is being killed.
  114. /// </summary>
  115. /// <param name="gstate">gamestate</param>
  116. /// <returns>True to prevent from dying; false otherwise. Note that true should only be returned when this gameobject needs to stick around
  117. /// for the death. (to animate falling off the screen, for example)</returns>
  118. public virtual bool Die(BCBlockGameState gstate)
  119. {
  120. if (ptd.TrackBlock is iPlatformBlockExtension)
  121. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gstate, this, false);
  122. gstate.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup(() => {
  123. gstate.GameObjects.AddLast(new MarioDeathStyleObject(this));
  124. BCBlockGameState.Soundman.PlaySound("shelldie");
  125. }));
  126. return false;
  127. }
  128. /// <summary>
  129. /// called when this object hits the side of a block.
  130. /// </summary>
  131. /// <param name="gamestate"></param>
  132. /// <param name="hitblocks"></param>
  133. protected virtual void hitside(BCBlockGameState gamestate, List<Block> hitblocks)
  134. {
  135. }
  136. public sealed override int GetScoreValue()
  137. {
  138. return 0;
  139. }
  140. protected virtual void TouchLevelSide(BCBlockGameState gamestate, ref List<GameObject> Addobjects, ref List<GameObject> removeobjects, bool LeftSide)
  141. {
  142. //leftside is true if we touched the left side. False if we hit the right.
  143. //do nothing and the object will go off the side and be destroyed.
  144. }
  145. public enum CollideTypeConstants
  146. {
  147. Collide_Nothing, //no collision
  148. Collide_Bounce, //will collide but the two objects will go their own ways. calls PlatformObject's static "bounce" method.
  149. Collide_Passive, //will collide, but won't destroy the 'attacker'
  150. Collide_Aggressive, //will collide, destroying both objects.
  151. }
  152. protected void DoCollide(BCBlockGameState gamestate, PlatformObject otherenemy)
  153. {
  154. PlatformObject platformed = otherenemy;
  155. PlatformObject checkobject = platformed;
  156. CollideTypeConstants ctc = platformed.Collide(gamestate, this);
  157. if (ctc == CollideTypeConstants.Collide_Aggressive)
  158. {
  159. //kill us both...
  160. BCBlockGameState.Soundman.PlaySound("shelldie");
  161. Block.AddScore(gamestate, 25, Location);
  162. //GameObject ourdeath = new MarioDeathStyleObject(this);
  163. if (!Die(gamestate))
  164. {
  165. gamestate.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup(() =>
  166. gamestate.GameObjects.Remove(this)));
  167. }
  168. if (!(checkobject as PlatformObject).Die(gamestate))
  169. {
  170. gamestate.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup(() =>
  171. gamestate.GameObjects.Remove(checkobject)));
  172. }
  173. }
  174. else if (ctc == CollideTypeConstants.Collide_Passive)
  175. {
  176. //destroy the other object, but not us.
  177. BCBlockGameState.Soundman.PlaySound("shelldie");
  178. Block.AddScore(gamestate, 30, Location);
  179. //make them start at the same speed (horizontally) as the "shell" (or us, rather)
  180. checkobject.Velocity = new PointF(Velocity.X, checkobject.Velocity.Y);
  181. if (!checkobject.Die(gamestate))
  182. {
  183. gamestate.Defer(() => gamestate.GameObjects.Remove(checkobject));
  184. }
  185. }
  186. else if (ctc == CollideTypeConstants.Collide_Bounce)
  187. {
  188. PlatformObject.Bounce(gamestate, this, checkobject as PlatformObject);
  189. }
  190. }
  191. /// <summary>
  192. /// called by some implementations of PlatformObject; for example, Shell, to determine if it can "kill" the object.
  193. /// </summary>
  194. /// <param name="gstate"></param>
  195. /// <param name="otherobject"></param>
  196. /// <returns></returns>
  197. public virtual CollideTypeConstants Collide(BCBlockGameState gstate, PlatformObject otherobject)
  198. {
  199. return CollideTypeConstants.Collide_Aggressive;
  200. }
  201. public static void Bounce(BCBlockGameState gstate, PlatformObject Enemy1, PlatformObject Enemy2)
  202. {
  203. //task: we want to "bounce" the two enemies off one another.
  204. //knowns: we know they are touching
  205. //first change the speeds if they aren't moving, 0 gums up the works.
  206. // if(Enemy1.Velocity.Y==0) Enemy1.Velocity = new PointF(0.001f,0);
  207. // if (Enemy2.Velocity.Y == 0) Enemy2.Velocity = new PointF(0.001f, 0);
  208. //however they need to be moving towards one another to count. First get the midpoint between the two.
  209. //get the difference between their X location and the midpoint. the Sign of their speeds
  210. //must be the opposite of the Math.Sign of their difference.
  211. PointF midpoint = new PointF();
  212. //special consideration: objects that don't support iMovingObject are assumed to be stationary, so we don't want to move those.
  213. //so we adjust "midpoint" so it isn't reall a midpoint at all but rather a position that will
  214. //result in the later code in this routine "moving" the stationary object where it already is.
  215. if ((Enemy1 is IMovingObject) && (Enemy2 is IMovingObject))
  216. {
  217. midpoint = BCBlockGameState.MidPoint(Enemy1.Location, Enemy1.Location);
  218. }
  219. else if (Enemy1 is IMovingObject)
  220. {
  221. midpoint = Enemy2.Location;
  222. }
  223. else if (Enemy2 is IMovingObject)
  224. {
  225. midpoint = new PointF(Enemy1.Location.X + Enemy1.DrawSize.Width, Enemy1.Location.Y);
  226. }
  227. float e1diff = Enemy1.Location.X - midpoint.X;
  228. float e2diff = Enemy2.Location.X - midpoint.X;
  229. if (Math.Sign(Enemy1.Velocity.X) != Math.Sign(e1diff)
  230. && Math.Sign(Enemy2.Velocity.X) != Math.Sign(e2diff))
  231. {
  232. //good! they are headed towards one another.
  233. //we want to adjust their locations. First sort them so we have the one on the left, and the one on the right.
  234. SortedList<float, GameEnemy> sortlocation = new SortedList<float, GameEnemy>();
  235. sortlocation.Add(Enemy1.Location.X, Enemy1);
  236. sortlocation.Add(Enemy2.Location.X, Enemy2);
  237. GameEnemy LeftEnemy = sortlocation.First().Value;
  238. GameEnemy RightEnemy = sortlocation.Last().Value;
  239. //move the left enemy to the midpoint - it's width.
  240. //move the right enemy to the midpoint.
  241. LeftEnemy.Location = new PointF(midpoint.X - LeftEnemy.DrawSize.Width, LeftEnemy.Location.Y);
  242. RightEnemy.Location = new PointF(midpoint.X, LeftEnemy.Location.Y);
  243. //make their speeds the same Math.Sign as the calculated e1diff and e2diff for each...
  244. //at least, for those that support iMovingObject.
  245. if (LeftEnemy is IMovingObject)
  246. {
  247. IMovingObject castleft = (IMovingObject)LeftEnemy;
  248. castleft.Velocity = new PointF(Math.Abs(castleft.Velocity.X) * Math.Sign(e1diff), castleft.Velocity.Y);
  249. }
  250. if (RightEnemy is IMovingObject)
  251. {
  252. IMovingObject castright = (IMovingObject)RightEnemy;
  253. castright.Velocity = new PointF(Math.Abs(castright.Velocity.Y) * Math.Sign(e2diff), castright.Velocity.Y);
  254. }
  255. }
  256. }
  257. protected bool Killed = false;
  258. public void Kill()
  259. {
  260. Killed = true;
  261. }
  262. bool TrackingBlockPositionChanged = false;
  263. protected bool ongroundlastframe = false;
  264. protected bool onground = false;
  265. public override bool PerformFrame(BCBlockGameState gamestate)
  266. {
  267. try
  268. {
  269. gamestate.Blocks.AddLast(gamestate.PlayerPaddle);
  270. if (Killed)
  271. {
  272. Die(gamestate);
  273. return true;
  274. }
  275. //our calculations need not our "real" velocity, but our "frame" FrameVel.
  276. PointF FrameVel = new PointF(Velocity.X * gamestate.GetMultiplier(), Velocity.Y * gamestate.GetMultiplier());
  277. onground = false;
  278. //each frame, we check for collisions.
  279. //Debug.Print("Shell, location:" + Location + " Vel:" + FrameVel);
  280. //first we need to see if we are "on the ground".
  281. //we do this by extending our rect downwards a third. if that returns true when we hit test for blocks, than we are on the ground.
  282. //the "test" rectangle's are half the size of the object itself and extend from one eighth within the object, to the bottom of the object
  283. //plus our Y frame speed.
  284. Rectangle ourrectangle = new Rectangle((int)Location.X, (int)Location.Y, (int)useSize.Width, (int)useSize.Width);
  285. Polygon ourrectpoly = new Polygon(ourrectangle);
  286. if (!Nocollisions)
  287. {
  288. Func<float, float, int> PosCalcFunction = ((Loc, size) => ((int)(Loc + size * .875f)));
  289. Func<float, int> SizeCalcFunction = ((size) => ((int)size / 4));
  290. //iterate through all Blocks.
  291. #if !oldplatform
  292. var result =
  293. (from p in gamestate.Blocks
  294. let y = GeometryHelper.PolygonCollision(ourrectpoly,p.GetPoly(),Velocity)
  295. where y.Intersect|y.WillIntersect
  296. orderby y.MinimumTranslationVector.Magnitude ascending
  297. select new{Poly=p,CollisionResult=y}).FirstOrDefault() ;
  298. if (result != null)
  299. {
  300. //result is now the anon type for the nearest Polygon that we touch.
  301. //now we base whether we are standing on, being blocked by, or touched our head to the object
  302. //based on the adjustment vector.
  303. //note that the adjustment vector indicates how we have to move the
  304. //PlatformObject.
  305. if (result.CollisionResult.Intersect)
  306. {
  307. //are we touching it with our "feet"?
  308. if (result.CollisionResult.MinimumTranslationVector.Y <= 0)
  309. {
  310. //if the adjustment moves us upward, than we are touching from above, so we are considered to be "on the ground"
  311. onground = true;
  312. Velocity = new PointF(Velocity.X, 0);
  313. if (TrackPlatform)
  314. {
  315. if (ptd.TrackBlock != result.Poly)
  316. {
  317. if (ptd.TrackBlock is iPlatformBlockExtension)
  318. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, false);
  319. //if (ptd.TrackBlock != null) ptd.TrackBlock.OnBlockRectangleChange -= TrackBlock_OnBlockRectangleChange;
  320. ptd.TrackBlock = result.Poly;
  321. if (ptd.TrackBlock is iPlatformBlockExtension)
  322. {
  323. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, true);
  324. }
  325. //ptd.TrackBlock.OnBlockRectangleChange += new Action<RectangleF>(TrackBlock_OnBlockRectangleChange);
  326. ptd.PreviousFramePosition = result.Poly.Location;
  327. }
  328. }
  329. Location = new PointF(Location.X, Location.Y + GetGroundOffset());
  330. standingon(gamestate, ref AddObjects, ref removeobjects, new Block[] { result.Poly }.ToList(), result.Poly);
  331. }
  332. else if (result.CollisionResult.MinimumTranslationVector.Y > 0)
  333. {
  334. //Velocity = new PointF(Velocity.X, Math.Abs(Velocity.Y));
  335. //touching from below.
  336. TouchTop(gamestate, ref AddObjects, ref removeobjects, new Block[] { result.Poly }.ToList(), result.Poly);
  337. }
  338. if (Math.Abs(result.CollisionResult.MinimumTranslationVector.X) > 0.5f)
  339. {
  340. if (result.CollisionResult.MinimumTranslationVector.X < 0)
  341. TouchRight(gamestate, ref AddObjects, ref removeobjects, new Block[] { result.Poly }.ToList(), result.Poly);
  342. else
  343. TouchLeft(gamestate, ref AddObjects, ref removeobjects, new Block[] { result.Poly }.ToList(), result.Poly);
  344. }
  345. }
  346. foreach (Block iterateblock in gamestate.Blocks)
  347. {
  348. var pcr = GeometryHelper.PolygonCollision(ourrectpoly, iterateblock.GetPoly(), FrameVel);
  349. }
  350. Location = new PointF(Location.X + result.CollisionResult.MinimumTranslationVector.X, Location.Y + result.CollisionResult.MinimumTranslationVector.Y);
  351. }
  352. if (Velocity.Y < FallSpeedLimit && !(onground || ongroundlastframe))
  353. {
  354. float GravityXEffect = gamestate.ScaleValue(GravityEffect.X);
  355. float GravityYEffect = gamestate.ScaleValue(GravityEffect.Y);
  356. Velocity = new PointF(Velocity.X + (GravityXEffect), Velocity.Y + (GravityYEffect));
  357. }
  358. BCBlockGameState.IncrementLocation(gamestate, ref _Location, Velocity);
  359. return (!gamestate.GameArea.IntersectsWith(ourrectangle)) || base.PerformFrame(gamestate, ref AddObjects, ref removeobjects);
  360. #endif
  361. //old code here...
  362. /*
  363. // Rectangle TopTestRect = new RectangleF(LocationX,(Location.Y
  364. //Rectangle LeftTestRect = new Rectangle((int)(Location.X - (useSize.Width * .125f)),
  365. // (int)Location.Y, SizeCalcFunction(useSize.Width), (int)(useSize.Height * 0.5));
  366. float tenpercentw = (useSize.Width * .1f);
  367. Rectangle TopTestRect = new Rectangle((int)Location.X + (int)(tenpercentw), (int)(Location.Y - (useSize.Height * .125f)),
  368. (int)(useSize.Width) - (int)(tenpercentw * 2), SizeCalcFunction(useSize.Height));
  369. RectangleF GroundTestRect = new RectangleF(Location.X + (int)(useSize.Width * .125), (Location.Y + (useSize.Height - (useSize.Height * .125f))),
  370. useSize.Width - ((int)(useSize.Width * .125)), SizeCalcFunction(useSize.Height) / 4);
  371. //Debug.Print(Location.ToString());
  372. List<Block> groundblocks = BCBlockGameState.Block_HitTest(gamestate.Blocks, GroundTestRect, false);
  373. */
  374. float tenpercentw = (useSize.Width * .1f);
  375. //float topsans = Math.Min(Location.Y - useSize.Height * 0.125f, Location.Y - FrameVel.Y);
  376. //float bottomadd = Math.Max(Location.Y + useSize.Height * 0.125f, Location.Y + FrameVel.Y);
  377. //test rectangle data for hitting stuff above.
  378. int TopLeft = (int)(Location.X + (tenpercentw));
  379. int TopRight = (int)(Location.X + useSize.Width - tenpercentw);
  380. int TopTop = (int)Math.Min(Location.Y - (useSize.Height * 0.125f), Location.Y + FrameVel.Y);
  381. int TopBottom = (int)(Location.Y + Math.Max(useSize.Height * 0.125f,FrameVel.Y));
  382. Rectangle TopTestRect = new Rectangle(TopLeft, TopTop, TopRight - TopLeft, TopBottom - TopTop);
  383. //test rectangle data for the ground.
  384. int BottomLeft = TopLeft;
  385. int BottomRight = TopRight;
  386. int BottomTop = (int)(Location.Y + useSize.Height - (useSize.Height * 0.125f));
  387. int BottomBottom = (int)(Math.Max(Location.Y + useSize.Height + (useSize.Height * 0.125f), Location.Y + FrameVel.Y));
  388. RectangleF GroundTestRect = new RectangleF(BottomLeft, BottomTop, BottomRight - BottomLeft, BottomBottom - BottomTop);
  389. List<Block> groundblocks = null;
  390. if (Math.Sign(Velocity.Y) != -1)
  391. groundblocks = BCBlockGameState.Block_HitTest(gamestate.Blocks, GroundTestRect, false);
  392. else
  393. groundblocks = new List<Block>();
  394. // Debug.Print(onground?"onground":"!onground");
  395. //get blocks in proper direction, adjust our position and "hit" the block.
  396. List<Block> hitblocks = null;
  397. if (FrameVel.Y < 0)
  398. {
  399. RectangleF rf = new RectangleF(TopTestRect.Left, TopTestRect.Top, TopTestRect.Width, TopTestRect.Height);
  400. hitblocks = BCBlockGameState.Block_HitTest(gamestate.Blocks, rf, false);
  401. if (hitblocks.Count > 0)
  402. {
  403. float currminimum = float.MaxValue;
  404. Block foundbottommost = null;
  405. foreach (Block findbottommost in hitblocks)
  406. {
  407. if (foundbottommost == null || findbottommost.BlockRectangle.Bottom > foundbottommost.BlockRectangle.Bottom)
  408. foundbottommost = findbottommost;
  409. }
  410. Location = new PointF(Location.X, foundbottommost.BlockRectangle.Bottom+1);
  411. TouchTop(gamestate, hitblocks, foundbottommost);
  412. }
  413. }
  414. else if (FrameVel.Y > 0)
  415. {
  416. //check for paddle contact.
  417. if (gamestate.PlayerPaddle != null)
  418. {
  419. Rectangle testrect = gamestate.PlayerPaddle.Getrect();
  420. if (GroundTestRect.IntersectsWith(testrect))
  421. {
  422. if (TouchPaddle(gamestate, gamestate.PlayerPaddle))
  423. Location = new PointF(Location.X, testrect.Top - useSize.Height + GetGroundOffset());
  424. onground = true;
  425. ongroundlastframe = true;
  426. }
  427. }
  428. }
  429. if (FrameVel.X > 0)
  430. {
  431. Rectangle RightTestRect = new Rectangle(PosCalcFunction(Location.X, useSize.Width), (int)Location.Y,
  432. SizeCalcFunction(useSize.Width), (int)(useSize.Height * 0.5f));
  433. //moving right.
  434. RectangleF rf = new RectangleF(RightTestRect.Left, RightTestRect.Top, RightTestRect.Width, RightTestRect.Height);
  435. hitblocks = BCBlockGameState.Block_HitTest(gamestate.Blocks, rf, false);
  436. if (hitblocks.Count > 0)
  437. {
  438. //move to the left of the leftmost block...
  439. float currminimum = float.MaxValue;
  440. Block foundleftmost = null;
  441. foreach (Block findleftmost in hitblocks)
  442. {
  443. if (foundleftmost == null || findleftmost.BlockRectangle.Left < foundleftmost.BlockRectangle.Left)
  444. foundleftmost = findleftmost;
  445. }
  446. Location = new PointF(foundleftmost.BlockRectangle.Left - useSize.Width, Location.Y);
  447. TouchRight(gamestate, hitblocks, foundleftmost);
  448. }
  449. }
  450. else if (FrameVel.X < 0)
  451. {
  452. Rectangle LeftTestRect = new Rectangle((int)(Location.X - (useSize.Width * .125f)),
  453. (int)Location.Y, SizeCalcFunction(useSize.Width), (int)(useSize.Height * 0.5));
  454. RectangleF rf = new RectangleF(LeftTestRect.Left, LeftTestRect.Top, LeftTestRect.Width, LeftTestRect.Height);
  455. hitblocks = BCBlockGameState.Block_HitTest(gamestate.Blocks, rf, false);
  456. if (hitblocks.Count > 0)
  457. {
  458. float currminimum = float.MaxValue;
  459. Block foundrightmost = null;
  460. foreach (Block findrightmost in hitblocks)
  461. {
  462. if (foundrightmost == null || findrightmost.BlockRectangle.Right > foundrightmost.BlockRectangle.Right)
  463. foundrightmost = findrightmost;
  464. }
  465. Location = new PointF(foundrightmost.BlockRectangle.Right, Location.Y);
  466. TouchLeft(gamestate, hitblocks, foundrightmost);
  467. }
  468. }
  469. onground = groundblocks.Any();
  470. if (onground)
  471. {
  472. //acquire the highest block.
  473. Block currlowest = null;
  474. foreach (Block loopblock in groundblocks)
  475. {
  476. if (currlowest == null || loopblock.BlockRectangle.Top > currlowest.BlockRectangle.Top)
  477. currlowest = loopblock;
  478. }
  479. if (currlowest != null)
  480. {
  481. if (currlowest.BlockRectangle.Top > Location.Y)
  482. Location = new PointF(Location.X, currlowest.BlockRectangle.Top - useSize.Height + GetGroundOffset());
  483. if (TrackPlatform)
  484. {
  485. if (ptd.TrackBlock != currlowest )
  486. {
  487. if (ptd.TrackBlock is iPlatformBlockExtension)
  488. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, false);
  489. //if (ptd.TrackBlock != null) ptd.TrackBlock.OnBlockRectangleChange -= TrackBlock_OnBlockRectangleChange;
  490. ptd.TrackBlock = currlowest;
  491. if (ptd.TrackBlock is iPlatformBlockExtension)
  492. {
  493. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, true);
  494. }
  495. //ptd.TrackBlock.OnBlockRectangleChange += new Action<RectangleF>(TrackBlock_OnBlockRectangleChange);
  496. ptd.PreviousFramePosition = currlowest.Location;
  497. }
  498. }
  499. if (currlowest is Paddle)
  500. {
  501. Debug.Print("paddle");
  502. }
  503. standingon(gamestate, groundblocks, currlowest);
  504. }
  505. }
  506. else
  507. {
  508. //if we aren't on the ground, clear the trackblock data.
  509. //if (ptd.TrackBlock != null) ptd.TrackBlock.OnBlockRectangleChange -= TrackBlock_OnBlockRectangleChange;
  510. if (ptd.TrackBlock != null)
  511. {
  512. if (ptd.TrackBlock is iPlatformBlockExtension)
  513. ((iPlatformBlockExtension)ptd.TrackBlock).Standon(gamestate, this, false);
  514. Debug.Print("Stopped Tracking a block:" + ptd.TrackBlock.GetType().Name);
  515. }
  516. ptd.TrackBlock = null;
  517. }
  518. if (onground || ongroundlastframe)
  519. {
  520. Velocity = new PointF(Velocity.X, 0);
  521. }
  522. if ((hitblocks ?? new List<Block>()).Count > 0)
  523. {
  524. hitside(gamestate, hitblocks);
  525. }
  526. }
  527. if (Velocity.Y < FallSpeedLimit && !(onground || ongroundlastframe))
  528. {
  529. float GravityXEffect = gamestate.ScaleValue(GravityEffect.X);
  530. float GravityYEffect = gamestate.ScaleValue(GravityEffect.Y);
  531. Velocity = new PointF(Velocity.X + (GravityXEffect), Velocity.Y + (GravityYEffect));
  532. }
  533. BCBlockGameState.IncrementLocation(gamestate, ref _Location, Velocity);
  534. if (TrackPlatform)
  535. {
  536. //if we are tracking platforms, AND we are currently on one...
  537. if (ptd.TrackBlock != null)
  538. {
  539. if (ptd.TrackBlock.Location != ptd.PreviousFramePosition)
  540. {
  541. PointF thediff = new PointF(ptd.TrackBlock.Location.X - ptd.PreviousFramePosition.X,
  542. ptd.TrackBlock.Location.Y - ptd.PreviousFramePosition.Y);
  543. //add this diff to our location.
  544. Debug.Print("The difference is " + thediff);
  545. _Location = new PointF(_Location.X + thediff.X, _Location.Y + thediff.Y);
  546. ptd.PreviousFramePosition = ptd.TrackBlock.Location;
  547. }
  548. }
  549. }
  550. ongroundlastframe = onground;
  551. //checks to see if it hit the side of the level.
  552. Rectangle LeftCheck = new Rectangle(-20, 0, 20, gamestate.GameArea.Height);
  553. Rectangle RightCheck = new Rectangle(gamestate.GameArea.Width, 0, 20, gamestate.GameArea.Height);
  554. return (!gamestate.GameArea.IntersectsWith(ourrectangle)) || base.PerformFrame(gamestate);
  555. }
  556. finally
  557. {
  558. gamestate.Blocks.Remove(gamestate.PlayerPaddle);
  559. }
  560. }
  561. void TrackBlock_OnBlockRectangleChange(RectangleF obj)
  562. {
  563. TrackingBlockPositionChanged = true;
  564. }
  565. public virtual float GetGroundOffset()
  566. {
  567. return 2;
  568. }
  569. }
  570. [NoKillIncrement()]
  571. //Platform objects will not increment the Statistics Kill counter.
  572. public abstract class PlatformEnemy : PlatformObject
  573. {
  574. protected PlatformEnemy(PointF pPosition, PointF pVelocity, Dictionary<string, string[]> pStateFrameData, int pFrameDelay, ImageAttributes puseattributes)
  575. : base(pPosition, pVelocity, pStateFrameData, pFrameDelay, puseattributes)
  576. { }
  577. }
  578. //Shell Object. Designed to emulate the action of Mario's Koopa shells.
  579. public class Shell : PlatformEnemy
  580. {
  581. //how it works:
  582. //the shell will need to start with a speed.
  583. public Shell(PointF pPosition)
  584. : this(pPosition, new SizeF(16, 16))
  585. {
  586. //GravityEffect = new PointF(0, 11f);
  587. GravityEffect = GetScaledGravityEffect(0.8f);
  588. }
  589. public Shell(PointF pPosition, SizeF pusesize)
  590. : this(pPosition, new Nullable<SizeF>(pusesize))
  591. { }
  592. public Shell(PointF pPosition, SizeF? pusesize)
  593. : base(pPosition, new PointF(4, 0), null, 4, null)
  594. {
  595. StateFrameImageKeys = new Dictionary<string, string[]>();
  596. /*
  597. StateFrameImageKeys.Add("NORMAL",new string[]{"SHELL"}); //add extra frames here to animate the shell. (or make it possible to stop....)
  598. StateFrameIndex = new Dictionary<string, int>();
  599. StateFrameIndex.Add("NORMAL", 0);
  600. FrameDelayTimes = new Dictionary<string, int[]>();
  601. FrameDelayTimes.Add("NORMAL", new int[] { 25 });
  602. EnemyState = "NORMAL";
  603. * */
  604. StateFrameImageKeys.Add("NORMAL", new string[] { "SHLL1", "SHLL2", "SHLL3", "FLIPX:SHLL2" }); //add extra frames here to animate the shell. (or make it possible to stop....)
  605. StateFrameImageKeys.Add("STOPPED", new String[] { "SHELL1" });
  606. StateFrameIndex = new Dictionary<string, int>();
  607. StateFrameIndex.Add("NORMAL", 0);
  608. StateFrameIndex.Add("STOPPED", 0);
  609. FrameDelayTimes = new Dictionary<string, int[]>();
  610. FrameDelayTimes.Add("NORMAL", new int[] { 2, 2, 2, 2 });
  611. FrameDelayTimes.Add("STOPPED", new int[] { 2 });
  612. EnemyAction = "NORMAL";
  613. if (pusesize != null) useSize = pusesize.Value;
  614. }
  615. protected override void hitside(BCBlockGameState gamestate, List<Block> hitblocks)
  616. {
  617. Block smackit = hitblocks.First();
  618. BCBlockGameState.Block_Hit(gamestate, smackit, Velocity); //smack it with the shell's velocity.
  619. gamestate.Forcerefresh = true;
  620. }
  621. public override bool PerformFrame(BCBlockGameState gamestate)
  622. {
  623. //additional checks, to see if we collide with another Enemy
  624. foreach (GameObject checkobject in gamestate.GameObjects)
  625. {
  626. if (checkobject is PlatformObject && checkobject != this)
  627. {
  628. PlatformObject platformed = ((PlatformObject)checkobject);
  629. if (platformed.GetRectangle().IntersectsWith(GetRectangle()))
  630. {
  631. this.DoCollide(gamestate, platformed);
  632. }
  633. }
  634. }
  635. return base.PerformFrame(gamestate);
  636. }
  637. }
  638. public interface iBumpable
  639. {
  640. void Bump(Block bumpedby);
  641. }
  642. }