PageRenderTime 80ms CodeModel.GetById 38ms 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

Large files files are truncated, but you can click here to view the full file

  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

Large files files are truncated, but you can click here to view the full file