PageRenderTime 57ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/Poing2/GameEnemy.cs

http://github.com/BCProgramming/BASeBlock
C# | 2651 lines | 1702 code | 620 blank | 329 comment | 223 complexity | 47b91c17baef76d80bd58c7f6bc5cf34 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. using System;
  2. using System.CodeDom.Compiler;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.Drawing.Design;
  7. using System.Drawing.Imaging;
  8. using System.Linq;
  9. using System.Net.Sockets;
  10. using System.Reflection;
  11. using System.Reflection.Emit;
  12. using System.Runtime.Serialization;
  13. using System.ComponentModel;
  14. using BASeCamp.BASeBlock.Blocks;
  15. using BASeCamp.BASeBlock.Events;
  16. using BASeCamp.BASeBlock.Particles;
  17. using BASeCamp.BASeBlock.Projectiles;
  18. namespace BASeCamp.BASeBlock
  19. {
  20. public class NoKillIncrementAttribute : System.Attribute
  21. {
  22. }
  23. /// <summary>
  24. /// Attribute that forces a class deriving from GameEnemy to Increment the Statistics kill counter when it dies.
  25. /// this cannot be overridden (to prevent kill incrementing)
  26. /// </summary>
  27. public class KillIncrementAttribute : System.Attribute
  28. {
  29. }
  30. /// <summary>
  31. /// GameEnemy: derives from GameObject, and provides the core logic for all Enemies that will be created.
  32. /// </summary>
  33. public abstract class GameEnemy : GameObject,iLocatable ,IExplodable
  34. {
  35. /*many enemies will be implemented using different "states"; that is, an enemy may have one set of animation frames
  36. for when it is "idle", and other animation frames for Attacking and so forth. We keep track of several things to help in this:
  37. * 1. A Dictionary that stores Arrays of Image KEYS (we don't want to duplicate image data across multiple instances) which can be indexed
  38. * based on the "EnemyState" string value, which, ideally, could be an enumeration, but different enemies may have any number of states and artificially limiting them seems
  39. * a bit premature- so we use a string.
  40. */
  41. protected int _HitPoints = 50;
  42. protected int _MaxHitPoints = 50;
  43. public int HitPoints { get { return _HitPoints; }
  44. set { _HitPoints = value; if (_HitPoints > _MaxHitPoints) _MaxHitPoints = value; }
  45. }
  46. public int MaxHitPoints { get { return _MaxHitPoints; } set { _MaxHitPoints = value; } }
  47. [Flags]
  48. public enum AutoSizeModeConstants
  49. {
  50. AutoSize_None, //default, no sizing will be done as frames change.
  51. AutoSize_ExpandTranslate, //expand to the left or up to preserve frame X scale.
  52. AutoSize_ExpandUp, //expand width or height to preserve frame Y scale.
  53. AutoSize_ExpandCenter //expansion will center the axis in the same location the center sits in the current frame.
  54. }
  55. public class FrameSizeMode
  56. {
  57. public AutoSizeModeConstants XSizeMode { get; set; }
  58. public AutoSizeModeConstants YSizeMode { get; set; }
  59. public FrameSizeMode(AutoSizeModeConstants pXSizeMode, AutoSizeModeConstants pYSizeMode)
  60. {
  61. XSizeMode = pXSizeMode;
  62. YSizeMode = pYSizeMode;
  63. }
  64. /// <summary>
  65. /// given our scalemode, location, and the current and new size, changes Location and currsize
  66. /// to scale appropriate to newsize.
  67. /// </summary>
  68. /// <param name="sizemode"></param>
  69. /// <param name="Location"></param>
  70. /// <param name="currsize"></param>
  71. /// <param name="newsize"></param>
  72. public void Rescale(ref PointF Location, ref SizeF currsize, SizeF newsize)
  73. {
  74. }
  75. private void RescaleAxis(AutoSizeModeConstants smode,ref float value, ref float Currsize, ref float newsize)
  76. {
  77. switch (smode)
  78. {
  79. case AutoSizeModeConstants.AutoSize_None:
  80. break; //nothing...
  81. case AutoSizeModeConstants.AutoSize_ExpandTranslate:
  82. //move value so that the the "end" of the size will be the same. (so the right or bottom remains unchanged)
  83. value = (value + Currsize) - newsize;
  84. Currsize = newsize;
  85. break;
  86. case AutoSizeModeConstants.AutoSize_ExpandUp:
  87. //expand outwards...
  88. Currsize = newsize;
  89. break;
  90. case AutoSizeModeConstants.AutoSize_ExpandCenter:
  91. value = value + ((value + Currsize / 2) - (value + newsize / 2));
  92. break;
  93. }
  94. }
  95. }
  96. private FrameSizeMode _FramingSizeMode = new FrameSizeMode(AutoSizeModeConstants.AutoSize_None, AutoSizeModeConstants.AutoSize_None);
  97. public FrameSizeMode FramingSizeMode { get { return _FramingSizeMode; } set { _FramingSizeMode = value; } }
  98. private List<EnemyTrigger> _Triggers = new List<EnemyTrigger>();
  99. /// <summary>
  100. /// Trigger that get's invoked for certain circumstances
  101. /// </summary>
  102. public List<EnemyTrigger> Triggers {get { return _Triggers; }
  103. set {
  104. _Triggers = value;
  105. if (_Triggers != null)
  106. {
  107. foreach (var looptrigger in _Triggers)
  108. {
  109. looptrigger.OurEnemy=this;
  110. }
  111. }
  112. }
  113. } //Trigger to invoke when this enemy dies.
  114. //public Trigger TriggerInvoke { get; set; }
  115. public ImageAttributes DrawAttributes=null;
  116. //Enemy State:
  117. protected SizeF? _DrawSize=null; //if null, will use default. This is a float, and represents the % size we should use of the
  118. public virtual SizeF DrawSize {
  119. get
  120. {
  121. if (_DrawSize == null) return this.GetCurrentImage().Size;
  122. else return _DrawSize.Value;
  123. }
  124. set { _DrawSize = value; }}
  125. //current frame image.
  126. public event EventHandler<EnemyDeathEventArgs> OnDeath;
  127. private struct deathtriggerdata
  128. {
  129. public GameEnemy theenemy;
  130. public BCBlockGameState stateobj;
  131. public String SoundPlay;
  132. public List<EnemyTrigger> Triggers;
  133. }
  134. public override string ToString()
  135. {
  136. String buildit = "GameEnemy { \n" +
  137. "Location:" + this.Location.ToString() + "\n" +
  138. "Size:" + this.DrawSize.ToString() + "\n" +
  139. "State:" + this.EnemyState.ToString() + "\n" +
  140. "Hitpoints:" + this.HitPoints.ToString() + "\n" +
  141. "maxHP:" + this.MaxHitPoints.ToString() + "\n";
  142. buildit+="Triggers {\n";
  143. foreach(var iterate in Triggers)
  144. {
  145. buildit+=iterate.ToString() + "\n";
  146. buildit+="----\n";
  147. }
  148. buildit+="\n";
  149. return buildit;
  150. }
  151. public virtual void ExplosionInteract(object sender, PointF Origin, PointF Vector)
  152. {
  153. HitPoints -= (int)Vector.Magnitude();
  154. }
  155. //System.Threading.Timer usetimer;
  156. System.Threading.Thread deaththread;
  157. DateTime InitialDeath;
  158. private void CalledOnDeath(Object sender, EnemyDeathEventArgs e)
  159. {
  160. if (e.EnemyDied is ChomperEnemy)
  161. {
  162. Debug.Print("Chomper...");
  163. }
  164. if (Triggers != null)
  165. {
  166. Debug.Print("Enemy killed; invoking triggers.");
  167. foreach (EnemyTrigger looptrigger in Triggers)
  168. {
  169. looptrigger.InvokeTriggerWithDelay(e.StateObject);
  170. }
  171. }
  172. /*if (Triggers != null)
  173. {
  174. Debug.Print("OH NOES an enemy died, -invoking triggers");
  175. foreach (EnemyTrigger looptrigger in Triggers)
  176. {
  177. looptrigger.InvokeTriggerWithDelay(stateobject);
  178. }
  179. /*
  180. deathtriggerdata datatriggers = new deathtriggerdata();
  181. datatriggers.theenemy = enemydied;
  182. datatriggers.stateobj = stateobject;
  183. datatriggers.SoundPlay = "puzzle";
  184. //Debug.Print("assigning datatriggers TriggerID of " + Trigger);
  185. datatriggers.Triggers= Triggers;
  186. //usetimer = new Timer(deathtriggertimeout,(object)datatriggers,0,500);
  187. deaththread = new Thread(deathtriggertimeout);
  188. InitialDeath = DateTime.Now;
  189. deaththread.Start((object) datatriggers);*/
  190. //}
  191. //assuming that works, we would now destroy all blocks in the level that have the same TriggerID as us.
  192. }
  193. /*
  194. private void deathtriggertimeout(object parameter)
  195. {
  196. Debug.Print("deathtriggertimeout");
  197. while ((DateTime.Now - InitialDeath).Seconds < 2)
  198. {
  199. Thread.Sleep(100);
  200. }
  201. Debug.Print("timeout expired...");
  202. deathtriggerdata dtd = (deathtriggerdata)parameter;
  203. //destroy all blocks with the same ID.
  204. // Debug.Print("TriggerID=" + dtd.TriggerID);
  205. List<Block> removethese = new List<Block>();
  206. if(!String.IsNullOrEmpty(dtd.SoundPlay))
  207. BCBlockGameState.Soundman.PlaySound(dtd.SoundPlay,3.0f);
  208. lock (dtd.stateobj )
  209. {
  210. if (dtd.TriggerObj != null)
  211. {
  212. //Debug.Print("Total number of blocks with triggerID of " + dtd.TriggerID + (from b in dtd.stateobj.Blocks where b.TriggerID == dtd.TriggerID select b).Count().ToString());
  213. //foreach (
  214. // Block loopblock in
  215. // (from b in dtd.stateobj.Blocks where b.TriggerID == dtd.TriggerID select b))
  216. /*
  217. foreach (Block b in dtd.stateobj.Blocks)
  218. {
  219. if (b.TriggerID == TriggerID)
  220. {
  221. Debug.Print("kerploding a block...");
  222. b.StandardSpray(dtd.stateobj);
  223. removethese.Add(b);
  224. }
  225. }
  226. foreach (Block removeblock in removethese)
  227. {
  228. dtd.stateobj.Blocks.Remove(removeblock);
  229. }
  230. *
  231. dtd.TriggerObj.InvokeTrigger(dtd.stateobj);
  232. dtd.stateobj.Forcerefresh = true;
  233. }
  234. }
  235. }
  236. */
  237. public virtual int GetScoreValue()
  238. {
  239. return 100;
  240. }
  241. private bool AllowKillIncrement()
  242. {
  243. return !(BCBlockGameState.HasAttribute(this.GetType(), typeof(NoKillIncrementAttribute))) ||
  244. BCBlockGameState.HasAttribute(this.GetType(),typeof(NoKillIncrementAttribute));
  245. }
  246. internal void InvokeOnDeath(BCBlockGameState stateobject,ref List<GameObject> AddObjects,ref List<GameObject> removeobjects)
  247. {
  248. var gotset = stateobject.ClientObject.GetPlayingSet();
  249. if (gotset !=null && AllowKillIncrement())
  250. {
  251. gotset.Statistics.EnemyKills++;
  252. }
  253. int gotvalue = GetScoreValue();
  254. if(gotvalue != 0)
  255. Block.AddScore(stateobject, GetScoreValue(), Location);
  256. var deathproc = OnDeath;
  257. if (deathproc != null)
  258. {
  259. deathproc(this, new EnemyDeathEventArgs(this, stateobject));
  260. }
  261. }
  262. public SizeF GetSize()
  263. {
  264. return DrawSize;
  265. }
  266. //public event Action<String, String> EnemyStateChanged;
  267. public event EventHandler<EnemyStateChangeEventArgs> EnemyStateChanged;
  268. private void RaiseEnemyStateChange(String oldstate, String newstate)
  269. {
  270. Debug.Print("Enemy State changed:" + oldstate + " to " + newstate);
  271. var invokethis = EnemyStateChanged;
  272. if (invokethis != null)
  273. {
  274. EnemyStateChangeEventArgs esc = new EnemyStateChangeEventArgs(oldstate, newstate);
  275. invokethis(this, esc);
  276. }
  277. }
  278. public String EnemyAction
  279. {
  280. set
  281. {
  282. if (_ActionIndex == null) InitActions();
  283. //map backwards...
  284. foreach (var iterate in _ActionIndex)
  285. {
  286. if (iterate.Value.Equals(value, StringComparison.OrdinalIgnoreCase))
  287. EnemyState = iterate.Key;
  288. }
  289. }
  290. get
  291. {
  292. if (_ActionIndex == null) InitActions();
  293. return GetAction(_EnemyState);
  294. }
  295. }
  296. protected void InitActions()
  297. {
  298. _ActionIndex = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  299. foreach (var kvp in StateFrameImageKeys)
  300. {
  301. _ActionIndex.Add(kvp.Key, kvp.Key);
  302. }
  303. }
  304. protected String _EnemyState = "idle";
  305. public String EnemyState
  306. {
  307. get { return _EnemyState; }
  308. set
  309. {
  310. if (_EnemyState != value)
  311. {
  312. RaiseEnemyStateChange(_EnemyState, value);
  313. _EnemyState = value;
  314. }
  315. }
  316. }
  317. protected int mDefaultFrameDelay=50;
  318. protected PointF _Location;
  319. public virtual PointF Location { get { return _Location; } set { _Location = value; } }
  320. protected float DrawRotation; //rotation to draw...
  321. //dictionary of Image keys...
  322. protected Dictionary<String, String[]> _StateFrameImageKeys = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
  323. //a similar dictionary, which maps a state to an action. By default, each state is it's own action.
  324. //this is added to allow for different states for the images (such as jumping up and down) while having the
  325. //logic be able to deal with it in the same manner.
  326. protected Dictionary<String, String> _ActionIndex = null;
  327. public static GameEnemy CreateBoss<T>(PointF location, BCBlockGameState gstate) where T : GameEnemy
  328. {
  329. var resultvalue = CreateBoss<T>(location, gstate, (g) => g.OnDeath += bossdeath,new TimeSpan(0,0,0,3));
  330. return resultvalue;
  331. }
  332. public static void bossdeath(Object sender,EnemyDeathEventArgs e)
  333. {
  334. createenemy_OnDeath(sender, e);
  335. }
  336. public static GameEnemy CreateBoss<T>(PointF location, BCBlockGameState gstate,Action<T> onDeath,TimeSpan DeathDelay) where T:GameEnemy
  337. {
  338. GameEnemy createenemy = null;
  339. Type createtype = typeof(T);
  340. MethodInfo usemethod = null;
  341. foreach (var iterate in createtype.GetMethods())
  342. {
  343. if (iterate.Name.Equals("CreateBoss", StringComparison.OrdinalIgnoreCase) && iterate.IsStatic)
  344. {
  345. usemethod = iterate;
  346. }
  347. }
  348. try
  349. {
  350. if (usemethod != null)
  351. {
  352. createenemy= (GameEnemy)usemethod.Invoke(null,new object[] {location,gstate});
  353. }
  354. }
  355. catch(Exception exx)
  356. {
  357. Debug.Print(exx.ToString());
  358. }
  359. if (createenemy != null)
  360. {
  361. //int useChannel = Trigger.GetAvailableID(gstate);
  362. //createenemy.Triggers.Add(new EnemyTrigger(createenemy, useChannel, new TimeSpan(0, 0, 0, 0, 500)));
  363. gstate.Blocks.AddLast(new NormalBlock(new RectangleF(-500, -500, 32, 16)));
  364. BCBlockGameState.Soundman.PlayTemporaryMusic("smb2boss",1.0f,true);
  365. //gstate.PlayingLevel.LevelEvents.Add(new FinishLevelEvent(useChannel));
  366. gstate.PlayingLevel.TallyMusicName = "bossclear";
  367. gstate.PlayingLevel.ClearTitle = " BOSS CLEAR \n";
  368. gstate.BossCounter++; //add one to the boss counter.
  369. //show a message expressing the gravity of the situation.
  370. gstate.EnqueueMessage(createenemy.GetType().Name + "Boss Has awoken!");
  371. createenemy.OnDeath += createenemy_OnDeath;
  372. return createenemy;
  373. }
  374. return null;
  375. }
  376. static void createenemy_OnDeath(Object sender, EnemyDeathEventArgs e)
  377. {
  378. e.StateObject.EnqueueMessage(e.EnemyDied.GetType().Name + " Boss defeated! Well Done!");
  379. BCBlockGameState.Soundman.StopMusic();
  380. BCBlockGameState.Soundman.PlaySound("BOSSKILL");
  381. var currset = e.StateObject.ClientObject.GetPlayingSet();
  382. if (currset != null)
  383. {
  384. currset.Statistics.BossKills++;
  385. }
  386. e.StateObject.BossCounter--;
  387. }
  388. //protected int FrameDelay = 35; //delay between frame changes.
  389. protected Dictionary<String, int[]> FrameDelayTimes = new Dictionary<string, int[]>(StringComparer.OrdinalIgnoreCase);
  390. private T[] CreateArray<T>(T valueassign,int number)
  391. {
  392. T[] createarray = new T[number];
  393. for (int i = 0; i < createarray.Length;i++ )
  394. {
  395. createarray[i] = valueassign;
  396. }
  397. return createarray;
  398. }
  399. public Dictionary<String, String[]> StateFrameImageKeys
  400. {
  401. get {
  402. return _StateFrameImageKeys;
  403. }
  404. set {
  405. _StateFrameImageKeys=value;
  406. if (_StateFrameImageKeys == null) return;
  407. StateFrameIndex.Clear();
  408. FrameDelayTimes.Clear();
  409. foreach (KeyValuePair<String, String[]> kvp in _StateFrameImageKeys)
  410. {
  411. StateFrameIndex.Add(kvp.Key, 0);
  412. FrameDelayTimes.Add(kvp.Key, CreateArray<int>(50, kvp.Value.Length));
  413. }
  414. }
  415. }
  416. protected Dictionary<String, int> StateFrameIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  417. protected GameEnemy(PointF Position, Dictionary<String, String[]> StateFrameData, int FrameDelay)
  418. : this(Position, StateFrameData, FrameDelay, null)
  419. {
  420. OnDeath = CalledOnDeath;
  421. }
  422. protected String GetAction(String state)
  423. {
  424. return _ActionIndex[state];
  425. }
  426. protected GameEnemy(PointF Position, Dictionary<String, String[]> StateFrameData,int FrameDelay,ImageAttributes useattributes)
  427. {
  428. OnDeath = CalledOnDeath;
  429. DrawAttributes=useattributes;
  430. Location=Position;
  431. mDefaultFrameDelay=FrameDelay;
  432. StateFrameImageKeys = StateFrameData;
  433. if(StateFrameData!=null)
  434. {
  435. _ActionIndex = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  436. foreach (KeyValuePair<String, String[]> kvp in StateFrameData)
  437. {
  438. if (StateFrameIndex.ContainsKey(kvp.Key))
  439. {
  440. StateFrameIndex[kvp.Key] = 0;
  441. FrameDelayTimes[kvp.Key] = CreateArray(FrameDelay, kvp.Value.Length);
  442. }
  443. else
  444. {
  445. StateFrameIndex.Add(kvp.Key, 0);
  446. FrameDelayTimes.Add(kvp.Key, CreateArray(FrameDelay, kvp.Value.Length));
  447. }
  448. _ActionIndex.Add(kvp.Key, kvp.Key);
  449. }
  450. }
  451. }
  452. public virtual Image GetCurrentImage()
  453. {
  454. if (StateFrameImageKeys == null) return null;
  455. try
  456. {
  457. return BCBlockGameState.Imageman.getLoadedImage(StateFrameImageKeys[_EnemyState][StateFrameIndex[_EnemyState]]);
  458. }
  459. catch
  460. {
  461. //do nothin
  462. return null;
  463. }
  464. }
  465. public RectangleF GetRectangleF()
  466. {
  467. if (DrawSize != null)
  468. return new RectangleF(Location.X, Location.Y, DrawSize.Width, DrawSize.Height);
  469. else
  470. {
  471. Image grabimage = GetCurrentImage();
  472. return new RectangleF(Location.X,Location.Y,grabimage.Width,grabimage.Height);
  473. }
  474. }
  475. public void setRectangleF(RectangleF newrect)
  476. {
  477. Location = new PointF(newrect.Left, newrect.Top);
  478. DrawSize = new SizeF(newrect.Width, newrect.Height);
  479. }
  480. public void setRectangle(Rectangle newrect)
  481. {
  482. RectangleF convrect = newrect.ToRectangleF();
  483. setRectangleF(convrect);
  484. }
  485. public Rectangle GetRectangle()
  486. {
  487. RectangleF convrect = GetRectangleF();
  488. return new Rectangle((int)convrect.Left,(int)convrect.Top,(int)convrect.Width,(int)convrect.Height);
  489. }
  490. /// <summary>
  491. /// called when a set of frames is about to loop around.
  492. /// </summary>
  493. public virtual void OnFramesetComplete(BCBlockGameState gamestate)
  494. {
  495. } //called when a set of frames loops back to 0.
  496. /// <summary>
  497. /// increments the frame number of the currently selected list of state images.
  498. /// </summary>
  499. ///
  500. private int GetFrameDelayTime()
  501. {
  502. //if the FrameDelayTimes Dictionary is null- or if it doesn't extend far enough to cover the current frame...
  503. /*if (FrameDelayTimes == null || FrameDelayTimes.Count < StateFrameIndex[_EnemyState])
  504. {
  505. //return a default value.
  506. return mDefaultFrameDelay;
  507. }*/
  508. //otherwise, retrieve the appropriate delay time based on this character/enemies current state and current frame.
  509. try
  510. {
  511. return FrameDelayTimes[_EnemyState][StateFrameIndex[_EnemyState]];
  512. }
  513. catch
  514. {
  515. return mDefaultFrameDelay;
  516. }
  517. }
  518. int FrameDelayCounter = 0;
  519. private void IncrementImageFrame(BCBlockGameState gamestate)
  520. {
  521. FrameDelayCounter++;
  522. if (FrameDelayCounter > GetFrameDelayTime())
  523. {
  524. Trace.WriteLine("FrameSet:" + _EnemyState);
  525. OnFramesetComplete(gamestate);
  526. FrameDelayCounter = 0;
  527. int currframe = StateFrameIndex[_EnemyState];
  528. int numframes = StateFrameImageKeys[_EnemyState].Length - 1;
  529. if (currframe >= numframes)
  530. currframe = 0;
  531. else
  532. currframe++;
  533. StateFrameIndex[_EnemyState] = currframe;
  534. }
  535. }
  536. public override bool PerformFrame(BCBlockGameState gamestate)
  537. {
  538. IncrementImageFrame(gamestate);
  539. //throw new NotImplementedException();
  540. //return _HitPoints <= 0;
  541. return false;
  542. }
  543. public virtual Image getIcon()
  544. {
  545. Bitmap buildbit = new Bitmap((int)DrawSize.Width, (int)DrawSize.Height);
  546. Graphics g = Graphics.FromImage(buildbit);
  547. //translate to 0,0.
  548. g.TranslateTransform(-(Location.X), -(Location.Y));
  549. Draw(g);
  550. return buildbit;
  551. }
  552. public override void Draw(Graphics g)
  553. {
  554. Image currimage = GetCurrentImage();
  555. if (currimage == null)
  556. {
  557. Debug.Print("BREAK");
  558. }
  559. var copyt = g.Transform.Clone();
  560. PointF DrawLocation = Location;
  561. if (Math.Abs(DrawRotation) > 0.1f)
  562. {
  563. g.TranslateTransform(DrawSize.Width / 2 + DrawLocation.X, DrawSize.Height / 2 + DrawLocation.Y);
  564. g.RotateTransform(DrawRotation);
  565. g.TranslateTransform(-DrawSize.Width / 2, -DrawSize.Height / 2);
  566. DrawLocation = new PointF(0, 0);
  567. }
  568. if (DrawSize != null)
  569. {
  570. if (DrawAttributes == null)
  571. {
  572. //g.DrawImage(GetCurrentImage(), Location.X, Location.Y, DrawSize.Value.Width, DrawSize.Value.Height);
  573. g.DrawImage(GetCurrentImage(), DrawLocation.X, DrawLocation.Y, DrawSize.Width, DrawSize.Height);
  574. }
  575. else
  576. {
  577. //g.DrawImage(BlockImage, new Rectangle((int)BlockRectangle.Left, (int)BlockRectangle.Top, (int)BlockRectangle.Width, (int)BlockRectangle.Height), 0f, 0f, BlockImage.Width, BlockImage.Height, GraphicsUnit.Pixel, DrawAttributes);
  578. g.DrawImage(currimage, new Rectangle((int)DrawLocation.X, (int)DrawLocation.Y, (int)DrawSize.Width, (int)DrawSize.Height), 0f, 0f, currimage.Width, currimage.Height, GraphicsUnit.Pixel, DrawAttributes);
  579. //g.DrawImage(GetCurrentImage(), Location.X, DrawSize.Value.Width, DrawSize.Value.Height, DrawAttributes);
  580. }
  581. }
  582. else
  583. {
  584. if(DrawAttributes==null)
  585. g.DrawImage(GetCurrentImage(), DrawLocation);
  586. else
  587. g.DrawImage(currimage, new Rectangle((int)DrawLocation.X, (int)DrawLocation.Y, (int)currimage.Width, (int)currimage.Height), 0f, 0f, currimage.Width, currimage.Height, GraphicsUnit.Pixel, DrawAttributes);
  588. }
  589. g.Transform=copyt;
  590. if (this is iImagable)
  591. {
  592. //some Enemies implement interfaces that provide location, size, and speed information.
  593. //we will use this to draw a HP bar above those enemies.
  594. //this is just a testing feature...
  595. //first, decide where to draw it.
  596. iImagable refprojectile = this as iImagable;
  597. var ourrect = refprojectile.getRectangle();
  598. //3 pixels high.
  599. Rectangle HPBar = new Rectangle(ourrect.Left - 2, ourrect.Top - 3, ourrect.Width + 4, 2);
  600. if (!g.ClipBounds.Contains(HPBar))
  601. HPBar.Offset(0, ourrect.Height + 3);
  602. float healthpercent = (float)HitPoints / (float)MaxHitPoints;
  603. Rectangle fullbar = new Rectangle(HPBar.Left, HPBar.Top, (int)(Math.Ceiling((float)HPBar.Width * healthpercent)), HPBar.Height);
  604. g.FillRectangle(Brushes.White, HPBar);
  605. using (SolidBrush usebrush = new SolidBrush(BCBlockGameState.MixColor(Color.Red, Color.Lime, (float)HitPoints / (float)MaxHitPoints)))
  606. {
  607. g.FillRectangle(usebrush, fullbar);
  608. }
  609. g.DrawRectangle(Pens.Black, HPBar);
  610. }
  611. //throw new NotImplementedException();
  612. }
  613. }
  614. [Bossable]
  615. public class SnakeEnemy : GameEnemy, IDeserializationCallback
  616. {
  617. public enum SnakeMoveDirection
  618. {
  619. SnakeMove_Left,
  620. SnakeMove_Up,
  621. SnakeMove_Right,
  622. SnakeMove_Down
  623. }
  624. private SizeF createblockSize = new SizeF(8, 8);
  625. private DateTime lastcall = DateTime.Now;
  626. private int FrameDelay = 500;
  627. private bool killed = false;
  628. public PointF BlockSpeed = new PointF(0, 1);
  629. public Type BlockTypeCreate = typeof(DemonBlock);
  630. public int MaxLength = 25;
  631. private Block SnakeHead = null;
  632. private const int gridsize = 15;
  633. private Queue<Block> snakecomponents = new Queue<Block>();
  634. public SnakeEnemy(Block[] usesegments, int Milliframedelay)
  635. : base(usesegments[usesegments.Length - 1].BlockLocation, null, Milliframedelay)
  636. {
  637. snakecomponents = new Queue<Block>();
  638. //element 0 is the tail; the last element is our new "head". as such ,to make our queue, we need to enqueue from the "last" item (the head) to the first item.
  639. for (int i = 0; i < usesegments.Length; i++)
  640. {
  641. snakecomponents.Enqueue(usesegments[i]);
  642. if (i == usesegments.Length)
  643. usesegments[i].OnBlockHit += SnakeHead_OnBlockHit;
  644. else
  645. usesegments[i].OnBlockDestroy += SnakeBody_OnBlockDestroy;
  646. }
  647. SnakeHead = usesegments[usesegments.Length - 1];
  648. BlockTypeCreate = SnakeHead.GetType();
  649. }
  650. public SnakeEnemy(PointF pPosition)
  651. : this(pPosition, 150)
  652. {
  653. }
  654. public SnakeEnemy(PointF pPosition, int MilliFrameDelay)
  655. : this(pPosition, MilliFrameDelay, typeof(NormalBlock))
  656. {
  657. }
  658. public override int GetScoreValue()
  659. {
  660. return 4000;
  661. }
  662. public SnakeEnemy(PointF pPosition, int MilliFrameDelay, Type pBlockTypeCreate)
  663. : base(pPosition, null, MilliFrameDelay)
  664. {
  665. if (!pBlockTypeCreate.IsSubclassOf(typeof(Block)))
  666. {
  667. throw new ArgumentException("Snakes can only be made of blocks", "pBlockTypeCreate");
  668. }
  669. BlockTypeCreate = pBlockTypeCreate;
  670. FrameDelay = MilliFrameDelay;
  671. Location = pPosition;
  672. }
  673. private PointF[] prospectivedirections = new PointF[] { new PointF(0, 1), new PointF(1, 0), new PointF(0, -1), new PointF(-1, 0) };
  674. private SnakeMoveDirection GetCurrentDirection()
  675. {
  676. if (BlockSpeed.X == 0 && BlockSpeed.Y == 1)
  677. {
  678. return SnakeMoveDirection.SnakeMove_Down;
  679. }
  680. else if (BlockSpeed.X == 0 && BlockSpeed.Y == -1)
  681. {
  682. return SnakeMoveDirection.SnakeMove_Up;
  683. }
  684. else if (BlockSpeed.X == -1 && BlockSpeed.Y == 0)
  685. {
  686. return SnakeMoveDirection.SnakeMove_Left;
  687. }
  688. else if (BlockSpeed.X == 1 && BlockSpeed.Y == 0)
  689. {
  690. return SnakeMoveDirection.SnakeMove_Right;
  691. }
  692. return SnakeMoveDirection.SnakeMove_Right;
  693. }
  694. private int GetWeightForDirection(int[,] gridweights, SnakeMoveDirection direction)
  695. {
  696. int midspot = (gridsize / 2);
  697. int startx = 0, starty = 0, endx = 0, endy = 0;
  698. switch (direction)
  699. {
  700. case SnakeMoveDirection.SnakeMove_Up:
  701. startx = 0;
  702. endx = gridsize;
  703. starty = 0;
  704. endy = midspot;
  705. break;
  706. case SnakeMoveDirection.SnakeMove_Left:
  707. startx = 0;
  708. endx = midspot;
  709. starty = 0;
  710. endy = gridsize;
  711. break;
  712. case SnakeMoveDirection.SnakeMove_Down:
  713. startx = 0;
  714. endx = gridsize;
  715. starty = gridsize - midspot;
  716. endy = gridsize;
  717. break;
  718. case SnakeMoveDirection.SnakeMove_Right:
  719. startx = gridsize - midspot;
  720. endx = gridsize;
  721. starty = 0;
  722. endy = gridsize;
  723. break;
  724. }
  725. int accumtotal = 0;
  726. for (int loopx = startx; loopx < endx; loopx++)
  727. {
  728. for (int loopy = starty; loopy < endy; loopy++)
  729. {
  730. accumtotal += gridweights[loopx, loopy];
  731. }
  732. }
  733. return accumtotal;
  734. }
  735. private PointF directionToSpeed(SnakeMoveDirection convertdirection)
  736. {
  737. switch (convertdirection)
  738. {
  739. case SnakeMoveDirection.SnakeMove_Up:
  740. return new PointF(0, -1);
  741. case SnakeMoveDirection.SnakeMove_Left:
  742. return new PointF(-1, 0);
  743. case SnakeMoveDirection.SnakeMove_Down:
  744. return new PointF(0, 1);
  745. break;
  746. case SnakeMoveDirection.SnakeMove_Right:
  747. return new PointF(1, 0);
  748. break;
  749. }
  750. return new PointF(0, 1);
  751. }
  752. private SnakeMoveDirection[] GetProspectiveDirections(SnakeMoveDirection currentdirection)
  753. {
  754. switch (currentdirection)
  755. {
  756. case SnakeMoveDirection.SnakeMove_Up:
  757. return new SnakeMoveDirection[] { SnakeMoveDirection.SnakeMove_Left, SnakeMoveDirection.SnakeMove_Up, SnakeMoveDirection.SnakeMove_Right };
  758. break;
  759. case SnakeMoveDirection.SnakeMove_Down:
  760. return new SnakeMoveDirection[] { SnakeMoveDirection.SnakeMove_Right, SnakeMoveDirection.SnakeMove_Down, SnakeMoveDirection.SnakeMove_Left };
  761. break;
  762. case SnakeMoveDirection.SnakeMove_Left:
  763. return new SnakeMoveDirection[] { SnakeMoveDirection.SnakeMove_Down, SnakeMoveDirection.SnakeMove_Left, SnakeMoveDirection.SnakeMove_Up };
  764. break;
  765. case SnakeMoveDirection.SnakeMove_Right:
  766. return new SnakeMoveDirection[] { SnakeMoveDirection.SnakeMove_Up, SnakeMoveDirection.SnakeMove_Right, SnakeMoveDirection.SnakeMove_Down };
  767. break;
  768. }
  769. return null;
  770. }
  771. private int IsBlockAtPos(BCBlockGameState statein, PointF poscheck)
  772. {
  773. return (from n in statein.Blocks where n.BlockRectangle.Contains(poscheck) select n).Count();
  774. }
  775. private int getPosWeight(BCBlockGameState statein, PointF poscheck)
  776. {
  777. //the way the simple AI works is that it basically interprets the world in units of it's size.
  778. //the AI itself basically has to choose to either turn left, go straight, or turn right.
  779. //too simplify this I currently use a small 9x9 grid; if a block occupies a given location or that location is outside the bounds of the level,
  780. //the grid value will be false. Otherwise, it will be true. the decision of which direction to turn is based on the values in the grid. (whichever prospective direction has the most true values).
  781. //*changed right now to ints, whichever direction has the lowest sum).
  782. if (!statein.GameArea.Contains((int)poscheck.X, (int)poscheck.Y))
  783. {
  784. return 3;
  785. }
  786. else
  787. return 2 * IsBlockAtPos(statein,poscheck);
  788. }
  789. private int[,] getAIGridData(BCBlockGameState gamestate)
  790. {
  791. if (SnakeHead == null) return null;
  792. int middlespot = (int)Math.Ceiling((float)gridsize / 2); //should be five for gridsize of 9.
  793. SizeF totalgridsize = new SizeF(gridsize * createblockSize.Width, gridsize * createblockSize.Height);
  794. SizeF halfgridsize = new SizeF(totalgridsize.Width / 2, totalgridsize.Height / 2);
  795. int[,] returngrid = new int[gridsize, gridsize];
  796. PointF centralPoint = SnakeHead.CenterPoint();
  797. for (int x = 0; x < gridsize; x++)
  798. {
  799. float Xuse = ((centralPoint.X - halfgridsize.Width) + (createblockSize.Width * x));
  800. for (int y = 0; y < gridsize; y++)
  801. {
  802. float Yuse = ((centralPoint.Y - halfgridsize.Height) + (createblockSize.Height * y));
  803. //at 0, we want to be at centerpoint-(blockwidth*middlespot)
  804. returngrid[x, y] = getPosWeight(gamestate, new PointF(Xuse, Yuse));
  805. }
  806. }
  807. return returngrid;
  808. }
  809. //private PointF[] getprospectiveDirections(BCBlockGameState gamestate, PointF currentdirection)
  810. //{
  811. // float xpart = currentdirection.X;
  812. // float ypart = currentdirection.Y;
  813. //}
  814. private void CalcAI(BCBlockGameState gamestate)
  815. {
  816. Random rg = BCBlockGameState.rgen;
  817. //currently changes the snake direction randomly.
  818. //if (rg.NextDouble() < 0.1)
  819. //{
  820. //BlockSpeed = prospectivedirections[rg.Next(0,prospectivedirections.Length)];
  821. //}
  822. sincelastchange++;
  823. if (sincelastchange > 2)
  824. {
  825. SnakeMoveDirection[] snakedirections = GetProspectiveDirections(GetCurrentDirection());
  826. //get the list of possible directions to go.
  827. //calculate the grid weights around the snakes head.
  828. int[,] gridweight = getAIGridData(gamestate);
  829. if (gridweight != null)
  830. {
  831. //corrdirections: corresponds to SnakeMoveDirection Array, containing the accumulated value of that direction from the grid.
  832. int[] corrdirections = new int[snakedirections.Length];
  833. int currmin = int.MaxValue, minindex = -1;
  834. for (int i = 0; i < snakedirections.Length; i++)
  835. {
  836. corrdirections[i] = GetWeightForDirection(gridweight, snakedirections[i]);
  837. if (corrdirections[i] < currmin)
  838. {
  839. currmin = corrdirections[i];
  840. minindex = i;
  841. }
  842. }
  843. Debug.Print("Current Direction is " + Enum.GetName(typeof(SnakeMoveDirection), GetCurrentDirection()) +
  844. " Chosen direction is " + Enum.GetName(typeof(SnakeMoveDirection), snakedirections[minindex]));
  845. //we have our direction now; the snakedirection corresponding to the minimum.
  846. if (snakedirections[minindex] != GetCurrentDirection()) sincelastchange = 0;
  847. BlockSpeed = directionToSpeed(snakedirections[minindex]);
  848. }
  849. }
  850. }
  851. private Block CreateNewBlock(BCBlockGameState gamestate, PointF poscreate)
  852. {
  853. //Block createdblock = new NormalBlock(new RectangleF(poscreate, createblockSize), new SolidBrush(Color.Red), new Pen(Color.Black));
  854. Block createdblock = (Block)Activator.CreateInstance(BlockTypeCreate, new object[] { new RectangleF(poscreate, createblockSize) });
  855. if (createdblock is NormalBlock)
  856. {
  857. NormalBlock nb = createdblock as NormalBlock;
  858. nb.BlockColor = Color.Red;
  859. nb.PenColor = Color.Black;
  860. }
  861. lock (gamestate.Blocks)
  862. gamestate.Blocks.AddLast(createdblock);
  863. gamestate.Forcerefresh = true;
  864. return createdblock;
  865. }
  866. private int sincelastchange = 0; //number of frames since the last direction change.
  867. public override bool PerformFrame(BCBlockGameState gamestate)
  868. {
  869. //return base.PerformFrame(gamestate, ref AddObjects, ref removeobjects);
  870. //DON'T call the base; the snake isn't implemented like some other enemies, in that most of it's "matter" is really just a line of blocks.
  871. //Debug.Print("Snake performframe");
  872. //Debug.Print("Millis:" + (DateTime.Now - lastcall).TotalMilliseconds.ToString());
  873. if ((DateTime.Now - lastcall).TotalMilliseconds > FrameDelay)
  874. {
  875. if (!killed)
  876. {
  877. //perform a Frame. duh
  878. //calculated AI.
  879. CalcAI(gamestate);
  880. //AI has been calculated, so now we want to add a new block, and if our queue is longer then our maximum length, remove the tail.
  881. //for the sake of brevity, the "Position" value will indicate the upper left corner of the "head" piece.
  882. PointF useoffset = new PointF(createblockSize.Width * BlockSpeed.X,
  883. createblockSize.Height * BlockSpeed.Y);
  884. //retrieve the new coordinates to add the newest entry...
  885. PointF newheadposition = new PointF(Location.X + useoffset.X, Location.Y + useoffset.Y);
  886. //create the new block.
  887. Block createdblock = CreateNewBlock(gamestate, newheadposition);
  888. //stop hooking events from the previous snake head.
  889. if (SnakeHead != null)
  890. {
  891. SnakeHead.OnBlockHit -= SnakeHead_OnBlockHit;
  892. SnakeHead.OnBlockDestroy += SnakeBody_OnBlockDestroy;
  893. }
  894. SnakeHead = createdblock;
  895. snakecomponents.Enqueue(SnakeHead);
  896. SnakeHead.OnBlockHit += SnakeHead_OnBlockHit;
  897. Location = SnakeHead.BlockLocation;
  898. while (snakecomponents.Count > MaxLength)
  899. {
  900. //remove tail bits until our length is appropriate.
  901. Block deq = snakecomponents.Dequeue();
  902. //remove this block from the game.
  903. gamestate.Blocks.Remove(deq);
  904. }
  905. }
  906. else
  907. {
  908. //killed is true; so animate the snakes GRISLY DEMISE.
  909. //dequeue the block...
  910. if (snakecomponents.Count == 0) return true;
  911. Block deq = snakecomponents.Dequeue();
  912. while (!gamestate.Blocks.Contains(deq) && deq != null && snakecomponents.Count > 0)
  913. {
  914. deq = snakecomponents.Dequeue();
  915. }
  916. //skip blocks that aren't part of the snake anymore for whatever reason (or that are part of the snake but were destroyed in the meantime)
  917. //remove it from the game as well.
  918. if (snakecomponents.Count > 0)
  919. {
  920. if (deq != SnakeHead)
  921. {
  922. gamestate.Blocks.Remove(deq);
  923. BCBlockGameState.Soundman.PlaySound("gren");
  924. // PointF eo = deq.CenterPoint();
  925. deq.StandardSpray(gamestate);
  926. deq.AddScore(gamestate, 5);
  927. }
  928. else
  929. {
  930. gamestate.Blocks.Remove(deq);
  931. BCBlockGameState.Soundman.PlaySound("bomb");
  932. deq.StandardSpray(gamestate);
  933. deq.AddScore(gamestate, 25);
  934. gamestate.GameObjects.AddLast(new ExplosionEffect(deq.CenterPoint(), 48));
  935. }
  936. }
  937. /*for (int i = 0; i < 50; i++)
  938. {
  939. Random rg = BCBlockGameState.rgen;
  940. PointF genpoint = new PointF((float)(eo.X + (rg.NextDouble() * createblockSize.Width)), (float)(eo.Y + (rg.NextDouble() * createblockSize.Height)));
  941. DustParticle dp = new DustParticle(genpoint, 5f);
  942. gamestate.Particles.Add(dp);
  943. }*/
  944. }
  945. lastcall = DateTime.Now;
  946. gamestate.Forcerefresh = true;
  947. }
  948. return snakecomponents.Count ==0;
  949. }
  950. void SnakeBody_OnBlockDestroy(Object Sender,BlockHitEventArgs<bool> e )
  951. {
  952. if (e.TheBlock == SnakeHead || killed)
  953. {
  954. e.Result = true;
  955. //return; //when already dying, ignore.
  956. }
  957. Debug.Print("Snake's body was Destroyed...");
  958. //first, convert our queue into an LinkedList, to make this work a little easier.
  959. Queue<Block> newsnake = new Queue<Block>();
  960. //Queue<Block> refreshus = new Queue<Block>();
  961. //Block[] usearray = snakecomponents.ToArray();
  962. //the "tail" will be the first element, the last element will be the "head" block.
  963. //PSUEDOCODE:
  964. //use dequeue on OUR snake segments, adding each one to another queue, and unhooking out event handlers.
  965. Block removeit = null;
  966. while (removeit != e.TheBlock)
  967. {
  968. removeit = snakecomponents.Dequeue();
  969. removeit.OnBlockDestroy -= SnakeBody_OnBlockDestroy;
  970. //remove from game as well.
  971. removeit.AddScore(e.GameState, 5);
  972. removeit.StandardSpray(e.GameState);
  973. BCBlockGameState.Soundman.PlaySound("gren");
  974. e.GameState.Blocks.Remove(removeit);
  975. //newsnake.Enqueue(removeit);
  976. }
  977. //newsnake is the proper size.
  978. //commented out: was adding snakes instead :P but found it to be a huge pain in the rear.
  979. //Block[] snakesegments = newsnake.ToArray();
  980. //SnakeEnemy se = new SnakeEnemy(snakesegments, FrameDelay);
  981. //gamestate.GameObjects.AddLast(se);
  982. e.Result = true;
  983. // return true; //extra work should be done here to "split" the snake in half at the block.
  984. }
  985. void SnakeHead_OnBlockHit(Object sender,BlockHitEventArgs<bool> e )
  986. {
  987. //Note: this is Onblock <HIT> because we want the snake to be "defeatable" regardless of the behaviour of the block; so if the head block
  988. //is hit at all the snake dies.
  989. if (!killed)
  990. {
  991. Debug.Print("The snake has been killed. The horror!");
  992. FrameDelay *= 3;
  993. killed = true;
  994. }
  995. e.Result = true;
  996. }
  997. public override void Draw(Graphics g)
  998. {
  999. //amazingly- Do nothing! All the drawing will be managed by the Levelset and blocks and whatnot.
  1000. }
  1001. #region IDeserializationCallback Members
  1002. public void OnDeserialization(object sender)
  1003. {
  1004. //throw new NotImplementedException();
  1005. foreach (EnemyTrigger looptrigger in base.Triggers)
  1006. {
  1007. looptrigger.OurEnemy = this;
  1008. }
  1009. }
  1010. #endregion
  1011. #region iLocatable Members
  1012. /*
  1013. public new PointF Location
  1014. {
  1015. get
  1016. {
  1017. if(SnakeHead!=null)
  1018. return SnakeHead.CenterPoint();
  1019. return new PointF(0, 0);
  1020. }
  1021. set
  1022. {
  1023. //not truly supported...
  1024. //throw new NotImplementedException();
  1025. }
  1026. }
  1027. */
  1028. #endregion
  1029. }
  1030. public class ShootSpinner : SpinnerGuy
  1031. {
  1032. [Editor(typeof(ItemTypeEditor<iProjectile>),typeof(UITypeEditor))]
  1033. private Type _ShootType = typeof(LaserShot);
  1034. public Type ShootType { get { return _ShootType; } set { _ShootType = value; } }
  1035. public ShootSpinner(PointF pPosition)
  1036. : this(pPosition, new SizeF(16, 16))
  1037. {
  1038. }
  1039. public ShootSpinner(PointF pPosition, SizeF usesize)
  1040. : base(pPosition,usesize)
  1041. {
  1042. }
  1043. public override int GetScoreValue()
  1044. {
  1045. return 400;
  1046. }
  1047. protected override void Shoot(BCBlockGameState gamestate)
  1048. {
  1049. float UseVelocity = 4;
  1050. float useangle = (float)((DrawRotation / 360) * (2 * Math.PI));
  1051. //calculate the velocity....
  1052. PointF projectilespeed = new PointF((float)(Math.Cos(useangle) * UseVelocity), (float)(Math.Sin(useangle) * UseVelocity));
  1053. projectilespeed = new PointF(projectilespeed.X + base.VelocityUse.X, projectilespeed.Y + base.VelocityUse.Y);
  1054. //type has to have a constructor that accepts a Location and Speed.
  1055. if (_ShootType == typeof(LaserShot))
  1056. {
  1057. LaserShot shootbullet = new LaserShot(GetRectangle().CenterPoint(), projectilespeed);
  1058. shootbullet.DamagesPaddle = true;
  1059. shootbullet.Weak = true;
  1060. gamestate.Defer(() =>
  1061. {
  1062. gamestate.GameObjects.AddLast(shootbullet);
  1063. BCBlockGameState.Soundman.PlaySound("FIRELASER", 1.0f);
  1064. });
  1065. }
  1066. else
  1067. {
  1068. Projectile shootit = (Projectile)Activator.CreateInstance(_ShootType, GetRectangle().CenterPoint(), projectilespeed);
  1069. gamestate.Defer(() => gamestate.GameObjects.AddLast(shootit));
  1070. BCBlockGameState.Soundman.PlaySound("FIRELASER", 1.0f);
  1071. }
  1072. }
  1073. }
  1074. //Similar to Eyeguy, but it bounces around the level.
  1075. public class BouncerGuy : EyeGuy
  1076. {
  1077. public BouncerGuy(PointF pPosition)
  1078. : this(pPosition, new SizeF(16, 16))
  1079. {
  1080. }
  1081. public BouncerGuy(PointF pPosition, SizeF pSize)
  1082. : this(pPosition, pSize, new PointF(2.5f, 2.5f))
  1083. {
  1084. }
  1085. public BouncerGuy(PointF pPosition, SizeF pSize, PointF pVelocity):base()
  1086. {
  1087. HitPoints *= 2;
  1088. //setup code is similar to the EyeGuy...
  1089. DoBlockChecks = false;
  1090. counterdelay = 8;
  1091. //choose a Colour
  1092. ColorMatrix[] possiblecolours = new ColorMatrix[] { BASeBlock.ColorMatrices.GetColourizer(3, 1, 1), BASeBlock.ColorMatrices.GetColourizer(1, 1, 3), ColorMatrices.GetColourizer(1, 3, 1) };
  1093. Location = pPosition;
  1094. DrawSize = pSize;
  1095. VelocityUse = pVelocity;
  1096. counterdelay = 5;
  1097. if (BCBlockGameState.rgen.NextDouble() > 0.25)
  1098. {
  1099. DrawAttributes = new ImageAttributes();
  1100. ColorMatrix rndchosen = possiblecolours[BCBlockGameState.rgen.Next(0, possiblecolours.Length)];
  1101. DrawAttributes.SetColorMatrix(rndchosen);
  1102. }
  1103. StateFrameImageKeys = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
  1104. StateFrameImageKeys.Add("idle", new string[] { "bouncer_closed" });
  1105. StateFrameImageKeys.Add("opening", new string[] { "bouncer_closed", "bouncer_mid", "bouncer_open" });
  1106. StateFrameImageKeys.Add("active", new string[] { "bouncer_open" });
  1107. StateFrameImageKeys.Add("dying", new string[] { "bouncer_open" });
  1108. StateFrameImageKeys.Add("closing", new string[] { "bouncer_open", "bouncer_mid", "bouncer_closed" });
  1109. StateFrameIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  1110. StateFrameIndex.Add("idle", 0);
  1111. StateFrameIndex.Add("active", 0);
  1112. StateFrameIndex.Add("opening", 0);
  1113. StateFrameIndex.Add("closing", 0);
  1114. StateFrameIndex.Add("dying", 0);
  1115. FrameDelayTimes = new Dictionary<string, int[]>(StringComparer.OrdinalIgnoreCase);
  1116. FrameDelayTimes.Add("idle", new int[] { 25 });
  1117. FrameDelayTimes.Add("opening", new int[] { 15, 15, 15 });
  1118. FrameDelayTimes.Add("active", new int[] { 5 });
  1119. FrameDelayTimes.Add("closing", new int[] { 15, 15, 15 });
  1120. FrameDelayTimes.Add("dying", new int[] { 25 });
  1121. }
  1122. public override string ToString()
  1123. {
  1124. return "@" + Location.ToString() + " Width:" + DrawSize + " Velocity:" + VelocityUse;
  1125. }
  1126. protected override RectangleF getBoundary(BCBlockGameState mgamestate)
  1127. {
  1128. return mgamestate.GameArea;
  1129. }
  1130. public override int GetScoreValue()
  1131. {
  1132. return 720;
  1133. }
  1134. protected override void BallImpact(cBall ballcheck, Block.BallRelativeConstants ballrel)
  1135. {
  1136. if (((ballrel & Block.BallRelativeConstants.Relative_Right) == Block.BallRelativeConstants.Relative_Right) ||
  1137. (ballrel & Block.BallRelativeConstants.Relative_Left) == Block.BallRelativeConstants.Relative_Left)
  1138. {
  1139. //horizontal; flip x velocity.
  1140. VelocityUse = new PointF(-VelocityUse.X, VelocityUse.Y);
  1141. }
  1142. if (((ballrel & Block.BallRelativeConstants.Relative_Down) == Block.BallRelativeConstants.Relative_Down) ||
  1143. (ballrel & Block.BallRelativeConstants.Relative_Up) == Block.BallRelativeConstants.Relative_Up)
  1144. {
  1145. VelocityUse = new PointF(VelocityUse.X, -VelocityUse.Y);
  1146. }
  1147. }
  1148. protected override void ChangeVelocity(BCBlockGameState gstate)
  1149. {
  1150. int xSign = Math.Sign(VelocityUse.X), ySign = Math.Sign(VelocityUse.Y);
  1151. float distanceRight, distanceBottom;
  1152. float distanceTop = Math.Abs(Location.Y - gstate.GameArea.Top);
  1153. distanceBottom = Math.Abs(Location.Y - gstate.GameArea.Bottom);
  1154. float distanceLeft = Math.Abs(Location.X - gstate.GameArea.Left);
  1155. distanceRight = Math.Abs(Location.X - gstate.GameArea.Right);
  1156. float minval = (new[] { distanceTop, distanceLeft, distanceRight, distanceBottom }).Min();
  1157. if (distanceTop == minval)
  1158. {
  1159. ySign = 1;
  1160. }
  1161. else if (distanceBottom == minval)
  1162. ySign = -1;
  1163. else if (distanceLeft == minval)
  1164. {
  1165. xSign = 1;
  1166. }
  1167. else if (distanceRight == minval)
  1168. xSign = -1;
  1169. VelocityUse = new PointF(Math.Abs(VelocityUse.X) * xSign, Math.Abs(VelocityUse.Y) * ySign);
  1170. }
  1171. protected override float getRotationAmount()
  1172. {
  1173. return 0;
  1174. }
  1175. }
  1176. [Bossable]
  1177. public class QuadSpinner : EyeGuy
  1178. {
  1179. protected override void Shoot(BCBlockGameState gamestate)
  1180. {
  1181. //we don't shoot a fireball, instead we shoot a "bullet".
  1182. counterdelay = BCBlockGameState.rgen.Next(5, 50); //change counter, we shoot at random intervals.
  1183. var coreangle = (float)(((DrawRotation / 360) * (2 * Math.PI)));
  1184. for (int i = 0; i < 3; i++)
  1185. {
  1186. float useangle = (float)(coreangle + ((Math.PI / 2) * (float)i));
  1187. float UseVelocity = 4;
  1188. //calculate the velocity....
  1189. PointF projectilespeed = new PointF((float)(Math.Cos(useangle) * UseVelocity), (float)(Math.Sin(useangle) * UseVelocity));
  1190. projectilespeed = new PointF(projectilespeed.X + base.VelocityUse.X, projectilespeed.Y + base.VelocityUse.Y);
  1191. Bullet shootbullet = new Bullet(GetRectangle().CenterPoint(), projectilespeed);
  1192. shootbullet.Owner = this;
  1193. shootbullet.DamagePaddle = true;
  1194. gamestate.Defer(() => gamestate.GameObjects.AddLast(shootbullet));
  1195. }
  1196. BCBlockGameState.Soundman.PlaySound("SPINSHOOT", 1.0f);
  1197. }
  1198. void QuadSpinnerGuy_OnDeath(Object sender, EnemyDeathEventArgs e)
  1199. {
  1200. var enemydied = e.EnemyDied;
  1201. var stateobject = e.StateObject;
  1202. //spawn a EyeGuy in the same location.
  1203. EyeGuy neweg = new EyeGuy(enemydied.Location, enemydied.DrawSize);
  1204. stateobject.QueueFrameEvent((n, q) => { stateobject.GameObjects.AddLast(neweg); return false; }, null);
  1205. }
  1206. public QuadSpinner(PointF pPosition,SizeF pSize)
  1207. {
  1208. //setup code is similar to the EyeGuy...
  1209. //choose a Colour
  1210. ColorMatrix[] possiblecolours = new ColorMatrix[] { BASeBlock.ColorMatrices.GetColourizer(3, 1, 1), BASeBlock.ColorMatrices.GetColourizer(1, 1, 3), ColorMatrices.GetColourizer(1, 3, 1) };
  1211. Location = pPosition;
  1212. DrawSize = pSize;
  1213. counterdelay = BCBlockGameState.rgen.Next(5,50);
  1214. if (BCBlockGameState.rgen.NextDouble() > 0.25)
  1215. {
  1216. DrawAttributes = new ImageAttributes();
  1217. ColorMatrix rndchosen = possiblecolours[BCBlockGameState.rgen.Next(0, possiblecolours.Length)];
  1218. DrawAttributes.SetColorMatrix(rndchosen);
  1219. }
  1220. StateFrameImageKeys = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
  1221. StateFrameImageKeys.Add("idle", new string[] { "quadshooter_closed" });
  1222. StateFrameImageKeys.Add("opening", new string[] { "quadshooter_closed", "quadshooter_mid", "quadshooter_open" });
  1223. StateFrameImageKeys.Add("active", new string[] { "quadshooter_open" });
  1224. StateFrameImageKeys.Add("dying", new string[] { "quadshooter_open" });
  1225. StateFrameImageKeys.Add("closing", new string[] { "quadshooter_open", "quadshooter_mid", "quadshooter_closed" });
  1226. StateFrameIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  1227. StateFrameIndex.Add("idle", 0);
  1228. StateFrameIndex.Add("active", 0);
  1229. StateFrameIndex.Add("opening", 0);
  1230. StateFrameIndex.Add("closing", 0);
  1231. StateFrameIndex.Add("dying", 0);
  1232. FrameDelayTimes = new Dictionary<string, int[]>(StringComparer.OrdinalIgnoreCase);
  1233. FrameDelayTimes.Add("idle", new int[] { 25 });
  1234. FrameDelayTimes.Add("opening", new int[] { 15, 15, 15 });
  1235. FrameDelayTimes.Add("active", new int[] { 5 });
  1236. FrameDelayTimes.Add("closing", new int[] { 15, 15, 15 });
  1237. FrameDelayTimes.Add("dying", new int[] { 25 });
  1238. base.VelocityUse = new PointF(3, 0);
  1239. OnDeath += QuadSpinnerGuy_OnDeath;
  1240. }
  1241. }
  1242. [Bossable]
  1243. public class SpinnerGuy : EyeGuy
  1244. {
  1245. //protected PointF VelocityUse = new PointF(0, 0);
  1246. public new static EyeGuy CreateBoss(PointF pPosition, BCBlockGameState mGameState)
  1247. {
  1248. SpinnerGuy spinnerboss = new SpinnerGuy(pPosition, new SizeF(64, 64));
  1249. spinnerboss.DoBlockChecks = false;
  1250. spinnerboss.idlewaittime = 20;
  1251. spinnerboss.counterdelay = 10;
  1252. spinnerboss.HitPoints *= 25;
  1253. //BCBlockGameState.Soundman.PlayMusic("D:\\mycovers\\thedecisivebattle.mp3", true);
  1254. spinnerboss.OnShoot += spinnerboss_OnShoot;
  1255. spinnerboss.OnDeath += spinnerboss_OnDeath;
  1256. return spinnerboss;
  1257. }
  1258. static void spinnerboss_OnDeath(Object sender,EnemyDeathEventArgs e)
  1259. {
  1260. //throw new NotImplementedException();
  1261. }
  1262. static int incspawn = 0;
  1263. static void spinnerboss_OnShoot(Object Sender,EyeGuyShootEventArgs<bool> e )
  1264. {
  1265. incspawn++;
  1266. if (incspawn == 100)
  1267. {
  1268. incspawn = 0;
  1269. PointF usebouncerspeed = e.GameState.AimAtPaddle(e.EnemyGuy.GetRectangleF().CenterPoint(), (float)(BCBlockGameState.rgen.NextDouble() * 3 + 2));
  1270. BouncerGuy bg = new BouncerGuy(e.EnemyGuy.GetRectangleF().CenterPoint(), new SizeF(16, 16), usebouncerspeed);
  1271. bg.HitPoints = 1;
  1272. bg.MaxHitPoints = 1;
  1273. e.GameState.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup((() => e.GameState.GameObjects.AddLast(bg))));
  1274. e.Result = true;
  1275. }
  1276. e.Result = false;
  1277. // throw new NotImplementedException();
  1278. }
  1279. public SpinnerGuy(PointF pPosition):this(pPosition,new SizeF(16,16))
  1280. {
  1281. }
  1282. public SpinnerGuy(PointF pPosition, SizeF usesize):base()
  1283. {
  1284. //setup code is similar to the EyeGuy...
  1285. //choose a Colour
  1286. ColorMatrix[] possiblecolours = new ColorMatrix[] { BASeBlock.ColorMatrices.GetColourizer(3, 1, 1), BASeBlock.ColorMatrices.GetColourizer(1, 1, 3), ColorMatrices.GetColourizer(1, 3, 1) };
  1287. Location = pPosition;
  1288. DrawSize = usesize;
  1289. counterdelay = 5;
  1290. if (BCBlockGameState.rgen.NextDouble() > 0.25)
  1291. {
  1292. DrawAttributes = new ImageAttributes();
  1293. ColorMatrix rndchosen = possiblecolours[BCBlockGameState.rgen.Next(0, possiblecolours.Length)];
  1294. DrawAttributes.SetColorMatrix(rndchosen);
  1295. }
  1296. StateFrameImageKeys = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
  1297. StateFrameImageKeys.Add("idle", new string[] { "spinshooter_closed" });
  1298. StateFrameImageKeys.Add("opening", new string[] { "spinshooter_closed", "spinshooter_mid", "spinshooter_open" });
  1299. StateFrameImageKeys.Add("active", new string[] { "spinshooter_open" });
  1300. StateFrameImageKeys.Add("dying", new string[] { "spinshooter_open" });
  1301. StateFrameImageKeys.Add("closing", new string[] { "spinshooter_open", "spinshooter_mid", "spinshooter_closed" });
  1302. StateFrameIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  1303. StateFrameIndex.Add("idle", 0);
  1304. StateFrameIndex.Add("active", 0);
  1305. StateFrameIndex.Add("opening", 0);
  1306. StateFrameIndex.Add("closing", 0);
  1307. StateFrameIndex.Add("dying", 0);
  1308. FrameDelayTimes = new Dictionary<string, int[]>(StringComparer.OrdinalIgnoreCase);
  1309. FrameDelayTimes.Add("idle", new int[] { 25 });
  1310. FrameDelayTimes.Add("opening", new int[] { 15, 15, 15 });
  1311. FrameDelayTimes.Add("active", new int[] { 5 });
  1312. FrameDelayTimes.Add("closing", new int[] { 15, 15, 15 });
  1313. FrameDelayTimes.Add("dying", new int[] { 25 });
  1314. base.VelocityUse = new PointF(3, 0);
  1315. OnDeath += SpinnerGuy_OnDeath;
  1316. }
  1317. public override int GetScoreValue()
  1318. {
  1319. return 320;
  1320. }
  1321. void SpinnerGuy_OnDeath(Object sender,EnemyDeathEventArgs e)
  1322. {
  1323. var enemydied = e.EnemyDied;
  1324. var stateobject = e.StateObject;
  1325. //spawn a EyeGuy in the same location.
  1326. EyeGuy neweg = new EyeGuy(enemydied.Location, enemydied.DrawSize);
  1327. stateobject.QueueFrameEvent((n, q) => { stateobject.GameObjects.AddLast(neweg); return false; },null);
  1328. }
  1329. protected override void Shoot(BCBlockGameState gamestate)
  1330. {
  1331. //we don't shoot a fireball, instead we shoot a "bullet".
  1332. float UseVelocity = 4;
  1333. float useangle = (float)((DrawRotation/360)*(2*Math.PI));
  1334. //calculate the velocity....
  1335. PointF projectilespeed = new PointF((float)(Math.Cos(useangle) * UseVelocity), (float)(Math.Sin(useangle) * UseVelocity));
  1336. projectilespeed = new PointF(projectilespeed.X + base.VelocityUse.X, projectilespeed.Y + base.VelocityUse.Y);
  1337. Bullet shootbullet = new Bullet(GetRectangle().CenterPoint(), projectilespeed);
  1338. shootbullet.Owner = this;
  1339. shootbullet.DamagePaddle = true;
  1340. gamestate.Defer(() => gamestate.GameObjects.AddLast(shootbullet));
  1341. BCBlockGameState.Soundman.PlaySound("SPINSHOOT", 1.0f);
  1342. }
  1343. public override bool PerformFrame(BCBlockGameState gamestate)
  1344. {
  1345. return base.PerformFrame(gamestate);
  1346. }
  1347. }
  1348. public class EyeGuyShootEventArgs<T> : EventArgs
  1349. {
  1350. //Func<BCBlockGameState, EyeGuy, bool>
  1351. private BCBlockGameState _gstate;
  1352. private EyeGuy _eyeguy;
  1353. private T _Result;
  1354. public BCBlockGameState GameState { get { return _gstate; } set { _gstate = value; } }
  1355. public EyeGuy EnemyGuy { get { return _eyeguy; } set { _eyeguy = value; } }
  1356. public T Result { get { return _Result; } set { _Result = value; } }
  1357. public EyeGuyShootEventArgs(BCBlockGameState pGameState,EyeGuy pEnemy)
  1358. {
  1359. _gstate = pGameState;
  1360. EnemyGuy=pEnemy;
  1361. }
  1362. }
  1363. [Bossable]
  1364. public class EyeGuy : GameEnemy,iImagable,iSizedProjectile
  1365. {
  1366. public event EventHandler<EyeGuyShootEventArgs<bool>> OnShoot = null;
  1367. public static EyeGuy CreateBoss(PointF pPosition,BCBlockGameState mGameState)
  1368. {
  1369. EyeGuy eyeboss = new EyeGuy(pPosition, new SizeF(64, 64));
  1370. eyeboss.DoBlockChecks = false;
  1371. eyeboss.idlewaittime = 20;
  1372. eyeboss.counterdelay = 25;
  1373. eyeboss.HitPoints *= 15;
  1374. //BCBlockGameState.Soundman.PlayMusic("D:\\mycovers\\thedecisivebattle.mp3", true);
  1375. eyeboss.OnShoot += eyeboss_OnShoot;
  1376. eyeboss.OnDeath += eyeboss_OnDeath;
  1377. return eyeboss;
  1378. }
  1379. static int incspawn = 0;
  1380. static void eyeboss_OnShoot(Object Sender,EyeGuyShootEventArgs<bool> e )
  1381. {
  1382. incspawn++;
  1383. if (incspawn == 100)
  1384. {
  1385. incspawn = 0;
  1386. PointF usebouncerspeed = e.GameState.AimAtPaddle(e.EnemyGuy.GetRectangleF().CenterPoint(), (float)(BCBlockGameState.rgen.NextDouble() * 3 + 2));
  1387. BouncerGuy bg = new BouncerGuy(e.EnemyGuy.GetRectangleF().CenterPoint(), new SizeF(16, 16), usebouncerspeed);
  1388. bg.HitPoints = 1;
  1389. bg.MaxHitPoints = 1;
  1390. e.GameState.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup((() => e.GameState.GameObjects.AddLast(bg))));
  1391. e.Result = true;
  1392. }
  1393. e.Result = false;
  1394. }
  1395. iActiveSoundObject revertmusic = null;
  1396. static void eyeboss_OnDeath(Object sender, EnemyDeathEventArgs e)
  1397. {
  1398. }
  1399. private bool FireOnShoot(BCBlockGameState gstate, EyeGuy Sender)
  1400. {
  1401. var temp = OnShoot;
  1402. if (temp == null) return false;
  1403. {
  1404. EyeGuyShootEventArgs<bool> e = new EyeGuyShootEventArgs<bool>(gstate, Sender);
  1405. temp(this, e);
  1406. //return temp(gstate, Sender);
  1407. return e.Result;
  1408. }
  1409. }
  1410. public EyeGuy(PointF Position)
  1411. : this(Position, new SizeF(16, 16))
  1412. {
  1413. }
  1414. protected EyeGuy():base(PointF.Empty,null,5)
  1415. {
  1416. }
  1417. public EyeGuy(PointF Position, SizeF usesize)
  1418. : base(Position, null, 50)
  1419. {
  1420. //choose a Colour
  1421. ColorMatrix[] possiblecolours = new ColorMatrix[] { BASeBlock.ColorMatrices.GetColourizer(3, 1, 1), BASeBlock.ColorMatrices.GetColourizer(1, 1, 3), ColorMatrices.GetColourizer(1, 3, 1) };
  1422. if (BCBlockGameState.rgen.NextDouble() > 0.25)
  1423. {
  1424. DrawAttributes = new ImageAttributes();
  1425. ColorMatrix rndchosen = possiblecolours[BCBlockGameState.rgen.Next(0, possiblecolours.Length)];
  1426. DrawAttributes.SetColorMatrix(rndchosen);
  1427. }
  1428. VelocityUse = new PointF(3, 0);
  1429. DrawSize = usesize;
  1430. StateFrameImageKeys = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
  1431. StateFrameImageKeys.Add("idle", new string[] { "eyeclose" });
  1432. StateFrameImageKeys.Add("opening", new string[] { "eyeclose", "eyemid", "eyeopen" });
  1433. StateFrameImageKeys.Add("active", new string[] { "eyeopen" });
  1434. StateFrameImageKeys.Add("dying", new string[] { "eyeopen" });
  1435. StateFrameImageKeys.Add("closing", new string[] { "eyeopen", "eyemid", "eyeclose" });
  1436. StateFrameIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
  1437. StateFrameIndex.Add("idle", 0);
  1438. StateFrameIndex.Add("active", 0);
  1439. StateFrameIndex.Add("opening", 0);
  1440. StateFrameIndex.Add("closing", 0);
  1441. StateFrameIndex.Add("dying", 0);
  1442. FrameDelayTimes = new Dictionary<string, int[]>(StringComparer.OrdinalIgnoreCase);
  1443. FrameDelayTimes.Add("idle", new int[] { 25 });
  1444. FrameDelayTimes.Add("opening", new int[] { 15, 15, 15 });
  1445. FrameDelayTimes.Add("active", new int[] { 5 });
  1446. FrameDelayTimes.Add("closing", new int[] { 15, 15, 15 });
  1447. FrameDelayTimes.Add("dying", new int[] { 25 });
  1448. }
  1449. protected int _idlewaittime = 150;
  1450. protected PointF VelocityUse = new PointF(0, 0);
  1451. protected int idlecounter = 0;
  1452. protected int shootcounter = 0;
  1453. protected int _counterdelay = 30;
  1454. public int counterdelay { get { return _counterdelay; } set { _counterdelay = value; } }
  1455. public int idlewaittime { get { return _idlewaittime; } set { _idlewaittime = value; } }
  1456. private PointF GetCenter()
  1457. {
  1458. return new PointF(Location.X + DrawSize.Width / 2, Location.Y + DrawSize.Height / 2);
  1459. }
  1460. public override int GetScoreValue()
  1461. {
  1462. return 300;
  1463. }
  1464. private Type[] _ShootTypes = new Type[]{typeof(Fireball),typeof(BlueFireball)};
  1465. private float[] _ShootProbability = new float[] { 8, 2 };
  1466. public Type[] ShootTypes
  1467. {
  1468. get { return _ShootTypes; }
  1469. set
  1470. {
  1471. if (!value.All((w) => w is iProjectile))
  1472. throw new ArgumentException("All types in provided array must implement iProjectile.");
  1473. _ShootTypes = value;
  1474. }
  1475. }
  1476. public float[] ShootProbability
  1477. {
  1478. get
  1479. {
  1480. if (_ShootProbability.Length != _ShootTypes.Length)
  1481. Array.Resize(ref _ShootProbability, _ShootTypes.Length);
  1482. return _ShootProbability;
  1483. }
  1484. set
  1485. {
  1486. _ShootProbability = value;
  1487. }
  1488. }
  1489. protected Type ChooseShootType()
  1490. {
  1491. return BCBlockGameState.Select(_ShootTypes, ShootProbability);
  1492. }
  1493. private iProjectile SpawnProjectile(PointF pPosition, SizeF psize, PointF pVelocity)
  1494. {
  1495. Type usetype = ChooseShootType();
  1496. //valid constructor: PointF, PointF,SizeF (location,speed,size) and also lacking Size parameter.
  1497. var ip = (iProjectile)Activator.CreateInstance(usetype, pPosition, pVelocity);
  1498. //set size if it is a sized projectile.
  1499. if (ip is iSizedProjectile) ((iSizedProjectile)ip).Size = psize;
  1500. return ip;
  1501. }
  1502. /// <summary>
  1503. /// called to, uh... shoot. EyeGuy default implementation shoots a fireball towards the paddle.
  1504. /// </summary>
  1505. /// <param name="gamestate"></param>
  1506. protected virtual void Shoot(BCBlockGameState gamestate)
  1507. {
  1508. if (FireOnShoot(gamestate, this)) return;
  1509. float randomspeed = 2;
  1510. randomspeed += (float)(BCBlockGameState.rgen.NextDouble() * 4);
  1511. PointF choosevelocity = gamestate.AimAtPaddle(Location, randomspeed);
  1512. iProjectile Shotitem = SpawnProjectile(Location, new SizeF(8,8), choosevelocity);
  1513. /*
  1514. if (BCBlockGameState.rgen.NextDouble() > 0.2)
  1515. {
  1516. addobjects.Add(new Fireball(gamestate, GetCenter(), new SizeF(8, 8), randomspeed));
  1517. }
  1518. else
  1519. {
  1520. addobjects.Add(new BlueFireball(gamestate, GetCenter(), new SizeF(8, 8), randomspeed));
  1521. }
  1522. * */
  1523. gamestate.Defer(() =>
  1524. {
  1525. gamestate.GameObjects.AddLast(Shotitem as GameObject);
  1526. BCBlockGameState.Soundman.PlaySound("spitfire", 1.0f);
  1527. });
  1528. }
  1529. private bool dodestroyblocks = false;
  1530. protected virtual void ChangeVelocity(BCBlockGameState gamestate)
  1531. {
  1532. VelocityUse = new PointF(-VelocityUse.X, -VelocityUse.Y);
  1533. }
  1534. protected virtual RectangleF getBoundary(BCBlockGameState mgamestate)
  1535. {
  1536. if (mgamestate.PlayerPaddle == null) return mgamestate.GameArea;
  1537. return new RectangleF(mgamestate.GameArea.Left, mgamestate.GameArea.Top, mgamestate.GameArea.Width, mgamestate.PlayerPaddle.Getrect().ToRectangleF().Top);
  1538. }
  1539. public bool DoBlockChecks = true;
  1540. protected virtual void BallImpact(cBall ballcheck,Block.BallRelativeConstants ballrel)
  1541. {
  1542. float minmagnitude = ballcheck.getMagnitude();
  1543. //new velocity should be set....
  1544. //if we are moving, add our velocity.
  1545. //needs more work, velocity of the ball needs
  1546. //to be changed more "realistically"...
  1547. if ((ballrel & Block.BallRelativeConstants.Relative_Left) == Block.BallRelativeConstants.Relative_Left)
  1548. {
  1549. VelocityUse = new PointF(minmagnitude, 0);
  1550. }
  1551. else if ((ballrel & Block.BallRelativeConstants.Relative_Right) == Block.BallRelativeConstants.Relative_Right)
  1552. {
  1553. VelocityUse = new PointF(-minmagnitude, 0);
  1554. }
  1555. else if ((ballrel & Block.BallRelativeConstants.Relative_Up) == Block.BallRelativeConstants.Relative_Up)
  1556. {
  1557. VelocityUse = new PointF(0, minmagnitude);
  1558. }
  1559. else if ((ballrel & Block.BallRelativeConstants.Relative_Down) == Block.BallRelativeConstants.Relative_Down)
  1560. {
  1561. VelocityUse = new PointF(0, -minmagnitude);
  1562. }
  1563. if (EnemyAction == "active")
  1564. {
  1565. ballcheck.Velocity = new PointF(ballcheck.Velocity.X + VelocityUse.X, ballcheck.Velocity.Y + VelocityUse.Y);
  1566. //also, depending on the direction we were hit, modify our direction.
  1567. //make sure we are going <at LEAST> minmagnitude...
  1568. if (ballcheck.getMagnitude() <= minmagnitude)
  1569. {
  1570. double gotangle = BCBlockGameState.GetAngle(new PointF(0, 0), ballcheck.Velocity);
  1571. ballcheck.Velocity = BCBlockGameState.GetVelocity(minmagnitude, gotangle);
  1572. }
  1573. ballcheck.invokeballimpact(ballcheck);
  1574. }
  1575. }
  1576. public override bool PerformFrame(BCBlockGameState gamestate)
  1577. {
  1578. //very first: check for ball impacts.
  1579. #region ball checks
  1580. //first, create a rectangle that extends out from this "eyeguy"...
  1581. RectangleF currrect = GetRectangleF();
  1582. Rectangle extendedRectangle = currrect.ToRectangle();
  1583. //RectangleF extendedRectangle = new RectangleF(Location.X - currrect.Width, Location.Y - currrect.Height / 2, currrect.Width * 2, currrect.Height * 2);
  1584. foreach (cBall Checkball in (from n in gamestate.Balls where extendedRectangle.Contains(n.Location.ToPoint()) select n))
  1585. {
  1586. //determine if Checkball touches us.
  1587. Block.BallRelativeConstants ballrel;
  1588. if (BCBlockGameState.CheckImpact(gamestate, currrect, Checkball, out ballrel))
  1589. {
  1590. HitPoints -= (int)(Math.Ceiling(Checkball.getMagnitude()));
  1591. if (HitPoints < 0)
  1592. {
  1593. EnemyAction = "dying";
  1594. }
  1595. BallImpact(Checkball, ballrel);
  1596. }
  1597. }
  1598. #endregion
  1599. if (DoBlockChecks)
  1600. {
  1601. #region block checks
  1602. List<Block> removethese = new List<Block>();
  1603. foreach (Block checkblock in (from n in gamestate.Blocks where n.HitTest(currrect) select n))
  1604. {
  1605. //destroy them...
  1606. //spawn a shiteload of particles.
  1607. PointF middlespot = checkblock.CenterPoint();
  1608. var closed = checkblock;
  1609. lock (gamestate.Blocks)
  1610. {
  1611. //checkblock.StandardSpray(gamestate);
  1612. //removethese.Add(checkblock);
  1613. gamestate.Defer(() =>
  1614. {
  1615. BCBlockGameState.Block_Hit(gamestate, closed);
  1616. gamestate.Blocks.Remove(closed);
  1617. });
  1618. }
  1619. gamestate.Forcerefresh = true;
  1620. }
  1621. //iterate and remove all 'removethese' ...
  1622. gamestate.Defer(() =>
  1623. {
  1624. foreach (Block removethis in removethese)
  1625. {
  1626. var copied = removethis;
  1627. gamestate.Blocks.Remove(copied);
  1628. }
  1629. });
  1630. #endregion
  1631. }
  1632. switch (EnemyAction)
  1633. {
  1634. case "idle":
  1635. idlecounter++;
  1636. if (idlecounter >= idlewaittime)
  1637. {
  1638. //if(Location.X > (gamestate.GameArea.Width/2))
  1639. ChangeVelocity(gamestate);
  1640. idlecounter = 0;
  1641. EnemyAction = "opening";
  1642. }
  1643. break;
  1644. case "opening":
  1645. //no special handling, we'll move forward as needed in the framesetcomplete routine.
  1646. break;
  1647. case "active":
  1648. //are we touching the edge of the gamearea?
  1649. DrawRotation += getRotationAmount();
  1650. shootcounter++;
  1651. if (shootcounter >= counterdelay)
  1652. {
  1653. shootcounter = 0;
  1654. Shoot(gamestate);
  1655. }
  1656. //Location = new PointF(Location.X + VelocityUse.X, Location.Y + VelocityUse.Y);
  1657. RectangleF useboundary = getBoundary(gamestate);
  1658. BCBlockGameState.IncrementLocation(gamestate, ref _Location, VelocityUse);
  1659. #region X boundary check
  1660. if (Math.Sign(VelocityUse.X) == 1)
  1661. {
  1662. if (((Location.X + VelocityUse.X + GetRectangleF().Width) > useboundary.Right))
  1663. {
  1664. EnemyAction = "closing";
  1665. Location = new PointF(useboundary.Right - GetRectangleF().Width, Location.Y);
  1666. //set to right edge.
  1667. }
  1668. }
  1669. else if (Math.Sign(VelocityUse.X) == -1)
  1670. {
  1671. if ((Location.X + VelocityUse.X < useboundary.Left))
  1672. {
  1673. EnemyAction = "closing";
  1674. //set to left edge
  1675. Location = new PointF(useboundary.Left, Location.Y);
  1676. }
  1677. }
  1678. #endregion
  1679. #region Y boundary check
  1680. float Ymax = useboundary.Bottom;
  1681. if (Math.Sign(VelocityUse.Y) == 1)
  1682. {
  1683. if (((Location.Y + VelocityUse.Y + GetRectangleF().Height) > Ymax))
  1684. {
  1685. EnemyAction = "closing";
  1686. Location = new PointF(Location.X, Ymax - GetRectangleF().Height);
  1687. //set to Bottom edge.
  1688. }
  1689. }
  1690. else if (Math.Sign(VelocityUse.Y) == -1)
  1691. {
  1692. if ((Location.Y + VelocityUse.Y < useboundary.Top))
  1693. {
  1694. EnemyAction = "closing";
  1695. //set to top edge
  1696. Location = new PointF(Location.X, useboundary.Top);
  1697. }
  1698. }
  1699. #endregion
  1700. break;
  1701. case "closing":
  1702. break; //no special handling
  1703. }
  1704. return base.PerformFrame(gamestate);
  1705. }
  1706. protected virtual float getRotationAmount()
  1707. {
  1708. return 5;
  1709. }
  1710. public override void OnFramesetComplete(BCBlockGameState gamestate)
  1711. {
  1712. Trace.WriteLine("Frameset Complete:" + EnemyAction);
  1713. if (EnemyAction == "dying")
  1714. {
  1715. Debug.Print("Break");
  1716. }
  1717. if (EnemyAction == "opening")
  1718. {
  1719. EnemyAction = "active";
  1720. }
  1721. else if (EnemyAction == "closing")
  1722. EnemyAction = "idle";
  1723. else if (EnemyAction == "dying")
  1724. {
  1725. //removeobjects.Add(this);
  1726. gamestate.Defer(()=>gamestate.GameObjects.Remove(this));
  1727. //also... add lots of particles.
  1728. //AND... and... and explosion sound.
  1729. BCBlockGameState.Soundman.PlaySound("gren");
  1730. for (int i = 0; i < 50; i++)
  1731. {
  1732. Random rg = BCBlockGameState.rgen;
  1733. PointF genpoint = new PointF((float)(Location.X + (rg.NextDouble() * GetSize().Width)), (float)(Location.Y + (rg.NextDouble() * GetSize().Height)));
  1734. DustParticle dp = new DustParticle(genpoint, 5f);
  1735. gamestate.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup(() => gamestate.Particles.Add(dp)));
  1736. gamestate.NextFrameCalls.Enqueue(new BCBlockGameState.NextFrameStartup(() =>
  1737. {
  1738. var iteratethis = PolyDebris.Fragment(this, 5, null);
  1739. foreach (var iterate in iteratethis)
  1740. {
  1741. gamestate.Particles.Add(iterate);
  1742. }
  1743. }));
  1744. }
  1745. //tweak: Feb 19th 2012: make a small explosioneffect now too.
  1746. ExplosionEffect kerplosion = new ExplosionEffect(Location, 40);
  1747. //remember to add it to the AddObject ref parameter...
  1748. gamestate.Defer(() => gamestate.GameObjects.AddLast(kerplosion));
  1749. }
  1750. }
  1751. #region iImagable Members
  1752. void iImagable.Draw(Graphics g)
  1753. {
  1754. this.Draw(g);
  1755. }
  1756. Size iImagable.Size
  1757. {
  1758. get
  1759. {
  1760. return new Size((int)DrawSize.Width, (int)DrawSize.Height);
  1761. }
  1762. set
  1763. {
  1764. DrawSize = new SizeF(value.Width, value.Height);
  1765. }
  1766. }
  1767. Point iImagable.Location
  1768. {
  1769. get
  1770. {
  1771. return new Point((int)Location.X, (int)Location.Y);
  1772. }
  1773. set
  1774. {
  1775. Location = new PointF(value.X, value.Y);
  1776. }
  1777. }
  1778. Rectangle iImagable.getRectangle()
  1779. {
  1780. return GetRectangle();
  1781. }
  1782. #endregion
  1783. public PointF Velocity { get { return VelocityUse; } set { VelocityUse = value; } }
  1784. public SizeF Size { get { return DrawSize; } set{_DrawSize=value;} }
  1785. }
  1786. public class ChomperEnemy : GameEnemy
  1787. {
  1788. protected enum MouthAnimationDirectionConstants
  1789. {
  1790. Mouth_Opening,
  1791. Mouth_Closing
  1792. }
  1793. const float MaxMouthAngle = 90f;
  1794. const float MinMouthAngle = 0;
  1795. private float ChomperRadius;
  1796. private float Mouthmovespeed = 10f;
  1797. private float _MouthDirectionAngle = 0f;
  1798. private float Speed = 2;
  1799. private Brush _Fill = Brushes.Yellow;
  1800. private Pen _Draw = Pens.Black;
  1801. //protected MouthAnimationDirectionConstants MouthAnimationDirection=MouthAnimationDirectionConstants.Mouth_Closing;
  1802. protected float CurrentMouthAngle = 45f;
  1803. public float MouthDirectionAngle { get { return _MouthDirectionAngle; } set { _MouthDirectionAngle = value; refreshVelocity(); } }
  1804. private void refreshVelocity()
  1805. {
  1806. _Velocity = new PointF((float)Math.Cos(ToRadians(MouthDirectionAngle)) * Speed,
  1807. (float)Math.Sin(ToRadians(MouthDirectionAngle)) * Speed);
  1808. }
  1809. protected MouthAnimationDirectionConstants MouthAnimationDirection
  1810. {
  1811. get
  1812. {
  1813. if (Math.Sign(Mouthmovespeed) == 1)
  1814. return MouthAnimationDirectionConstants.Mouth_Opening;
  1815. else
  1816. return MouthAnimationDirectionConstants.Mouth_Closing;
  1817. }
  1818. }
  1819. public ChomperEnemy(PointF pLocation, float Radius)
  1820. : base(pLocation, null, 0)
  1821. {
  1822. Location = pLocation;
  1823. ChomperRadius = Radius;
  1824. Speed = 2;
  1825. MouthDirectionAngle = 0;
  1826. }
  1827. private void EmitPacSound()
  1828. {
  1829. BCBlockGameState.Soundman.PlaySound("wakka", 0.5f);
  1830. }
  1831. private void MoveChomper(BCBlockGameState gstate)
  1832. {
  1833. if(gstate.Balls.Count > 1)
  1834. BCBlockGameState.IncrementLocation(gstate, ref _Location, Velocity);
  1835. }
  1836. private PointF _Velocity;
  1837. public PointF Velocity
  1838. {
  1839. get
  1840. {
  1841. return _Velocity;
  1842. }
  1843. set
  1844. {
  1845. _Velocity = value;
  1846. }
  1847. }
  1848. private static float ToRadians(float degrees)
  1849. {
  1850. return (float)(degrees * (Math.PI / 180));
  1851. }
  1852. private static float ToDegrees(float Radians)
  1853. {
  1854. return (float)(Radians / (Math.PI / 180));
  1855. }
  1856. public Rectangle ChomperRectangle()
  1857. {
  1858. return new Rectangle((int)(Location.X - ChomperRadius), (int)(Location.Y - ChomperRadius), (int)(ChomperRadius * 2), (int)(ChomperRadius * 2));
  1859. }
  1860. int framecounter = 0;
  1861. cBall closeball = null;
  1862. public override bool PerformFrame(BCBlockGameState gamestate)
  1863. {
  1864. framecounter++;
  1865. if (framecounter == 5)
  1866. {
  1867. framecounter = 0;
  1868. //determine the closest ball to the Chomper...
  1869. cBall gotmin = null;
  1870. float currentmin = 32768;
  1871. float getmin = 0;
  1872. foreach (cBall loopball in gamestate.Balls)
  1873. {
  1874. getmin = BCBlockGameState.Distance(Location.X, Location.Y, loopball.Location.X, loopball.Location.Y);
  1875. if (gotmin == null || (getmin < currentmin))
  1876. {
  1877. gotmin = loopball;
  1878. currentmin = getmin;
  1879. }
  1880. }
  1881. if (gotmin != null && !Dying)
  1882. {
  1883. //gotmin is the closest;
  1884. //move towards it.
  1885. closeball = gotmin;
  1886. //if it is closer then our radius, AND the angle between it and the chomper are less them then the maximum mouth opening stance then "eat" the ball.
  1887. float baseradians = (float)BCBlockGameState.GetAngle(Location, gotmin.Location);
  1888. double AngleDifference = BCBlockGameState.AngleDifference(MouthDirectionAngle, baseradians);
  1889. PointF result = BCBlockGameState.NudgeTowards(Location,Velocity,gotmin.Location,(float)((Math.PI)/10));
  1890. float fixradians = (float)BCBlockGameState.GetAngle(new PointF(0, 0), result);
  1891. if (Single.IsNaN(fixradians))
  1892. {
  1893. fixradians = ToRadians(MouthDirectionAngle);
  1894. }
  1895. float useanglex = fixradians;
  1896. MouthDirectionAngle = ToDegrees(fixradians);
  1897. /*
  1898. float fixradians = baseradians;
  1899. float useangle = ToDegrees(baseradians);
  1900. float useanglex = fixradians;
  1901. //double anglevary = Math.Sign(anglebetween - movementangle) * (Math.PI / 90) * 0.25f;
  1902. MouthDirectionAngle += Math.Sign(useangle - MouthDirectionAngle) * 10;
  1903. //int signuse = Math.Sign(MouthDirectionAngle - useangle);
  1904. //MouthDirectionAngle -= (signuse * 10);
  1905. */
  1906. if (currentmin < ChomperRadius && currentmin > 0)
  1907. {
  1908. if ((MouthDirectionAngle - useanglex) < (MouthDirectionAngle + (MaxMouthAngle / 2)))
  1909. {
  1910. //eat the ball.
  1911. if (!EatBall(gamestate, gotmin))
  1912. ElasticWith(gotmin);
  1913. return false;
  1914. }
  1915. else
  1916. {
  1917. //reflect the ball "away" from the chomper.
  1918. ElasticWith(gotmin);
  1919. }
  1920. }
  1921. }
  1922. }
  1923. if (Dying) Mouthmovespeed = Math.Abs(Mouthmovespeed);
  1924. bool returnvalue = false;
  1925. CurrentMouthAngle += Mouthmovespeed;
  1926. if (!Dying)
  1927. {
  1928. if (CurrentMouthAngle < MinMouthAngle)
  1929. {
  1930. EmitPacSound();
  1931. Mouthmovespeed *= -1;
  1932. CurrentMouthAngle = MinMouthAngle;
  1933. }
  1934. if (CurrentMouthAngle > MaxMouthAngle)
  1935. {
  1936. EmitPacSound();
  1937. Mouthmovespeed *= -1;
  1938. CurrentMouthAngle = MaxMouthAngle;
  1939. }
  1940. MoveChomper(gamestate);
  1941. //return base.PerformFrame(gamestate, ref AddObjects, ref removeobjects);
  1942. //change the current Angle
  1943. returnvalue = !gamestate.GameArea.IntersectsWith(ChomperRectangle());
  1944. }
  1945. else
  1946. {
  1947. Mouthmovespeed = Math.Abs(Mouthmovespeed);
  1948. if (CurrentMouthAngle >= (Math.PI *.2f))
  1949. {
  1950. gamestate.Defer(() =>
  1951. {
  1952. for (int i = 0; i < 10; i++)
  1953. {
  1954. gamestate.Particles.Add(new PolyDebris(Location, 6, Color.Yellow, 3, 8, 3, 8));
  1955. }
  1956. });
  1957. return true;
  1958. }
  1959. }
  1960. return returnvalue;
  1961. }
  1962. private void ElasticWith(cBall ballbounce)
  1963. {
  1964. Polygon ballpoly = ballbounce.GetBallPoly();
  1965. Vector Adjustment = new Vector();
  1966. //create the poly from our circle.
  1967. EllipseBlock eb = new EllipseBlock(new RectangleF(Location.X-ChomperRadius,Location.Y-ChomperRadius,ChomperRadius*2,ChomperRadius*2));
  1968. Polygon ChomperPoly = eb.GetPoly();
  1969. GeometryHelper.PolygonCollisionResult pcr = GeometryHelper.PolygonCollision(ChomperPoly, ballpoly, new Vector(ballbounce.Velocity.X, ballbounce.Velocity.Y));
  1970. Adjustment = pcr.MinimumTranslationVector;
  1971. ballbounce.Velocity = ballbounce.Velocity.Mirror(pcr.MinimumTranslationVector);
  1972. ballbounce.Velocity = new PointF(ballbounce.Velocity.X, ballbounce.Velocity.Y);
  1973. ballbounce.Location = new PointF(ballbounce.Location.X - Adjustment.X, ballbounce.Location.Y - Adjustment.Y);
  1974. }
  1975. private bool Dying = false;
  1976. private bool EatBall(BCBlockGameState gamestate, cBall gotmin)
  1977. {
  1978. if (Dying) return false;
  1979. // throw new NotImplementedException();
  1980. if (gamestate.Balls.Count > 1)
  1981. {
  1982. gamestate.RemoveBalls.Add(gotmin);
  1983. ChomperRadius += gotmin.Radius*2;
  1984. if (ChomperRadius > 32)
  1985. {
  1986. Dying = true;
  1987. BCBlockGameState.Soundman.PlaySound("pacdie");
  1988. }
  1989. else
  1990. {
  1991. BCBlockGameState.Soundman.PlaySound("emerge");
  1992. }
  1993. return true;
  1994. }
  1995. return false;
  1996. }
  1997. public override void Draw(Graphics g)
  1998. {
  1999. //base.Draw(g);
  2000. float useRadius = ChomperRadius;
  2001. g.FillPie(_Fill, Location.X - useRadius, Location.Y - useRadius, useRadius * 2, useRadius * 2, MouthDirectionAngle + (CurrentMouthAngle / 2), 360 - (CurrentMouthAngle/2));
  2002. g.DrawPie(_Draw, Location.X - useRadius, Location.Y - useRadius, useRadius * 2, useRadius * 2, MouthDirectionAngle + (CurrentMouthAngle / 2), 360 - (CurrentMouthAngle/2));
  2003. //if (closeball != null)
  2004. //g.DrawLine(new Pen(Color.Black, 2), Location.X, Location.Y, closeball.Location.X, closeball.Location.Y);
  2005. }
  2006. }
  2007. }