mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 19:26:38 +00:00 
			
		
		
		
	git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@138 ea6a568a-9f4f-0410-981a-c910a81bb256
		
			
				
	
	
		
			471 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			D
		
	
	
	
	
	
			
		
		
	
	
			471 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			D
		
	
	
	
	
	
| /*
 | |
|   OpenMW - The completely unofficial reimplementation of Morrowind
 | |
|   Copyright (C) 2008-2009  Nicolay Korslund
 | |
|   Email: < korslund@gmail.com >
 | |
|   WWW: http://openmw.snaptoad.com/
 | |
| 
 | |
|   This file (openmw.d) is part of the OpenMW package.
 | |
| 
 | |
|   OpenMW is distributed as free software: you can redistribute it
 | |
|   and/or modify it under the terms of the GNU General Public License
 | |
|   version 3, as published by the Free Software Foundation.
 | |
| 
 | |
|   This program is distributed in the hope that it will be useful, but
 | |
|   WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|   General Public License for more details.
 | |
| 
 | |
|   You should have received a copy of the GNU General Public License
 | |
|   version 3 along with this program. If not, see
 | |
|   http://www.gnu.org/licenses/ .
 | |
| 
 | |
|  */
 | |
| 
 | |
| module openmw;
 | |
| 
 | |
| import std.stdio;
 | |
| import std.string;
 | |
| import std.cstream;
 | |
| import std.file;
 | |
| 
 | |
| import ogre.ogre;
 | |
| import ogre.bindings;
 | |
| import gui.bindings;
 | |
| import gui.gui;
 | |
| 
 | |
| import bullet.bullet;
 | |
| 
 | |
| import scene.celldata;
 | |
| import scene.soundlist;
 | |
| import scene.gamesettings;
 | |
| import scene.player;
 | |
| 
 | |
| import core.resource;
 | |
| import core.memory;
 | |
| import core.config;
 | |
| 
 | |
| import monster.util.string;
 | |
| import monster.vm.mclass;
 | |
| import monster.vm.dbg;
 | |
| import mscripts.setup;
 | |
| 
 | |
| import sound.audio;
 | |
| 
 | |
| import input.events;
 | |
| 
 | |
| import terrain.terrain;
 | |
| 
 | |
| // Set up exit handler
 | |
| alias void function() c_func;
 | |
| extern(C) int atexit(c_func);
 | |
| 
 | |
| bool cleanExit = false;
 | |
| 
 | |
| void exitHandler()
 | |
| {
 | |
|   // If we exit uncleanly, print the function stack.
 | |
|   if(!cleanExit)
 | |
|     writefln(dbg.getTrace());
 | |
| }
 | |
| 
 | |
| 
 | |
| //*
 | |
| import std.gc;
 | |
| import gcstats;
 | |
| 
 | |
| void poolSize()
 | |
| {
 | |
|   GCStats gc;
 | |
|   getStats(gc);
 | |
|   writefln("Pool size: ", comma(gc.poolsize));
 | |
|   writefln("Used size: ", comma(gc.usedsize));
 | |
| }
 | |
| //*/
 | |
| 
 | |
| void main(char[][] args)
 | |
| {
 | |
|   bool render = true;
 | |
|   bool help = false;
 | |
|   bool resetKeys = false;
 | |
|   bool showOgreFlag = false;
 | |
|   bool debugOut = false;
 | |
|   bool extTest = false;
 | |
|   bool doGen = false;
 | |
|   bool nextSave = false;
 | |
|   bool loadSave = false;
 | |
| 
 | |
|   // Some examples to try:
 | |
|   //
 | |
|   // "Abandoned Shipwreck, Upper Level";
 | |
|   // "Gro-Bagrat Plantation";
 | |
|   // "Abinabi";
 | |
|   // "Abebaal Egg Mine";
 | |
|   // "Ald-ruhn, Ald Skar Inn";
 | |
|   // "Koal Cave";
 | |
|   // "Ald-ruhn, Arobar Manor Bedrooms"
 | |
|   // "Sud";
 | |
|   // "Vivec, The Lizard's Head";
 | |
|   // "ToddTest";
 | |
| 
 | |
|   // Cells to load
 | |
|   char[][] cells;
 | |
| 
 | |
|   // Savegame to load
 | |
|   char[] savefile;
 | |
| 
 | |
|   foreach(char[] a; args[1..$])
 | |
|     if(a == "-n") render = false;
 | |
|     else if(a == "-ex") extTest = true;
 | |
|     else if(a == "-gen") doGen = true;
 | |
|     else if(a == "-h") help=true;
 | |
|     else if(a == "-rk") resetKeys = true;
 | |
|     else if(a == "-oc") showOgreFlag = true;
 | |
|     else if(a == "-ns") config.noSound = true;
 | |
|     else if(a == "-save") nextSave = true;
 | |
|     else if(a == "-debug")
 | |
|       {
 | |
|         // Enable Monster debug output
 | |
|         dbg.dbgOut = dout;
 | |
| 
 | |
|         // Tell OGRE to do the same later on
 | |
|         debugOut = true;
 | |
|       }
 | |
|     else if(nextSave)
 | |
|       {
 | |
|         savefile = a;
 | |
|         nextSave = false;
 | |
|       }
 | |
|     else cells ~= a;
 | |
| 
 | |
|   if(cells.length > 1)
 | |
|     {
 | |
|       writefln("More than one cell specified, rendering disabled");
 | |
|       render=false;
 | |
|     }
 | |
| 
 | |
|   void showHelp()
 | |
|     {
 | |
|       writefln("Syntax: %s [options] cell-name [cell-name]", args[0]);
 | |
|       writefln("  Options:");
 | |
|       writefln("    -n            Only load, do not render");
 | |
|       writefln("    -ex           Test the terrain system");
 | |
|       writefln("    -gen          Generate landscape cache");
 | |
|       writefln("    -rk           Reset key bindings to default");
 | |
|       writefln("    -oc           Show the Ogre config dialogue");
 | |
|       writefln("    -ns           Completely disable sound");
 | |
|       writefln("    -debug        Print debug information");
 | |
|       writefln("    -save <file>  Load cell/pos from savegame");
 | |
|       writefln("    -h            Show this help");
 | |
|       writefln("");
 | |
|       writefln("Specifying more than one cell implies -n");
 | |
|     }
 | |
| 
 | |
|   if(help)
 | |
|     {
 | |
|       showHelp();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   initializeMemoryRegions();
 | |
|   initMonsterScripts();
 | |
| 
 | |
|   // This is getting increasingly hackish, but this entire engine
 | |
|   // design is now quickly outgrowing its usefulness, and a rewrite is
 | |
|   // coming soon anyway.
 | |
|   PlayerSaveInfo pi;
 | |
|   if(savefile != "")
 | |
|     {
 | |
|       if(cells.length)
 | |
|         {
 | |
|           writefln("Please don't specify both a savegame file (%s) and cell names (%s)", savefile, cells);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|       loadSave = true;
 | |
|       writefln("Loading savegame %s", savefile);
 | |
|       pi = importSavegame(savefile);
 | |
|       writefln("    Player name: %s", pi.playerName);
 | |
|       writefln("    Cell name:   %s", pi.cellName);
 | |
|       writefln("    Pos:         %s", pi.pos.position);
 | |
|       writefln("    Rot:         %s", pi.pos.rotation);
 | |
| 
 | |
|       cells = [pi.cellName];
 | |
|     }
 | |
| 
 | |
|   config.initialize(resetKeys);
 | |
|   scope(exit) config.writeConfig();
 | |
| 
 | |
|   // Check if the data directory exists
 | |
|   if(!exists(config.dataDir) || !isdir(config.dataDir))
 | |
|   {
 | |
|     writefln("Cannot find data directory '", config.dataDir,
 | |
| 	"' - please edit openmw.ini.");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If the -oc parameter is specified, we override any config
 | |
|   // setting.
 | |
|   if(showOgreFlag) config.finalOgreConfig = true;
 | |
| 
 | |
|   if(cells.length == 0)
 | |
|     if(config.defaultCell.length)
 | |
|       cells ~= config.defaultCell;
 | |
| 
 | |
|   if(cells.length == 1 && !loadSave)
 | |
|     config.defaultCell = cells[0];
 | |
| 
 | |
|   if(cells.length == 0)
 | |
|     {
 | |
|       showHelp();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   if(!config.noSound) initializeSound();
 | |
|   resources.initResources();
 | |
| 
 | |
|   // Load all ESM and ESP files
 | |
|   loadTESFiles(config.gameFiles);
 | |
| 
 | |
|   scene.gamesettings.loadGameSettings();
 | |
| 
 | |
|   CellData cd = cellList.get();
 | |
| 
 | |
|   foreach(char[] cName; cells)
 | |
|     {
 | |
|       // Release the last cell data
 | |
|       cellList.release(cd);
 | |
| 
 | |
|       // Get a cell data holder and load an interior cell
 | |
|       cd = cellList.get();
 | |
| 
 | |
|       try cd.loadIntCell(cName);
 | |
|       catch(Exception e)
 | |
| 	{
 | |
| 	  writefln("\nUnable to load cell '%s'.", cName);
 | |
| 	  writefln("\nDetails: %s", e);
 | |
|           writefln("
 | |
| Perhaps this cell does not exist in your Morrowind language version?
 | |
| Try specifying another cell name on the command line, or edit openmw.ini.");
 | |
| 	  return;
 | |
| 	}
 | |
| 
 | |
|       // If we're loading from save, override the player position
 | |
|       if(loadSave)
 | |
|         *playerData.position = pi.pos;
 | |
|     }
 | |
| 
 | |
|   // Simple safety hack
 | |
|   NodePtr putObject(MeshIndex m, Placement *pos, float scale,
 | |
|                     bool collide=false)
 | |
|     {
 | |
|       if(m is null)
 | |
| 	writefln("WARNING: CANNOT PUT NULL OBJECT");
 | |
|       else if(!m.isEmpty)
 | |
|         return placeObject(m, pos, scale, collide);
 | |
| 
 | |
|       //writefln("WARNING: CANNOT INSERT EMPTY MESH '%s'", m.getName);
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|   if(cd.inCell !is null)
 | |
|     // Set the name for the GUI (temporary hack)
 | |
|     gui_setCellName(cd.inCell.id.ptr);
 | |
| 
 | |
|   // Set up the exit handler
 | |
|   atexit(&exitHandler);
 | |
|   scope(exit) cleanExit = true;
 | |
| 
 | |
|   if(render)
 | |
|     {
 | |
|       // Warm up OGRE
 | |
|       setupOgre(debugOut);
 | |
|       scope(exit) cleanupOgre();
 | |
| 
 | |
|       // Create the GUI system
 | |
|       initGUI(debugOut);
 | |
| 
 | |
|       // Set up Bullet
 | |
|       initBullet();
 | |
|       scope(exit) cleanupBullet();
 | |
| 
 | |
|       // Initialize the internal input and event manager. The
 | |
|       // lower-level input system (OIS) is initialized by the
 | |
|       // setupOgre() call further up.
 | |
|       initializeInput();
 | |
| 
 | |
|       // Play some old tunes
 | |
|       if(extTest)
 | |
|         {
 | |
|           // Exterior cell
 | |
|           /*
 | |
| 	  Color c;
 | |
| 	  c.red = 180;
 | |
| 	  c.green = 180;
 | |
| 	  c.blue = 180;
 | |
| 	  setAmbient(c, c, c, 0);
 | |
| 
 | |
| 	  // Put in the water
 | |
| 	  ogre_createWater(cd.water);
 | |
| 
 | |
| 	  // Create an ugly sky
 | |
| 	  ogre_makeSky();
 | |
|           */
 | |
| 
 | |
|           initTerrain(doGen);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           // Interior cell
 | |
|           assert(cd.inCell !is null);
 | |
| 	  setAmbient(cd.ambi.ambient, cd.ambi.sunlight,
 | |
| 		     cd.ambi.fog, cd.ambi.fogDensity);
 | |
| 
 | |
| 	  // Not all interior cells have water
 | |
| 	  if(cd.inCell.flags & CellFlags.HasWater)
 | |
| 	    ogre_createWater(cd.water);
 | |
| 
 | |
|           // Insert the meshes of statics into the scene
 | |
|           foreach(ref LiveStatic ls; cd.statics)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
 | |
|           // Inventory lights
 | |
|           foreach(ref LiveLight ls; cd.lights)
 | |
|             {
 | |
|               NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|               ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
 | |
|               if(!config.noSound)
 | |
|                 {
 | |
|                   Sound *s = ls.m.sound;
 | |
|                   if(s)
 | |
|                     {
 | |
|                       ls.loopSound = soundScene.insert(s, true);
 | |
|                       if(ls.loopSound)
 | |
|                         {
 | |
|                           auto p = ls.getPos();
 | |
|                           ls.loopSound.setPos(p.position[0],
 | |
|                                               p.position[1],
 | |
|                                               p.position[2]);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|           // Static lights
 | |
|           foreach(ref LiveLight ls; cd.statLights)
 | |
|             {
 | |
|               NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
 | |
|               ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
 | |
|               if(!config.noSound)
 | |
|                 {
 | |
|                   Sound *s = ls.m.sound;
 | |
|                   if(s)
 | |
|                     {
 | |
|                       ls.loopSound = soundScene.insert(s, true);
 | |
|                       if(ls.loopSound)
 | |
|                         {
 | |
|                           auto p = ls.getPos();
 | |
|                           ls.loopSound.setPos(p.position[0],
 | |
|                                               p.position[1],
 | |
|                                               p.position[2]);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|           // Misc items
 | |
|           foreach(ref LiveMisc ls; cd.miscItems)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           /*
 | |
|           // NPCs (these are complicated, usually do not have normal meshes)
 | |
|           foreach(ref LiveNPC ls; cd.npcs)
 | |
|           putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           */
 | |
|           // Containers
 | |
|           foreach(ref LiveContainer ls; cd.containers)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
 | |
|           // Doors
 | |
|           foreach(ref LiveDoor ls; cd.doors)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Activators (including beds etc)
 | |
|           foreach(ref LiveActivator ls; cd.activators)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
 | |
|           // Potions
 | |
|           foreach(ref LivePotion ls; cd.potions)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Apparatus
 | |
|           foreach(ref LiveApparatus ls; cd.appas)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Ingredients
 | |
|           foreach(ref LiveIngredient ls; cd.ingredients)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Armors
 | |
|           foreach(ref LiveArmor ls; cd.armors)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Weapons
 | |
|           foreach(ref LiveWeapon ls; cd.weapons)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Books
 | |
|           foreach(ref LiveBook ls; cd.books)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Clothes
 | |
|           foreach(ref LiveClothing ls; cd.clothes)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Tools
 | |
|           foreach(ref LiveTool ls; cd.tools)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
|           // Creatures (not displayed very well yet)
 | |
|           foreach(ref LiveCreature ls; cd.creatures)
 | |
|             putObject(ls.m.model, ls.getPos(), ls.getScale());
 | |
| 
 | |
|           // End of interior cell
 | |
|         }
 | |
| 
 | |
|       // Run GUI system
 | |
|       startGUI();
 | |
| 
 | |
|       // Play some old tunes
 | |
|       if(!config.noSound)
 | |
|         Music.play();
 | |
| 
 | |
|       // Run it until the user tells us to quit
 | |
|       startRendering();
 | |
|     }
 | |
|   else if(debugOut) writefln("Skipping rendering");
 | |
| 
 | |
|   if(!config.noSound)
 | |
|     {
 | |
|       soundScene.kill();
 | |
|       shutdownSound();
 | |
|     }
 | |
| 
 | |
|   if(debugOut)
 | |
|     {
 | |
|       writefln();
 | |
|       writefln("%d statics", cd.statics.length);
 | |
|       writefln("%d misc items", cd.miscItems.length);
 | |
|       writefln("%d inventory lights", cd.lights.length);
 | |
|       writefln("%d static lights", cd.statLights.length);
 | |
|       writefln("%d NPCs", cd.npcs.length);
 | |
|       writefln("%d containers", cd.containers.length);
 | |
|       writefln("%d doors", cd.doors.length);
 | |
|       writefln("%d activators", cd.activators.length);
 | |
|       writefln("%d potions", cd.potions.length);
 | |
|       writefln("%d apparatuses", cd.appas.length);
 | |
|       writefln("%d ingredients", cd.ingredients.length);
 | |
|       writefln("%d armors", cd.armors.length);
 | |
|       writefln("%d weapons", cd.weapons.length);
 | |
|       writefln("%d books", cd.books.length);
 | |
|       writefln("%d tools", cd.tools.length);
 | |
|       writefln("%d clothes", cd.clothes.length);
 | |
|       writefln("%d creatures", cd.creatures.length);
 | |
|       writefln();
 | |
|     }
 | |
| 
 | |
|   // This isn't necessary but it's here for testing purposes.
 | |
|   cellList.release(cd);
 | |
| 
 | |
|   // Write some statistics
 | |
|   if(debugOut)
 | |
|     {
 | |
|       poolSize();
 | |
|       writefln(esmRegion);
 | |
|       writefln("Total objects: ", MonsterClass.getTotalObjects);
 | |
|     }
 | |
| }
 |