/// <summary> /// Constructs a new game engine. /// </summary> /// <param name="dataPath">The path to save game directory.</param> /// <param name="useNetwork">Controls the usage of the network engine.</param> /// <param name="deserializeState">Controls if the state is deserialized or not.</param> /// <param name="fileName">The path to the state file.</param> /// <param name="reportData">Determines if data should be reported.</param> /// <param name="leds">Provides a listing of game leds that can be used.</param> /// <param name="trackLastRun">Controls whether the PAC keeps track of the last run creature for blacklisting.</param> private GameEngine(string dataPath, bool useNetwork, bool deserializeState, string fileName, bool reportData, TerrariumLed[] leds, bool trackLastRun) { _ledIndicators = leds; _currentStateFileName = fileName; // test to make sure we're not violating any constraints by current // physics settings in the engine. EngineSettings.EngineSettingsAsserts(); // Calculate quanta and worldsize if we haven't done so yet if (_reloadSettings) CalculateWorldSize(); _pac = new PrivateAssemblyCache(dataPath, fileName, true, trackLastRun); // Reset the appdomain policy since we changed the location of the organism dlls // This must be done before any animals are loaded in any way. Make sure this call stays // as soon as possible AppDomain.CurrentDomain.SetAppDomainPolicy(SecurityUtils.MakePolicyLevel(_pac.AssemblyDirectory)); _usingNetwork = useNetwork; _populationData = new PopulationData(reportData, leds[(int) LedIndicators.ReportWebService]); // Should only happen if an exception prevented a previous attempt to start a game if (AppMgr.CurrentScheduler != null) { AppMgr.DestroyScheduler(); } // Create a scheduler that manages giving the creatures timeslices _scheduler = AppMgr.CreateSameDomainScheduler(this); _scheduler.Quantum = _organismQuanta; if (useNetwork) { // Required to start up the network listeners _networkEngine = new NetworkEngine(); } WorldState currentState; Boolean successfulDeserialization = false; if (deserializeState && File.Exists(fileName)) { try { if (_pac.LastRun.Length != 0) { // The process was killed while an organism was being run, blacklist it // Since this potentially means the animal hung the game. _pac.BlacklistAssemblies(new string[] {_pac.LastRun}); } this.deserializeState(fileName); currentState = CurrentVector.State; _scheduler.CurrentState = currentState; _scheduler.CompleteOrganismDeserialization(); successfulDeserialization = true; } catch (Exception e) { ErrorLog.LogHandledException(e); } } if (successfulDeserialization) return; // Set up initial world state currentState = new WorldState(GridWidth, GridHeight); currentState.TickNumber = 0; currentState.StateGuid = Guid.NewGuid(); currentState.Teleporter = new Teleporter(AnimalCount/EngineSettings.NumberOfAnimalsPerTeleporter); currentState.MakeImmutable(); WorldVector vector = new WorldVector(currentState); CurrentVector = vector; _scheduler.CurrentState = currentState; }
/// <summary> /// Processes turns in a phase manner. After 10 calls to ProcessTurn /// all 10 phases will be complete and the method will have completed /// one game tick. /// </summary> /// <returns>True if a tick has been processed, false otherwise.</returns> public Boolean ProcessTurn() { // shutdownError and invalidPeerError are two errors that get set from // deep in the system. We process them here because it is a clean place // to restart the game since we aren't in the middle of any phases. // This is just a mechanism for cleanly throwing an error from a known good // location. if (_shutdownError) { _shutdownError = false; throw new ShutdownFailureException(); } if (_invalidPeerError) { _invalidPeerError = false; throw new InvalidPeerException(); } // We break the main processing of the game into 10 phases that have been tuned to each // take roughly the same amount of time. We paint the screen in between each one. This way // We can have a constant frame rate and incrementally do the engine processing we need to // do. // After we do all 10 phases we've done one game "tick". We can do two ticks a second which means // we have a frame rate of 20. switch (_turnPhase) { case 0: // If we are in ecosystem mode, we want to save the game state periodically in case the user // shuts down the screensaver by pressing ctl-alt-del which just kills the process. This way // we won't have lost too much processing time. // We always want to save on the tick after we've reported so we don't get our state messed up // on the server. For example: never start the game in a state that was before the information // was sent to the server because the server will think we're corrupted. if (_logState || (_ecosystemMode && _populationData.IsReportingTick(CurrentVector.State.TickNumber - 1))) { Debug.WriteLine("Saving state."); serializeState(_currentStateFileName); } // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 1: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 2: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 3: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 4: // Give 1/5 of the animals a chance to do their processing Scheduler.Tick(); break; case 5: // Get all the actions that the animals want to perform in this tick. TickActions act = _scheduler.GatherTickActions(); CurrentVector.Actions = act; // Create a mutable version of the world state that we'll change to create the next // world state. _newWorldState = (WorldState) CurrentVector.State.DuplicateMutable(); _newWorldState.TickNumber = _newWorldState.TickNumber + 1; _populationData.BeginTick(_newWorldState.TickNumber, CurrentVector.State.StateGuid); // Remove any organisms queued to be removed removeOrganismsFromQueue(); // We take a snapshot of the organism IDs since we do several foreach loops // and change the state, which causes exceptions to occur _organismIDList = new string[_newWorldState.OrganismIDs.Count]; _newWorldState.OrganismIDs.CopyTo(_organismIDList, 0); killDiseasedOrganisms2(); break; case 6: Debug.Assert(_newWorldState != null, "Worldstate did not get created for this tick"); // Do this first, since it always must happen so that things (like growing) // won't happen if there isn't enough energy burnBaseEnergy(); // Do attacks before movement so that when a carnivore asks if it can attack // they actually get to hit the target before it moves doAttacks(); doDefends(); changeMovementVectors(); break; case 7: moveAnimals(); break; case 8: doBites(); growAllOrganisms(); incubate(); startReproduction(); heal(); break; case 9: // Do this last so that the plant always starts charged up, // and has to operate with what it has for the next turn giveEnergyToPlants(); // Teleport after all actions have been processed so that there are // no single turn pending actions left for the organism. teleportOrganisms(); // Insert any new organisms insertOrganismsFromQueue(); // Set Antenna States doAntennas(); // We're done changing the state, now make it immutable _newWorldState.MakeImmutable(); Debug.Assert(_newWorldState.Organisms.Count == Scheduler.Organisms.Count); WorldVector vector = new WorldVector(_newWorldState); CurrentVector = vector; _scheduler.CurrentState = _newWorldState; _populationData.EndTick(_newWorldState.TickNumber); _newWorldState = null; break; } _turnPhase++; if (_turnPhase == 10) { _turnPhase = 0; return true; } return false; }
// In 10 frames we animate the world from oldState to newState // Frame 0 paints the world exactly as it is in oldState internal void PaintFrame(WorldVector oldVector, WorldVector newVector, int frameNumber, Boolean erase) { if (frameNumber == 0) { if (newVector == null) { tddGameView.UpdateWorld(oldVector); } else { tddGameView.UpdateWorld(newVector); } } }
/// <summary> /// Updates the sprites controlled by the game view by providing a new /// world vector from the game engine. /// </summary> /// <param name="worldVector">The new world vector of organisms.</param> public void UpdateWorld(WorldVector worldVector) { #if TRACE Profiler.Start("TerrariumDirectDrawGameView.UpdateWorld()"); #endif wv = worldVector; paintPlants = true; updateTicker++; updateTickerChanged = true; var zones = wv.State.Teleporter.GetTeleportZones(); foreach (var zone in zones) { if (hackTable.ContainsKey(zone.ID)) { var tsZone = (TerrariumSprite) hackTable[zone.ID]; tsZone.XDelta = (zone.Rectangle.X - tsZone.XPosition)/10; tsZone.YDelta = (zone.Rectangle.Y - tsZone.YPosition)/10; hackTable[zone.ID] = tsZone; } else { InitTeleporter(zone); } } foreach (OrganismState orgState in wv.State.Organisms) { if (orgState.RenderInfo != null) { var tsSprite = (TerrariumSprite) orgState.RenderInfo; if (orgState is AnimalState) { if (tsSprite.PreviousAction != orgState.PreviousDisplayAction) { tsSprite.CurFrame = 0; tsSprite.PreviousAction = orgState.PreviousDisplayAction; } tsSprite.XDelta = (orgState.Position.X - tsSprite.XPosition)/10; tsSprite.YDelta = (orgState.Position.Y - tsSprite.YPosition)/10; } else { tsSprite.CurFrame = 0; tsSprite.PreviousAction = orgState.PreviousDisplayAction; tsSprite.XPosition = orgState.Position.X; tsSprite.YPosition = orgState.Position.Y; } orgState.RenderInfo = tsSprite; } else { InitOrganism(orgState); } } #if TRACE Profiler.End("TerrariumDirectDrawGameView.UpdateWorld()"); #endif }
// Called by the game engine every time a tick creates a new state for the world private void WorldVectorChanged(object sender, WorldVectorChangedEventArgs e) { oldVector = newVector; newVector = e.NewVector; if (tddGameView != null) { if (e.NewVector.State.Organisms.Count < 1) { if (GameEngine.Current != null && GameEngine.Current.EcosystemMode) { if (developerPanel.Leds[(int)LedIndicators.DiscoveryWebService].LedState == LedStates.Failed || developerPanel.Leds[(int)LedIndicators.ReportWebService].LedState == LedStates.Failed) { tddGameView.TerrariumMessage = emptyEcosystemServerDownMessage; } else { tddGameView.TerrariumMessage = emptyEcosystemMessage; } } else { tddGameView.TerrariumMessage = emptyTerrariumMessage; } } else { // Only clear our messages, not those set by someone else if (tddGameView.TerrariumMessage == emptyEcosystemMessage || tddGameView.TerrariumMessage == emptyTerrariumMessage) { tddGameView.TerrariumMessage = null; } } // If the network has a problem, always override the message with this if (GameEngine.Current != null && GameEngine.Current.NetworkEngine != null) { if (GameEngine.Current.NetworkEngine.NetworkStatusMessage.Length != 0) { tddGameView.TerrariumMessage = GameEngine.Current.NetworkEngine.NetworkStatusMessage; } else { if (tddGameView.TerrariumMessage == NetworkEngine.NetworkBehindNatMessage) { tddGameView.TerrariumMessage = null; } } } } // Tell the tracewindow that we've got a new tick if (traceWindow != null) { traceWindow.TickEnded(); } this.developerPanel.AnimalCount = GameEngine.Current.AnimalCount; this.developerPanel.MaximumAnimalCount = GameEngine.Current.MaxAnimals; this.developerPanel.PeerCount = GameEngine.Current.PeerCount; if (GameEngine.Current.EcosystemMode == true) { //this.taskBar.Text = GameConfig.WebRoot; } else { //this.taskBar.Text = GameEngine.Current.FileName; } this.taskBar.Text = "Terrarium"; this.taskBar.Text += "\r\nPopulation: " + GameEngine.Current.AnimalCount + "/" + GameEngine.Current.MaxAnimals + "\r\nPeers: " + GameEngine.Current.PeerCount; try { if (GameEngine.Current.IsNetworkEnabled == true) { developerPanel.Teleportations = GameEngine.Current.NetworkEngine.Teleportations; developerPanel.FailedSends = GameEngine.Current.NetworkEngine.FailedTeleportationSends; developerPanel.FailedReceives = GameEngine.Current.NetworkEngine.FailedTeleportationReceives; } } catch { } if (engineStateText != null && engineStateText.Length != 0) { this.bottomPanel.Ticker.Messages.Enqueue(engineStateText); engineStateText = ""; } }
// Sets up the form after a new game has been loaded private void NewGameLoaded() { if (GameEngine.Current.Pac.LastRun.Length != 0) { engineStateText += "Terrarium automatically shutdown and restarted (without saving)because the creature '" + PrivateAssemblyCache.GetAssemblyShortName(GameEngine.Current.Pac.LastRun) + "' hung your machine and Terrarium needed to remove this offensive animal."; GameEngine.Current.Pac.LastRun = ""; } if (GameEngine.Current.EcosystemMode == true) { this.developerPanel.GameModeText = "Ecosystem"; developerPanel.WebRoot = GameConfig.WebRoot; bottomPanel.GameMode = GameModes.Ecosystem; bottomPanel.Mode = screenSaverMode; } else { this.developerPanel.GameModeText = "Terrarium"; this.bottomPanel.GameMode = GameModes.Terrarium; if (GameEngine.Current.FileName.Length == 0) { developerPanel.WebRoot = "[New Terrarium]"; } else { developerPanel.WebRoot = GameEngine.Current.FileName; } } GameEngine.Current.WorldVectorChanged += new WorldVectorChangedEventHandler(WorldVectorChanged); GameEngine.Current.EngineStateChanged += new EngineStateChangedEventHandler(EngineStateChanged); oldVector = null; newVector = null; frameNumber = 0; InitializeGraphics(GameEngine.Current.WorldWidth, GameEngine.Current.WorldHeight); timer1.Enabled = true; if (screenSaverMode == ScreenSaverMode.Run) { Block.BlockScreens(Screen.FromControl(this)); this.ShowUI = false; this.Fullscreen = true; this.ContextMenu = null; this.tddGameView.DrawText = true; Cursor.Hide(); Cursor.Position = new Point(this.Width / 2, this.Height / 2); this.tddGameView.DrawCursor = false; } else { this.Cursor = Cursors.Default; if (wasRelaunched) { this.SuspendLayout(); if (windowState == FormWindowState.Maximized) { this.WindowState = FormWindowState.Maximized; } else if (windowState == FormWindowState.Minimized) { this.Hide(); this.taskBar.Visible = true; } else { this.WindowState = FormWindowState.Normal; } this.Location = new Point(windowRectangle.Left, windowRectangle.Top); this.Size = new Size(windowRectangle.Width, windowRectangle.Height); this.ResumeLayout(); } else { if (GameConfig.StartFullscreen == true) this.Fullscreen = true; } } this.ConfigureTickerBar(); }
/// <summary> /// Creates a new set of event arguments for when the world vector changes. /// </summary> /// <param name="oldVector">The previous world vector object.</param> /// <param name="newVector">The new world vector object.</param> public WorldVectorChangedEventArgs(WorldVector oldVector, WorldVector newVector) { this.oldVector = oldVector; this.newVector = newVector; }
/// <summary> /// Creates a new set of event arguments for when the world vector changes. /// </summary> /// <param name="oldVector">The previous world vector object.</param> /// <param name="newVector">The new world vector object.</param> public WorldVectorChangedEventArgs(WorldVector oldVector, WorldVector newVector) { OldVector = oldVector; NewVector = newVector; }