public Game() : base(DefaultWidth, DefaultHeight) { InitializeRoutedActions(); InitializeSharedState(); SoundEnabled = true; Action <Sound> play = s => { if (CanvasOverlay.parent == null) { return; } if (SoundEnabled) { s.play(); } }; var Menu = new MenuSprite(DefaultWidth).AttachTo(base.InfoOverlay); Menu.TextExternalLink2.htmlText = LinkPlayMoreGames; var DefenseY = 420; #region DebugDump var DebugDump = new DebugDumpTextField(); DebugDump.Field.y = DefaultHeight / 4; DebugDump.Field.x = 0; DebugDump.Field.width = DefaultWidth; DebugDump.Field.height = DefaultHeight / 2; DebugDump.Visible.ValueChangedToTrue += delegate { Menu.TextExternalLink2.htmlText = LinkPoweredByJSC; DebugDump.Field.AttachToBefore(BorderOverlay); DebugDump.DebugDumpUpdate(); }; DebugDump.Visible.ValueChangedToFalse += delegate { Menu.TextExternalLink2.htmlText = LinkPlayMoreGames; }; #endregion RoutedActions.SendTextMessage.Direct = e => DebugDump.Write(new { Message = e }); this.Statusbar = new Statusbar(); Statusbar.Lives.Value = 3; Statusbar.Score.Value = 0; Statusbar.Element.AttachTo(InfoOverlay); var MenuFader = new DualFader { Value = Menu }; #region common keys this.InvokeWhenStageIsReady( delegate { stage.click += delegate { if (MenuFader.Value != CanvasOverlay) { MenuFader.Value = CanvasOverlay; } }; stage.keyUp += e => { if (e.keyCode == Keyboard.ENTER) { MenuFader.Value = CanvasOverlay; } if (e.keyCode == Keyboard.ESCAPE) { MenuFader.Value = Menu; } if (e.keyCode == Keyboard.T) { DebugDump.Visible.Toggle(); } if (e.keyCode == Keyboard.M) { SoundEnabled = !SoundEnabled; } if (e.keyCode == Keyboard.C) { play(Sounds.miu); foreach (var v in cloud1.Members) { DebugDump.Write(new { v.Element.x, v.Element.y }); } DebugDump.Write(new { PlaysSurvived = this.PlaysSurvived.Value }); } }; } ); #endregion this.Ego = new PlayerShip(DefaultWidth, DefaultHeight) { Name = "Ego" }; // addding our entities to list ensures we know under what id to send them this.Ego.AddTo(this.SharedState.LocalObjects); this.Ego.GoodEgo.AddTo(this.SharedState.LocalObjects); this.Ego.EvilEgo.AddTo(this.SharedState.LocalObjects); // our ego cannot be hit while the menu is showing this.Ego.GodMode.ValueChangedTo += GodMode => DebugDump.Write(new { GodMode }); // MenuFader.ValueChangedTo += e => this.Ego.GodMode.Value = e == Menu; // this.Ego.GodMode.Value = true; var ReportedScore = 0; #region lives and gameover Action <StarShip> ApplyEgoRespawn = xego => { var WaitingForRespawn = false; xego.IsAlive.ValueChangedToFalse += delegate { if (WaitingForRespawn) { return; } WaitingForRespawn = true; this.ApplyFilter(Filters.GrayScaleFilter); this.RoutedActions.SendTextMessage.Direct("waiting for respawn..."); if (PlayerInput != null) { PlayerInput.Enabled.Value = false; } Statusbar.Lives.Value--; if (Statusbar.Lives <= 0) { var ScoreMinStep = this.Statusbar.Score.Value / 30; 100.AtInterval( t => { var v = Math.Max((this.Statusbar.Score.Value - ScoreMinStep), 0); Statusbar.Score.Value = v; if (v == 0) { t.stop(); } } ); } 3100.AtDelayDo( delegate { this.RoutedActions.SendTextMessage.Direct("respawn!"); if (Statusbar.Lives == 0) { Statusbar.Lives.Value = 3; Statusbar.Score.Value = 0; PlaysSurvived.Value = 0; } if (Statusbar.Score.Value > 0) { this.RoutedActions.AddRankingScore.Chained(Statusbar.Score.Value - ReportedScore); ReportedScore = Statusbar.Score.Value; } WaitingForRespawn = false; this.RoutedActions.RestoreStarship.Chained(xego); this.filters = null; if (PlayerInput != null) { PlayerInput.Enabled.Value = true; } play(Sounds.insertcoin); } ); }; }; ApplyEgoRespawn(this.Ego.GoodEgo); ApplyEgoRespawn(this.Ego.EvilEgo); #endregion #region input RoutedActions.DoPlayerMovement.Direct += (e, p) => { e.GoodEgo.MoveToTarget.Value = p; }; Action <double, double> DoEgoPlayerMovement = (arc, length) => RoutedActions.DoPlayerMovement.Chained(Ego, Ego.GoodEgo.ToPoint().MoveToArc(arc, Ego.GoodEgo.MaxStep * length)); this.RoutedActions.RestoreStarship.Direct = s => { DebugDump.Write("restore starship: " + s.Name); s.GodMode.Value = true; s.TakeDamage(0); s.ApplyFilter(new BlurFilter()); s.alpha = 1; 2000.AtDelayDo( delegate { s.GodMode.Value = false; s.filters = null; if (this.CoPlayers.Any(k => s == k.GoodEgo)) { s.ApplyFilter(Filters.ColorFillFilter(0xff)); } } ); }; this.InvokeWhenStageIsReady( delegate { PlayerInput = new PlayerInput(stage, Ego, this) { StepLeft = () => DoEgoPlayerMovement(Math.PI, 2), StepLeftEnd = () => DoEgoPlayerMovement(Math.PI, 0.5), StepRight = () => DoEgoPlayerMovement(0, 2), StepRightEnd = () => DoEgoPlayerMovement(0, 0.5), FireBullet = () => Ego.FireBullet(), SmartMoveTo = (x, y) => { // ignore mouse while out of bounds if (x < 0) { return; } if (x > DefaultWidth) { return; } RoutedActions.DoPlayerMovement.Chained(Ego, new Point(Ego.Wrapper(x, y), Ego.GoodEgoY)); } }; PlayerInput.Enabled.ValueChangedTo += InputEnabled => { DebugDump.Write(new { InputEnabled }); }; } ); #endregion this.GroupEnemies.Add(this.Ego.EvilEgo); // hide menu for fast start // MenuFader.Value = CanvasOverlay; this.PlaysSurvived.ValueChangedTo += PlaysSurvived => DebugDump.Write(new { PlaysSurvived }); this.PlaysSurvived.ValueChangedTo += PlaysSurvived => { if (PlaysSurvived == 5) { this.RoutedActions.AddAchivementFiver.ChainedOnce(); } if (PlaysSurvived == 1) { this.RoutedActions.AddAchivementFirst.ChainedOnce(); } }; const int ClipMargin = 20; #region evilmode this.Ego.EvilMode.ValueChangedToTrue += delegate { play(Sounds.fade); #region keep ego here for 10 secs this.Ego.GoodEgo.Clip = p => { if (p.x < DefaultWidth / 2) { p.x = Math.Min(p.x, -ClipMargin); } else { p.x = Math.Max(p.x, DefaultWidth + ClipMargin); } return(p); }; 5000.AtDelayDo( delegate { this.Ego.GoodEgo.Clip = null; play(Sounds.fade); } ); #endregion }; this.Ego.EvilMode.ValueChangedToFalse += delegate { play(Sounds.insertcoin); }; #endregion this.Ego.EvilMode.LinkTo(Statusbar.EvilMode); #region evilmode indicator this.Ego.EvilMode.ValueChangedToTrue += delegate { this.filters = new[] { Filters.RedChannelFilter }; }; this.Ego.EvilMode.ValueChangedToFalse += delegate { this.filters = null; }; #endregion this.Ego.GoodEgo.FireBullet = RoutedActions.FireBullet; this.Ego.GoodEgo.AttachTo(CanvasOverlay); this.Ego.EvilEgo.FireBullet = RoutedActions.FireBullet; this.Ego.EvilEgo.AttachTo(CanvasOverlay); #region build shared defense buildings for (int i = 0; i < 4; i++) { var offset = DefaultWidth * (i * 2 + 1) / 8; foreach (var v in DefenseBlock.CreateDefenseArray(offset, DefenseY)) { v.AttachTo(CanvasOverlay); v.AddTo(DefenseBlocks); v.AddTo(FragileEntities.Items); // defense blocks like invaders cloud are shared this.SharedState.SharedObjects.Add(v); } } #endregion Ego.AddTo(FragileEntities); #region Create and Move CoPlayer RoutedActions.CreateCoPlayer.Direct = (user, handler) => { var cp1 = new PlayerShip(DefaultWidth, DefaultHeight) { Name = "CoPlayer" }.AddTo(CoPlayers); cp1.GoodEgo.AttachTo(CanvasOverlay); cp1.EvilEgo.AttachTo(CanvasOverlay); // we are adding remote controlled objects cp1.AddTo(this.SharedState.RemoteObjects[user]); cp1.GoodEgo.AddTo(this.SharedState.RemoteObjects[user]); cp1.EvilEgo.AddTo(this.SharedState.RemoteObjects[user]); // group as enemies cp1.EvilEgo.AddTo(this.GroupEnemies); cp1.GoodEgo.ApplyFilter(Filters.ColorFillFilter(0xff)); cp1.AddTo(FragileEntities); handler(cp1); // this entity only moves when that player wants to move... // yet we might need to notify of damage }; RoutedActions.RemoveCoPlayer.Direct = user => { var CoPlayer = (PlayerShip)this.SharedState.RemoteObjects[user][0]; this.SharedState.RemoteObjects[user].Clear(); CoPlayers.Remove(CoPlayer); CoPlayer.EvilEgo.Orphanize(); CoPlayer.GoodEgo.Orphanize(); GroupEnemies.Remove(CoPlayer.EvilEgo); CoPlayer.EvilEgo.RemoveFrom(FragileEntities.Items); CoPlayer.GoodEgo.RemoveFrom(FragileEntities.Items); }; RoutedActions.MoveCoPlayer.Direct = (ego, p) => { ego.GoodEgo.TweenMoveTo(p.x, p.y); }; #endregion #region AddEnemy RoutedActions.AddEnemy.Direct += (e, p) => { e.Name = "Enemy"; e.TeleportTo(p.x, p.y) .AttachTo(CanvasOverlay) .AddTo(FragileEntities.Items) .AddTo(GroupEnemies); }; #endregion #region cloud cloud1 = new EnemyCloud { PlaySound = play }; cloud1.Members.ForEach( m => { // if a cloud member fires, it will go across network... m.Element.FireBullet = RoutedActions.FireBullet; this.SharedState.SharedObjects.Add(m.Element); // we are adding enemies over network - but they actually are shared objects RoutedActions.AddEnemy.Chained(m.Element, m.Element.ToPoint()); } ); cloud1.TickSounds = new Sound[] { Sounds.duh0, Sounds.duh1, Sounds.duh2, Sounds.duh3, }; cloud1.AttachTo(this.CanvasOverlay); cloud1.TickInterval.ValueChangedTo += e => DebugDump.Write(new { TickInterval = e }); //var CloudSpeedAcc = 1.04; //var CloudSpeed = 12.0; //var CloudMove = new Point(); Action ResetCloudLocal = delegate { cloud1.Speed = 12; cloud1.NextMove.x = cloud1.Speed; cloud1.NextMove.y = 0; cloud1.TeleportTo(60, 80); cloud1.TickInterval.Value = 1000; // rebuild defense foreach (var v in DefenseBlocks) { v.alpha = 1; } foreach (var v in KnownEgos) { v.GoodEgo.alpha = 1; v.EvilEgo.alpha = 1; } }; ResetCloudLocal(); bool ResetCloudSoonDisabled = false; RoutedActions.KillAllInvaders.Direct = delegate { cloud1.Members.ForEach(m => m.Element.alpha = 0); }; Action ResetCloudSoon = delegate { if (ResetCloudSoonDisabled) { return; } ResetCloudSoonDisabled = true; RoutedActions.KillAllInvaders.Chained(); // do not count evil mode if (!Ego.EvilMode) { PlaysSurvived.Value++; } cloud1.TickInterval.Value = 0; cloud1.TeleportTo(60, 80); cloud1.Speed = 12; cloud1.NextMove.x = cloud1.Speed; cloud1.NextMove.y = 0; 3000.AtDelayDo( delegate { cloud1.ResetColors(); ResetCloudLocal(); cloud1.ResetLives(); ResetCloudSoonDisabled = false; } ); }; cloud1.Tick += delegate { var r = cloud1.Warzone; if (r == null) { ResetCloudSoon(); return; } //this.graphics.clear(); //this.graphics.beginFill(0xffffff); //this.graphics.drawRect(r.x, r.y, r.width, r.height); //DebugDump.Write(new { r.left, r.right, cloud1.FrontRow.Length }); if (r.bottom > DefenseY) { ResetCloudSoon(); return; } var Skip = 4 * (CoPlayers.Count + 1); if (cloud1.Counter % Skip == 0) { // fire some bullets var rr = cloud1.FrontRow.Random(); // invaders bullets should have different sound or be silent rr.Element.FireBulletChained(1, new Point(rr.Element.x, rr.Element.y), new Point(rr.Element.x, DefaultHeight), Ego.GoodEgoY); //rb.Silent = true; //AddBullet.Chained( // rb //); } var IsFarRight = r.right >= (DefaultWidth - EnemyCloud.DefaultCloudMargin); if (cloud1.NextMove.x < 0) { IsFarRight = false; } var IsFarLeft = r.left <= (EnemyCloud.DefaultCloudMargin); if (cloud1.NextMove.x > 0) { IsFarLeft = false; } var WillStartVerticalMovement = IsFarLeft || IsFarRight; if (WillStartVerticalMovement && cloud1.NextMove.y == 0) { cloud1.NextMove.x = 0; cloud1.NextMove.y = 8; cloud1.Speed *= cloud1.SpeedAcc; } else { if (WillStartVerticalMovement) { cloud1.NextMove.y -= cloud1.Speed / 2; } else { } if (cloud1.NextMove.y <= 0) { cloud1.NextMove.y = 0; if (IsFarLeft) { cloud1.NextMove.x = cloud1.Speed; } else if (IsFarRight) { cloud1.NextMove.x = -cloud1.Speed; } } } //DebugDump.Write(new { CloudMove.x, CloudMove.y }); cloud1.MoveToOffset(cloud1.NextMove); }; #endregion //AddEnemy.Chained(new EnemyA(), new Point(200, 200)); //AddEnemy.Chained(new EnemyB(), new Point(240, 200)); //AddEnemy.Chained(new EnemyC(), new Point(280, 200)); //AddEnemy.Chained(new EnemyUFO(), new Point(160, 200)); //AddEnemy.Chained(new EnemyBigGun(), new Point(120, 200)); #region FireBullet RoutedActions.FireBullet.Direct = (StarShip starship, int Multiplier, Point From, Point To, double Limit, Action <BulletInfo> handler) => { var bullet = new SpriteWithMovement(); Multiplier = Math.Max(Multiplier, 1); for (int i = 1; i <= Multiplier; i++) { bullet.graphics.beginFill(Colors.Green); bullet.graphics.drawRect((i - Multiplier) * 2, -8, 1, 16); } bullet.StepMultiplier = 0.3; bullet.MaxStep = 24; if (From.y < To.y) { bullet.TeleportTo(From.x, From.y); bullet.TweenMoveTo(To.x + 0.00001, To.y); bullet.PositionChanged += delegate { if (bullet.y > Limit) { bullet.Orphanize(); } }; } else { bullet.TeleportTo(From.x, From.y); bullet.TweenMoveTo(To.x + 0.00001, To.y); bullet.PositionChanged += delegate { if (bullet.y < Limit) { bullet.Orphanize(); } }; } // it should not be null and provide the correct parent for the bullet if (starship == null) { starship = this.Ego.ActiveEgo; } var bulletp = new BulletInfo(bullet.WithParent(starship)) { Multiplier = Multiplier }; // local only FragileEntities.AddBullet(bulletp); bulletp.Element.AttachTo(CanvasOverlay); bulletp.Element.removed += delegate { FragileEntities.Bullets.Remove(bulletp); }; if (!bulletp.Silent) { play(Sounds.firemissile); } if (handler != null) { handler(bulletp); } }; #endregion #region SetWeaponMultiplier RoutedActions.SetWeaponMultiplier.Direct = (p, value) => { p.CurrentBulletMultiplier.Value = value; }; #endregion #region AddDamage RoutedActions.AddDamage.Direct += (target, damage, shooter) => { target.TakeDamage(damage); if (target.HitPoints <= 0) { // did we kill anything? // shall we take credit? if (GroupEnemies.Any(k => k == target)) { cloud1.TickInterval.Value = Math.Max((cloud1.TickInterval.Value - 25), 200); cloud1.Speed *= cloud1.SpeedAcc; } // we shot a coplayer while in evil mode! yay! if (shooter == Ego.EvilEgo) { if (KnownEgos.Any(k => k.GoodEgo == target)) { this.RoutedActions.AddAchivementUFO.Chained(); } } #region award localplayer and upgrade weapon if (shooter == Ego.ActiveEgo) { Statusbar.Score.Value += target.ScorePoints; TryUpgradeWeapon(); } #endregion play(target.GetDeathSound()); } else { play(Sounds.shortwhite); } //DebugDump.Write( // new // { // From = bullet.Parent.Name, // Delta = bullet.TotalDamage, // target.HitPoints, // To = target.Name // } //); }; #endregion #region FragileEntities this.FragileEntities.AddDamage = RoutedActions.AddDamage; this.FragileEntities.PrepareFilter = delegate { var GroupGood = KnownEgos.Select(i => i.GoodEgo).ToArray(); var GroupEvil = GroupEnemies.ToArray(); this.FragileEntities.Filter = (source, n) => { // spare yourself var query = source; // spare coplayers in the same mode if (GroupEnemies.Contains(n.Parent)) { query = query.Where(x => !GroupEvil.Contains(x)); } else { query = query.Where(x => !GroupGood.Contains(x)); } return(query); }; }; #endregion this.RoutedActions.AddAchivementFiver.Direct = delegate { play(Sounds.insertcoin); }; this.RoutedActions.AddAchivementMaxGun.Direct = delegate { play(Sounds.insertcoin); }; this.RoutedActions.AddAchivementUFO.Direct = delegate { play(Sounds.mothershiploop); }; Action <RoutedActionInfoBase> BaseHandler = e => DebugDump.Write(new { e.EventName }); // events for network // RoutedActions.AddDamage.BaseHandler += BaseHandler; RoutedActions.RestoreStarship.BaseHandler += BaseHandler; RoutedActions.AddAchivementFiver.BaseHandler += BaseHandler; RoutedActions.AddAchivementUFO.BaseHandler += BaseHandler; RoutedActions.AddAchivementMaxGun.BaseHandler += BaseHandler; //this.AddEnemy.BaseHandler += BaseHandler; ////this.AddBullet.BaseHandler += BaseHandler; //this.DoPlayerMovement.BaseHandler += BaseHandler; //this.SetWeaponMultiplier.BaseHandler += BaseHandler; }