forked from mirror/openmw-tes3mp
- completed moving music management to Monster script.
- started moving config - created base class for live objects, and example derived classes for lights and doors git-svn-id: https://openmw.svn.sourceforge.net/svnroot/openmw/trunk@67 ea6a568a-9f4f-0410-981a-c910a81bb256
This commit is contained in:
parent
e5fc473731
commit
f194b91b60
19 changed files with 756 additions and 386 deletions
|
@ -28,6 +28,7 @@ import std.file;
|
||||||
import std.path;
|
import std.path;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
|
import monster.monster;
|
||||||
import monster.util.string;
|
import monster.util.string;
|
||||||
|
|
||||||
import core.inifile;
|
import core.inifile;
|
||||||
|
@ -38,10 +39,6 @@ import sound.audio;
|
||||||
import input.keys;
|
import input.keys;
|
||||||
import input.ois;
|
import input.ois;
|
||||||
|
|
||||||
//import sdl.Keysym;
|
|
||||||
|
|
||||||
//import input.events : updateMouseSensitivity;
|
|
||||||
|
|
||||||
ConfigManager config;
|
ConfigManager config;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -52,20 +49,25 @@ ConfigManager config;
|
||||||
* from Morrowind.ini and for configuring OGRE. It doesn't currently
|
* from Morrowind.ini and for configuring OGRE. It doesn't currently
|
||||||
* DO all of this, but it is supposed to in the future.
|
* DO all of this, but it is supposed to in the future.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct ConfigManager
|
struct ConfigManager
|
||||||
{
|
{
|
||||||
|
MonsterObject *mo;
|
||||||
|
|
||||||
IniWriter iniWriter;
|
IniWriter iniWriter;
|
||||||
|
|
||||||
// Sound setting
|
// Sound setting
|
||||||
|
/*
|
||||||
float musicVolume;
|
float musicVolume;
|
||||||
float sfxVolume;
|
float sfxVolume;
|
||||||
float mainVolume;
|
float mainVolume;
|
||||||
bool useMusic;
|
bool useMusic;
|
||||||
|
//*/
|
||||||
|
|
||||||
// Mouse sensitivity
|
// Mouse sensitivity
|
||||||
float mouseSensX;
|
float *mouseSensX;
|
||||||
float mouseSensY;
|
float *mouseSensY;
|
||||||
bool flipMouseY;
|
bool *flipMouseY;
|
||||||
|
|
||||||
// Ogre configuration
|
// Ogre configuration
|
||||||
bool showOgreConfig; // The configuration setting
|
bool showOgreConfig; // The configuration setting
|
||||||
|
@ -97,50 +99,31 @@ struct ConfigManager
|
||||||
// Cell to load at startup
|
// Cell to load at startup
|
||||||
char[] defaultCell;
|
char[] defaultCell;
|
||||||
|
|
||||||
// Check that a given volume is within sane limits (0.0-1.0)
|
|
||||||
private static float saneVol(float vol)
|
|
||||||
{
|
|
||||||
if(!(vol >= 0)) vol = 0;
|
|
||||||
else if(!(vol <= 1)) vol = 1;
|
|
||||||
return vol;
|
|
||||||
}
|
|
||||||
|
|
||||||
// These set the volume to a new value and updates all sounds to
|
// These set the volume to a new value and updates all sounds to
|
||||||
// take notice.
|
// take notice.
|
||||||
void setMusicVolume(float vol)
|
void setMusicVolume(float vol)
|
||||||
{
|
{
|
||||||
musicVolume = saneVol(vol);
|
stack.pushFloat(vol);
|
||||||
|
mo.call("setMusicVolume");
|
||||||
jukebox.updateVolume();
|
|
||||||
battleMusic.updateVolume();
|
|
||||||
}
|
}
|
||||||
|
float getMusicVolume()
|
||||||
|
{ return mo.getFloat("musicVolume"); }
|
||||||
|
|
||||||
void setSfxVolume(float vol)
|
void setSfxVolume(float vol)
|
||||||
{
|
{
|
||||||
sfxVolume = saneVol(vol);
|
stack.pushFloat(vol);
|
||||||
|
mo.call("setSfxVolume");
|
||||||
// TODO: Call some sound manager here to adjust all active sounds
|
|
||||||
}
|
}
|
||||||
|
float getSfxVolume()
|
||||||
|
{ return mo.getFloat("sfxVolume"); }
|
||||||
|
|
||||||
void setMainVolume(float vol)
|
void setMainVolume(float vol)
|
||||||
{
|
{
|
||||||
mainVolume = saneVol(vol);
|
stack.pushFloat(vol);
|
||||||
|
mo.call("setMainVolume");
|
||||||
// Update the sound managers
|
|
||||||
setMusicVolume(musicVolume);
|
|
||||||
setSfxVolume(sfxVolume);
|
|
||||||
}
|
|
||||||
|
|
||||||
// These calculate the "effective" volumes.
|
|
||||||
float calcMusicVolume()
|
|
||||||
{
|
|
||||||
return musicVolume * mainVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calcSfxVolume()
|
|
||||||
{
|
|
||||||
return sfxVolume * mainVolume;
|
|
||||||
}
|
}
|
||||||
|
float getMainVolume()
|
||||||
|
{ return mo.getFloat("mainVolume"); }
|
||||||
|
|
||||||
// Initialize the config manager. Send a 'true' parameter to reset
|
// Initialize the config manager. Send a 'true' parameter to reset
|
||||||
// all keybindings to the default. A lot of this stuff will be moved
|
// all keybindings to the default. A lot of this stuff will be moved
|
||||||
|
@ -149,6 +132,12 @@ struct ConfigManager
|
||||||
// all setup and control should be handled in script code.
|
// all setup and control should be handled in script code.
|
||||||
void initialize(bool reset = false)
|
void initialize(bool reset = false)
|
||||||
{
|
{
|
||||||
|
// Initialize variables from Monster.
|
||||||
|
assert(mo !is null);
|
||||||
|
mouseSensX = mo.getFloatPtr("mouseSensX");
|
||||||
|
mouseSensY = mo.getFloatPtr("mouseSensY");
|
||||||
|
flipMouseY = mo.getBoolPtr("flipMouseY");
|
||||||
|
|
||||||
// Initialize the key binding manager
|
// Initialize the key binding manager
|
||||||
keyBindings.initKeys();
|
keyBindings.initKeys();
|
||||||
|
|
||||||
|
@ -189,14 +178,19 @@ struct ConfigManager
|
||||||
ini.readFile(confFile);
|
ini.readFile(confFile);
|
||||||
|
|
||||||
screenShotNum = ini.getInt("General", "Screenshots", 0);
|
screenShotNum = ini.getInt("General", "Screenshots", 0);
|
||||||
mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
|
float mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
|
||||||
musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
|
float musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
|
||||||
sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
|
float sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
|
||||||
useMusic = ini.getBool("Sound", "Enable Music", true);
|
bool useMusic = ini.getBool("Sound", "Enable Music", true);
|
||||||
|
|
||||||
mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
|
*mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
|
||||||
mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
|
*mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
|
||||||
flipMouseY = ini.getBool("Controls", "Flip Mouse Y Axis", false);
|
*flipMouseY = ini.getBool("Controls", "Flip Mouse Y Axis", false);
|
||||||
|
|
||||||
|
mo.setFloat("mainVolume", mainVolume);
|
||||||
|
mo.setFloat("musicVolume", musicVolume);
|
||||||
|
mo.setFloat("sfxVolume", sfxVolume);
|
||||||
|
mo.setBool("useMusic", useMusic);
|
||||||
|
|
||||||
defaultCell = ini.getString("General", "Default Cell", "Assu");
|
defaultCell = ini.getString("General", "Default Cell", "Assu");
|
||||||
|
|
||||||
|
@ -356,9 +350,9 @@ struct ConfigManager
|
||||||
writeBool("First Run", false);
|
writeBool("First Run", false);
|
||||||
|
|
||||||
section("Controls");
|
section("Controls");
|
||||||
writeFloat("Mouse Sensitivity X", mouseSensX);
|
writeFloat("Mouse Sensitivity X", *mouseSensX);
|
||||||
writeFloat("Mouse Sensitivity Y", mouseSensY);
|
writeFloat("Mouse Sensitivity Y", *mouseSensY);
|
||||||
writeBool("Flip Mouse Y Axis", flipMouseY);
|
writeBool("Flip Mouse Y Axis", *flipMouseY);
|
||||||
|
|
||||||
section("Bindings");
|
section("Bindings");
|
||||||
comment("Key bindings. The strings must match exactly.");
|
comment("Key bindings. The strings must match exactly.");
|
||||||
|
@ -370,10 +364,10 @@ struct ConfigManager
|
||||||
}
|
}
|
||||||
|
|
||||||
section("Sound");
|
section("Sound");
|
||||||
writeFloat("Main Volume", mainVolume);
|
writeFloat("Main Volume", mo.getFloat("mainVolume"));
|
||||||
writeFloat("Music Volume", musicVolume);
|
writeFloat("Music Volume", mo.getFloat("musicVolume"));
|
||||||
writeFloat("SFX Volume", sfxVolume);
|
writeFloat("SFX Volume", mo.getFloat("sfxVolume"));
|
||||||
writeBool("Enable Music", useMusic);
|
writeBool("Enable Music", mo.getBool("useMusic"));
|
||||||
|
|
||||||
section("Game Files");
|
section("Game Files");
|
||||||
foreach(int i, ref s; gameFiles)
|
foreach(int i, ref s; gameFiles)
|
||||||
|
|
|
@ -152,8 +152,8 @@ struct ResourceManager
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
jukebox.setPlaylist(getDir(config.musDir));
|
Music.setPlaylists(getDir(config.musDir),
|
||||||
battleMusic.setPlaylist(getDir(config.musDir2));
|
getDir(config.musDir2));
|
||||||
|
|
||||||
meshLookup.reset();
|
meshLookup.reset();
|
||||||
textureLookup.reset();
|
textureLookup.reset();
|
||||||
|
|
|
@ -50,50 +50,9 @@ import input.ois;
|
||||||
// TODO: Jukebox controls and other state-related data will later be
|
// TODO: Jukebox controls and other state-related data will later be
|
||||||
// handled entirely in script code, as will some of the key bindings.
|
// handled entirely in script code, as will some of the key bindings.
|
||||||
|
|
||||||
// Are we currently playing battle music?
|
|
||||||
bool battle = false;
|
|
||||||
|
|
||||||
// Pause?
|
// Pause?
|
||||||
bool pause = false;
|
bool pause = false;
|
||||||
|
|
||||||
// Temporarily store volume while muted
|
|
||||||
float muteVolume = -1;
|
|
||||||
|
|
||||||
void toggleMute()
|
|
||||||
{
|
|
||||||
if(muteVolume < 0)
|
|
||||||
{
|
|
||||||
muteVolume = config.mainVolume;
|
|
||||||
config.setMainVolume(0);
|
|
||||||
writefln("Muted");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
config.setMainVolume(muteVolume);
|
|
||||||
muteVolume = -1;
|
|
||||||
writefln("Mute off");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch between normal and battle music
|
|
||||||
void toggleBattle()
|
|
||||||
{
|
|
||||||
if(battle)
|
|
||||||
{
|
|
||||||
writefln("Changing to normal music");
|
|
||||||
jukebox.resume();
|
|
||||||
battleMusic.pause();
|
|
||||||
battle=false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
writefln("Changing to battle music");
|
|
||||||
jukebox.pause();
|
|
||||||
battleMusic.resume();
|
|
||||||
battle=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggleFullscreen()
|
void toggleFullscreen()
|
||||||
{
|
{
|
||||||
ogre_toggleFullscreen();
|
ogre_toggleFullscreen();
|
||||||
|
@ -105,24 +64,24 @@ void musVolume(bool increase)
|
||||||
{
|
{
|
||||||
float diff = -volDiff;
|
float diff = -volDiff;
|
||||||
if(increase) diff = -diff;
|
if(increase) diff = -diff;
|
||||||
config.setMusicVolume(diff + config.musicVolume);
|
config.setMusicVolume(diff + config.getMusicVolume);
|
||||||
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.musicVolume);
|
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.getMusicVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfxVolume(bool increase)
|
void sfxVolume(bool increase)
|
||||||
{
|
{
|
||||||
float diff = -volDiff;
|
float diff = -volDiff;
|
||||||
if(increase) diff = -diff;
|
if(increase) diff = -diff;
|
||||||
config.setSfxVolume(diff + config.sfxVolume);
|
config.setSfxVolume(diff + config.getSfxVolume);
|
||||||
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.sfxVolume);
|
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.getSfxVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mainVolume(bool increase)
|
void mainVolume(bool increase)
|
||||||
{
|
{
|
||||||
float diff = -volDiff;
|
float diff = -volDiff;
|
||||||
if(increase) diff = -diff;
|
if(increase) diff = -diff;
|
||||||
config.setMainVolume(diff + config.mainVolume);
|
config.setMainVolume(diff + config.getMainVolume);
|
||||||
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.mainVolume);
|
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.getMainVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
void takeScreenShot()
|
void takeScreenShot()
|
||||||
|
@ -137,9 +96,9 @@ float effMX, effMY;
|
||||||
|
|
||||||
void updateMouseSensitivity()
|
void updateMouseSensitivity()
|
||||||
{
|
{
|
||||||
effMX = config.mouseSensX;
|
effMX = *config.mouseSensX;
|
||||||
effMY = config.mouseSensY;
|
effMY = *config.mouseSensY;
|
||||||
if(config.flipMouseY) effMY = -effMY;
|
if(*config.flipMouseY) effMY = -effMY;
|
||||||
}
|
}
|
||||||
|
|
||||||
void togglePause()
|
void togglePause()
|
||||||
|
@ -212,7 +171,9 @@ extern(C) void d_handleKey(KC keycode, dchar text = 0)
|
||||||
if(k)
|
if(k)
|
||||||
switch(k)
|
switch(k)
|
||||||
{
|
{
|
||||||
case Keys.ToggleBattleMusic: toggleBattle(); break;
|
case Keys.ToggleBattleMusic:
|
||||||
|
Music.toggle();
|
||||||
|
break;
|
||||||
|
|
||||||
case Keys.MainVolUp: mainVolume(true); break;
|
case Keys.MainVolUp: mainVolume(true); break;
|
||||||
case Keys.MainVolDown: mainVolume(false); break;
|
case Keys.MainVolDown: mainVolume(false); break;
|
||||||
|
@ -220,7 +181,7 @@ extern(C) void d_handleKey(KC keycode, dchar text = 0)
|
||||||
case Keys.MusVolDown: musVolume(false); break;
|
case Keys.MusVolDown: musVolume(false); break;
|
||||||
case Keys.SfxVolUp: sfxVolume(true); break;
|
case Keys.SfxVolUp: sfxVolume(true); break;
|
||||||
case Keys.SfxVolDown: sfxVolume(false); break;
|
case Keys.SfxVolDown: sfxVolume(false); break;
|
||||||
case Keys.Mute: toggleMute(); break;
|
case Keys.Mute: Music.toggleMute(); break;
|
||||||
case Keys.Fullscreen: toggleFullscreen(); break;
|
case Keys.Fullscreen: toggleFullscreen(); break;
|
||||||
|
|
||||||
case Keys.PhysMode: bullet_nextMode(); break;
|
case Keys.PhysMode: bullet_nextMode(); break;
|
||||||
|
@ -298,8 +259,7 @@ extern(C) int d_frameStarted(float time)
|
||||||
musCumTime += time;
|
musCumTime += time;
|
||||||
if(musCumTime > musRefresh)
|
if(musCumTime > musRefresh)
|
||||||
{
|
{
|
||||||
jukebox.updateBuffers();
|
Music.updateBuffers();
|
||||||
battleMusic.updateBuffers();
|
|
||||||
musCumTime -= musRefresh;
|
musCumTime -= musRefresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
60
mscripts/cellobject.mn
Normal file
60
mscripts/cellobject.mn
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||||
|
Copyright (C) 2008 Nicolay Korslund
|
||||||
|
Email: < korslund@gmail.com >
|
||||||
|
WWW: http://openmw.snaptoad.com/
|
||||||
|
|
||||||
|
This file (cellobject.mn) 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/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// An object that exists inside a cell. All cell objects must have a
|
||||||
|
// position in space.
|
||||||
|
class CellObject : Object;
|
||||||
|
|
||||||
|
// Position and rotation in space
|
||||||
|
float x, y, z;
|
||||||
|
float r1, r2, r3;
|
||||||
|
|
||||||
|
float scale;
|
||||||
|
|
||||||
|
// Various variables that are currently unused. Most of the strings
|
||||||
|
// will be replaced by object references at some point.
|
||||||
|
|
||||||
|
// Owner of an object / activator
|
||||||
|
char[] owner;
|
||||||
|
|
||||||
|
// A global variable? Don't know what it's used for.
|
||||||
|
char[] global;
|
||||||
|
|
||||||
|
// Reference to a soul trapped creature?
|
||||||
|
char[] soul;
|
||||||
|
|
||||||
|
// Faction owner? Rank?
|
||||||
|
char[] cnam;
|
||||||
|
int indx;
|
||||||
|
|
||||||
|
// Magic value / health / uses of an item?
|
||||||
|
float xchg;
|
||||||
|
|
||||||
|
// ?? See comment below
|
||||||
|
int intv, nam9;
|
||||||
|
|
||||||
|
// ??
|
||||||
|
int fltv;
|
||||||
|
int unam;
|
||||||
|
|
||||||
|
// TODO: Scripts
|
61
mscripts/config.mn
Normal file
61
mscripts/config.mn
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||||
|
Copyright (C) 2008 Nicolay Korslund
|
||||||
|
Email: < korslund@gmail.com >
|
||||||
|
WWW: http://openmw.snaptoad.com/
|
||||||
|
|
||||||
|
This file (config.mn) 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/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Should be a singleton
|
||||||
|
class Config : Object;
|
||||||
|
|
||||||
|
// Only some config options have been moved into Monster. Key bindings
|
||||||
|
// and other low-level settings are still handled in D.
|
||||||
|
|
||||||
|
float musicVolume;
|
||||||
|
float sfxVolume;
|
||||||
|
float mainVolume;
|
||||||
|
bool useMusic;
|
||||||
|
|
||||||
|
float mouseSensX;
|
||||||
|
float mouseSensY;
|
||||||
|
bool flipMouseY;
|
||||||
|
|
||||||
|
// TODO: This could be replaced by some sort of hook placed on
|
||||||
|
// mainVolume. Writing to the variable would automatically update
|
||||||
|
// everything.
|
||||||
|
setMainVolume(float f)
|
||||||
|
{
|
||||||
|
mainVolume = f;
|
||||||
|
music().updateVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
setMusicVolume(float f)
|
||||||
|
{
|
||||||
|
musicVolume = f;
|
||||||
|
music().updateVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
setSfxVolume(float f)
|
||||||
|
{
|
||||||
|
sfxVolume = f;
|
||||||
|
// TODO: Update something here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the "real" music volume
|
||||||
|
float calcMusicVolume() { return mainVolume * musicVolume; }
|
32
mscripts/door.mn
Normal file
32
mscripts/door.mn
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||||
|
Copyright (C) 2008 Nicolay Korslund
|
||||||
|
Email: < korslund@gmail.com >
|
||||||
|
WWW: http://openmw.snaptoad.com/
|
||||||
|
|
||||||
|
This file (door.mn) 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/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Door : LockedObject;
|
||||||
|
|
||||||
|
// Does this door transport you to another cell?
|
||||||
|
bool teleport;
|
||||||
|
|
||||||
|
// Door destination
|
||||||
|
float destx, desty, destz;
|
||||||
|
float destr1, destr2, destr3;
|
||||||
|
char[] destCell;
|
|
@ -2096,12 +2096,12 @@ test()
|
||||||
// lost in a see of output from OGRE, run openmw with the -n switch
|
// lost in a see of output from OGRE, run openmw with the -n switch
|
||||||
// to disable rendering.
|
// to disable rendering.
|
||||||
print();
|
print();
|
||||||
print("Some random GSMT variables:");
|
print("Some random GMST variables:");
|
||||||
print("sThief: ", sThief);
|
print("sThief:", sThief);
|
||||||
print("iAlchemyMod: ", iAlchemyMod);
|
print("iAlchemyMod:", iAlchemyMod);
|
||||||
print("sBribe_100_Gold: ", sBribe_100_Gold);
|
print("sBribe_100_Gold:", sBribe_100_Gold);
|
||||||
print("fMaxWalkSpeed: ", fMaxWalkSpeed);
|
print("fMaxWalkSpeed:", fMaxWalkSpeed);
|
||||||
print("fWereWolfMagicka: ", fWereWolfMagicka);
|
print("fWereWolfMagicka:", fWereWolfMagicka);
|
||||||
print("sEffectSummonFabricant: ", sEffectSummonFabricant);
|
print("sEffectSummonFabricant:", sEffectSummonFabricant);
|
||||||
print();
|
print();
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,15 @@ float fadeInterval = 0.2;
|
||||||
// List of sounds to play
|
// List of sounds to play
|
||||||
char[][] playlist;
|
char[][] playlist;
|
||||||
|
|
||||||
|
// Name used for error messages
|
||||||
|
char[] name;
|
||||||
|
|
||||||
// Index of current song
|
// Index of current song
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
// The music volume, set by the user. Does NOT change to adjust for
|
// The music volume, set by the user. Does NOT change to adjust for
|
||||||
// fading, etc. TODO: This should be stored in a configuration class,
|
// fading, it is only updated when the user changes the volume
|
||||||
// not here.
|
// settings. Updated from Config through updateVolume().
|
||||||
float musVolume;
|
float musVolume;
|
||||||
|
|
||||||
bool isPlaying; // Is a song currently playing?
|
bool isPlaying; // Is a song currently playing?
|
||||||
|
@ -69,6 +72,8 @@ pause()
|
||||||
|
|
||||||
resume()
|
resume()
|
||||||
{
|
{
|
||||||
|
if(!config().useMusic) return;
|
||||||
|
|
||||||
if(isPaused) playSound();
|
if(isPaused) playSound();
|
||||||
else next();
|
else next();
|
||||||
|
|
||||||
|
@ -89,10 +94,13 @@ stop()
|
||||||
|
|
||||||
play()
|
play()
|
||||||
{
|
{
|
||||||
|
if(!config().useMusic) return;
|
||||||
|
|
||||||
if(index >= playlist.length)
|
if(index >= playlist.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setSound(playlist[index]);
|
setSound(playlist[index]);
|
||||||
|
setVolume(musVolume*fadeLevel);
|
||||||
playSound();
|
playSound();
|
||||||
|
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
|
@ -104,6 +112,8 @@ play()
|
||||||
// Play the next song in the playlist
|
// Play the next song in the playlist
|
||||||
next()
|
next()
|
||||||
{
|
{
|
||||||
|
if(!config().useMusic) return;
|
||||||
|
|
||||||
if(isPlaying)
|
if(isPlaying)
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
|
@ -119,11 +129,10 @@ next()
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the new music volume setting. TODO: This should be read from a
|
// Set the new music volume setting.
|
||||||
// config object instead.
|
updateVolume(float f)
|
||||||
updateVolume(float vol)
|
|
||||||
{
|
{
|
||||||
musVolume = vol;
|
musVolume = f;
|
||||||
if(isPlaying)
|
if(isPlaying)
|
||||||
setVolume(musVolume*fadeLevel);
|
setVolume(musVolume*fadeLevel);
|
||||||
}
|
}
|
||||||
|
|
30
mscripts/light.mn
Normal file
30
mscripts/light.mn
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||||
|
Copyright (C) 2008 Nicolay Korslund
|
||||||
|
Email: < korslund@gmail.com >
|
||||||
|
WWW: http://openmw.snaptoad.com/
|
||||||
|
|
||||||
|
This file (light.mn) 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/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Light : CellObject;
|
||||||
|
|
||||||
|
// Time left in seconds (for carried lights)
|
||||||
|
float lifetime;
|
||||||
|
|
||||||
|
// Is this a carryable light?
|
||||||
|
bool carry;
|
30
mscripts/lockedobject.mn
Normal file
30
mscripts/lockedobject.mn
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||||
|
Copyright (C) 2008 Nicolay Korslund
|
||||||
|
Email: < korslund@gmail.com >
|
||||||
|
WWW: http://openmw.snaptoad.com/
|
||||||
|
|
||||||
|
This file (lockedobject.mn) 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/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Objects that can have a lock level and a trap
|
||||||
|
class LockedObject : CellObject;
|
||||||
|
|
||||||
|
// ID of key and trap type.
|
||||||
|
char[] key, trap;
|
||||||
|
|
||||||
|
int lockLevel;
|
104
mscripts/music.mn
Normal file
104
mscripts/music.mn
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||||
|
Copyright (C) 2008 Nicolay Korslund
|
||||||
|
Email: < korslund@gmail.com >
|
||||||
|
WWW: http://openmw.snaptoad.com/
|
||||||
|
|
||||||
|
This file (jukebox.mn) 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/ .
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This class controls all the music.
|
||||||
|
class Music : Object;
|
||||||
|
|
||||||
|
// Create one jukebox for normal music, and one for battle music. This
|
||||||
|
// way we can pause / fade out one while the other resumes / fades in.
|
||||||
|
Jukebox jukebox, battle;
|
||||||
|
|
||||||
|
bool isBattle;
|
||||||
|
bool isMuted;
|
||||||
|
|
||||||
|
// Toggle between normal and battle music
|
||||||
|
toggle()
|
||||||
|
{
|
||||||
|
if(isBattle)
|
||||||
|
{
|
||||||
|
print("Switching to normal music");
|
||||||
|
battle.pause();
|
||||||
|
jukebox.resume();
|
||||||
|
isBattle = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print("Switching to battle music");
|
||||||
|
jukebox.pause();
|
||||||
|
battle.resume();
|
||||||
|
isBattle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMute()
|
||||||
|
{
|
||||||
|
if(!isMuted)
|
||||||
|
{
|
||||||
|
jukebox.updateVolume(0);
|
||||||
|
battle.updateVolume(0);
|
||||||
|
print("Muted");
|
||||||
|
isMuted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updateVolume();
|
||||||
|
isMuted = false;
|
||||||
|
print("Mute off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
{
|
||||||
|
jukebox = new Jukebox;
|
||||||
|
battle = new Jukebox;
|
||||||
|
|
||||||
|
jukebox.name = "Main";
|
||||||
|
battle.name = "Battle";
|
||||||
|
|
||||||
|
isBattle = false;
|
||||||
|
|
||||||
|
updateVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
play()
|
||||||
|
{
|
||||||
|
if(isBattle) battle.play();
|
||||||
|
else jukebox.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called at startup, listing the files in the "explore" and "battle"
|
||||||
|
// music directories respectively.
|
||||||
|
setPlaylists(char[][] normal, char[][] battlelist)
|
||||||
|
{
|
||||||
|
jukebox.setPlaylist(normal);
|
||||||
|
battle.setPlaylist(battlelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called whenever the volume settings (music volume or main volume)
|
||||||
|
// have been changed by the user.
|
||||||
|
updateVolume()
|
||||||
|
{
|
||||||
|
float v = config().calcMusicVolume();
|
||||||
|
jukebox.updateVolume(v);
|
||||||
|
battle.updateVolume(v);
|
||||||
|
}
|
|
@ -27,6 +27,8 @@ import monster.monster;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.date;
|
import std.date;
|
||||||
import core.resource : rnd;
|
import core.resource : rnd;
|
||||||
|
import core.config;
|
||||||
|
import sound.music;
|
||||||
|
|
||||||
// Set up the base Monster classes we need in OpenMW
|
// Set up the base Monster classes we need in OpenMW
|
||||||
void initMonsterScripts()
|
void initMonsterScripts()
|
||||||
|
@ -37,6 +39,10 @@ void initMonsterScripts()
|
||||||
// Make sure the Object class is loaded
|
// Make sure the Object class is loaded
|
||||||
auto mc = new MonsterClass("Object", "object.mn");
|
auto mc = new MonsterClass("Object", "object.mn");
|
||||||
|
|
||||||
|
// Create the config object too (only needed here because Object
|
||||||
|
// refers to Config. This will change.)
|
||||||
|
config.mo = (new MonsterClass("Config")).createObject;
|
||||||
|
|
||||||
// Bind various functions
|
// Bind various functions
|
||||||
mc.bind("print", { print(); });
|
mc.bind("print", { print(); });
|
||||||
mc.bind("randInt",
|
mc.bind("randInt",
|
||||||
|
@ -44,6 +50,10 @@ void initMonsterScripts()
|
||||||
(stack.popInt,stack.popInt));});
|
(stack.popInt,stack.popInt));});
|
||||||
mc.bind("sleep", new IdleSleep);
|
mc.bind("sleep", new IdleSleep);
|
||||||
|
|
||||||
|
// Temporary hacks
|
||||||
|
mc.bind("config", { stack.pushObject(config.mo); });
|
||||||
|
mc.bind("music", { stack.pushObject(Music.controlM); });
|
||||||
|
|
||||||
// Load and run the test script
|
// Load and run the test script
|
||||||
mc = new MonsterClass("Test");
|
mc = new MonsterClass("Test");
|
||||||
mc.createObject().call("test");
|
mc.createObject().call("test");
|
||||||
|
|
|
@ -24,6 +24,11 @@
|
||||||
// This is the base class of all OpenMW Monster classes.
|
// This is the base class of all OpenMW Monster classes.
|
||||||
class Object;
|
class Object;
|
||||||
|
|
||||||
|
// TODO: These are temporary hacks. A more elegant solution will be
|
||||||
|
// used once the language supports it.
|
||||||
|
native Config config();
|
||||||
|
native Music music();
|
||||||
|
|
||||||
// Sleeps a given amount of time
|
// Sleeps a given amount of time
|
||||||
idle sleep(float seconds);
|
idle sleep(float seconds);
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,11 @@ state printMessage
|
||||||
{
|
{
|
||||||
// This state code will run as long as the object is in this state.
|
// This state code will run as long as the object is in this state.
|
||||||
begin:
|
begin:
|
||||||
sleep(15);
|
sleep(10);
|
||||||
|
|
||||||
|
loop:
|
||||||
print("Howdy from the world of Monster scripts!");
|
print("Howdy from the world of Monster scripts!");
|
||||||
print("This script is located in mscripts/test.mn. Check it out!");
|
print("This script is located in mscripts/test.mn. Check it out!");
|
||||||
goto begin;
|
sleep(60);
|
||||||
|
goto loop;
|
||||||
}
|
}
|
||||||
|
|
55
openmw.d
55
openmw.d
|
@ -260,11 +260,11 @@ void main(char[][] args)
|
||||||
|
|
||||||
// Insert the meshes of statics into the scene
|
// Insert the meshes of statics into the scene
|
||||||
foreach(ref LiveStatic ls; cd.statics)
|
foreach(ref LiveStatic ls; cd.statics)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale, true);
|
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||||
// Inventory lights
|
// Inventory lights
|
||||||
foreach(ref LiveLight ls; cd.lights)
|
foreach(ref LiveLight ls; cd.lights)
|
||||||
{
|
{
|
||||||
NodePtr n = putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
|
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
|
||||||
if(!noSound)
|
if(!noSound)
|
||||||
{
|
{
|
||||||
|
@ -274,16 +274,19 @@ void main(char[][] args)
|
||||||
writefln("Dynamic light %s has sound %s", ls.m.id, s.id);
|
writefln("Dynamic light %s has sound %s", ls.m.id, s.id);
|
||||||
ls.loopSound = soundScene.insert(s, true);
|
ls.loopSound = soundScene.insert(s, true);
|
||||||
if(ls.loopSound)
|
if(ls.loopSound)
|
||||||
ls.loopSound.setPos(ls.base.pos.position[0],
|
{
|
||||||
ls.base.pos.position[1],
|
auto p = ls.getPos();
|
||||||
ls.base.pos.position[2]);
|
ls.loopSound.setPos(p.position[0],
|
||||||
|
p.position[1],
|
||||||
|
p.position[2]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Static lights
|
// Static lights
|
||||||
foreach(ref LiveLight ls; cd.statLights)
|
foreach(ref LiveLight ls; cd.statLights)
|
||||||
{
|
{
|
||||||
NodePtr n = putObject(ls.m.model, &ls.base.pos, ls.base.scale, true);
|
NodePtr n = putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||||
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
|
ls.lightNode = attachLight(n, ls.m.data.color, ls.m.data.radius);
|
||||||
if(!noSound)
|
if(!noSound)
|
||||||
{
|
{
|
||||||
|
@ -293,56 +296,59 @@ void main(char[][] args)
|
||||||
writefln("Static light %s has sound %s", ls.m.id, s.id);
|
writefln("Static light %s has sound %s", ls.m.id, s.id);
|
||||||
ls.loopSound = soundScene.insert(s, true);
|
ls.loopSound = soundScene.insert(s, true);
|
||||||
if(ls.loopSound)
|
if(ls.loopSound)
|
||||||
ls.loopSound.setPos(ls.base.pos.position[0],
|
{
|
||||||
ls.base.pos.position[1],
|
auto p = ls.getPos();
|
||||||
ls.base.pos.position[2]);
|
ls.loopSound.setPos(p.position[0],
|
||||||
|
p.position[1],
|
||||||
|
p.position[2]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Misc items
|
// Misc items
|
||||||
foreach(ref LiveMisc ls; cd.miscItems)
|
foreach(ref LiveMisc ls; cd.miscItems)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
/*
|
/*
|
||||||
// NPCs (these are complicated, usually do not have normal meshes)
|
// NPCs (these are complicated, usually do not have normal meshes)
|
||||||
foreach(ref LiveNPC ls; cd.npcs)
|
foreach(ref LiveNPC ls; cd.npcs)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
*/
|
*/
|
||||||
// Containers
|
// Containers
|
||||||
foreach(ref LiveContainer ls; cd.containers)
|
foreach(ref LiveContainer ls; cd.containers)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale, true);
|
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||||
// Doors
|
// Doors
|
||||||
foreach(ref LiveDoor ls; cd.doors)
|
foreach(ref LiveDoor ls; cd.doors)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Activators (including beds etc)
|
// Activators (including beds etc)
|
||||||
foreach(ref LiveActivator ls; cd.activators)
|
foreach(ref LiveActivator ls; cd.activators)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale, true);
|
putObject(ls.m.model, ls.getPos(), ls.getScale(), true);
|
||||||
// Potions
|
// Potions
|
||||||
foreach(ref LivePotion ls; cd.potions)
|
foreach(ref LivePotion ls; cd.potions)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Apparatus
|
// Apparatus
|
||||||
foreach(ref LiveApparatus ls; cd.appas)
|
foreach(ref LiveApparatus ls; cd.appas)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Ingredients
|
// Ingredients
|
||||||
foreach(ref LiveIngredient ls; cd.ingredients)
|
foreach(ref LiveIngredient ls; cd.ingredients)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Armors
|
// Armors
|
||||||
foreach(ref LiveArmor ls; cd.armors)
|
foreach(ref LiveArmor ls; cd.armors)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Weapons
|
// Weapons
|
||||||
foreach(ref LiveWeapon ls; cd.weapons)
|
foreach(ref LiveWeapon ls; cd.weapons)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Books
|
// Books
|
||||||
foreach(ref LiveBook ls; cd.books)
|
foreach(ref LiveBook ls; cd.books)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Clothes
|
// Clothes
|
||||||
foreach(ref LiveClothing ls; cd.clothes)
|
foreach(ref LiveClothing ls; cd.clothes)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Tools
|
// Tools
|
||||||
foreach(ref LiveTool ls; cd.tools)
|
foreach(ref LiveTool ls; cd.tools)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
// Creatures (not displayed very well yet)
|
// Creatures (not displayed very well yet)
|
||||||
foreach(ref LiveCreature ls; cd.creatures)
|
foreach(ref LiveCreature ls; cd.creatures)
|
||||||
putObject(ls.m.model, &ls.base.pos, ls.base.scale);
|
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||||
|
|
||||||
// Initialize the internal input and event manager. The
|
// Initialize the internal input and event manager. The
|
||||||
// lower-level input system (OIS) is initialized by the
|
// lower-level input system (OIS) is initialized by the
|
||||||
|
@ -350,7 +356,8 @@ void main(char[][] args)
|
||||||
initializeInput();
|
initializeInput();
|
||||||
|
|
||||||
// Start swangin'
|
// Start swangin'
|
||||||
if(!noSound) jukebox.play();
|
if(!noSound)
|
||||||
|
Music.play();
|
||||||
|
|
||||||
// Run it until the user tells us to quit
|
// Run it until the user tells us to quit
|
||||||
startRendering();
|
startRendering();
|
||||||
|
|
269
scene/celldata.d
269
scene/celldata.d
|
@ -28,6 +28,7 @@ import std.stdio;
|
||||||
public import esm.esmmain;
|
public import esm.esmmain;
|
||||||
|
|
||||||
import core.memory;
|
import core.memory;
|
||||||
|
import monster.monster;
|
||||||
|
|
||||||
import util.reglist;
|
import util.reglist;
|
||||||
|
|
||||||
|
@ -38,71 +39,26 @@ import sound.audio;
|
||||||
|
|
||||||
import scene.player;
|
import scene.player;
|
||||||
|
|
||||||
// Base properties common to all live objects. Currently extremely
|
|
||||||
// sketchy. TODO: This will all be handled in Monster script at some
|
|
||||||
// point.
|
|
||||||
struct LiveObjectBase
|
|
||||||
{
|
|
||||||
// Should this stuff be in here?
|
|
||||||
bool disabled; // Disabled in game
|
|
||||||
bool deleted; // Deleted relative to plugin file
|
|
||||||
|
|
||||||
// Used for objects created in-game, like custom potions or
|
|
||||||
// enchanted items. These can be completely deleted. It is also used
|
|
||||||
// for creatures created from leveled lists.
|
|
||||||
bool transient;
|
|
||||||
|
|
||||||
// Is this a door that teleports to another cell?
|
|
||||||
bool teleport;
|
|
||||||
|
|
||||||
// Scale, 1.0 is normal.
|
|
||||||
float scale;
|
|
||||||
|
|
||||||
// Owner of an object / activator
|
|
||||||
char[] owner;
|
|
||||||
|
|
||||||
// A global variable? Don't know what it's used for.
|
|
||||||
char[] global;
|
|
||||||
|
|
||||||
// Reference to a soul trapped creature?
|
|
||||||
char[] soul;
|
|
||||||
|
|
||||||
// Faction owner? Rank?
|
|
||||||
char[] cnam;
|
|
||||||
int indx;
|
|
||||||
|
|
||||||
// Magic value / health / uses of an item?
|
|
||||||
float xchg;
|
|
||||||
|
|
||||||
// ?? See comment below
|
|
||||||
int intv, nam9;
|
|
||||||
|
|
||||||
// Destination for a door
|
|
||||||
Placement destPos;
|
|
||||||
char[] destCell;
|
|
||||||
|
|
||||||
// Lock level?
|
|
||||||
int fltv;
|
|
||||||
|
|
||||||
// For locked doors and containers
|
|
||||||
char[] key, trap;
|
|
||||||
|
|
||||||
// Position in 3D world
|
|
||||||
Placement pos;
|
|
||||||
|
|
||||||
// ??
|
|
||||||
byte unam;
|
|
||||||
|
|
||||||
// TODO: Scripts
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic version of a "live" object
|
// Generic version of a "live" object
|
||||||
struct GenLive(T)
|
struct GenLive(T)
|
||||||
{
|
{
|
||||||
// This HAS to be a pointer, since we load the LOB after copying the
|
// Instance of class CellObject or a derived class (depending on
|
||||||
// LiveWhatever into the linked list.
|
// object type)
|
||||||
LiveObjectBase *base;
|
MonsterObject *obj;
|
||||||
T *m;
|
T *m;
|
||||||
|
|
||||||
|
Placement *getPos()
|
||||||
|
{
|
||||||
|
assert(obj !is null);
|
||||||
|
// This belongs in HACK-land
|
||||||
|
return cast(Placement*)obj.getFloatPtr("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
float getScale()
|
||||||
|
{
|
||||||
|
assert(obj !is null);
|
||||||
|
return obj.getFloat("scale");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alias GenLive!(Static) LiveStatic;
|
alias GenLive!(Static) LiveStatic;
|
||||||
|
@ -121,16 +77,27 @@ alias GenLive!(Door) LiveDoor;
|
||||||
alias GenLive!(Misc) LiveMisc;
|
alias GenLive!(Misc) LiveMisc;
|
||||||
alias GenLive!(Container) LiveContainer;
|
alias GenLive!(Container) LiveContainer;
|
||||||
|
|
||||||
|
// TODO: This is sort of redundant. Eliminate or rework it later.
|
||||||
struct LiveLight
|
struct LiveLight
|
||||||
{
|
{
|
||||||
LiveObjectBase *base;
|
MonsterObject *obj;
|
||||||
Light *m;
|
Light *m;
|
||||||
|
|
||||||
|
Placement *getPos()
|
||||||
|
{
|
||||||
|
assert(obj !is null);
|
||||||
|
// This belongs in HACK-land
|
||||||
|
return cast(Placement*)obj.getFloatPtr("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
float getScale()
|
||||||
|
{
|
||||||
|
assert(obj !is null);
|
||||||
|
return obj.getFloat("scale");
|
||||||
|
}
|
||||||
|
|
||||||
NodePtr lightNode;
|
NodePtr lightNode;
|
||||||
SoundInstance *loopSound;
|
SoundInstance *loopSound;
|
||||||
|
|
||||||
// Lifetime left, in seconds?
|
|
||||||
float time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellData
|
class CellData
|
||||||
|
@ -172,6 +139,9 @@ class CellData
|
||||||
{
|
{
|
||||||
reg = r;
|
reg = r;
|
||||||
killCell(); // Make sure all data is initialized.
|
killCell(); // Make sure all data is initialized.
|
||||||
|
|
||||||
|
// Set up the Monster classes if it's not done already
|
||||||
|
setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kills all data and initialize the object for reuse.
|
// Kills all data and initialize the object for reuse.
|
||||||
|
@ -286,7 +256,22 @@ class CellData
|
||||||
loadReferences();
|
loadReferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadReferences()
|
private:
|
||||||
|
|
||||||
|
static
|
||||||
|
MonsterClass cellObjC, doorC, lightC, lockedC;
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
if(cellObjC !is null) return;
|
||||||
|
|
||||||
|
cellObjC = new MonsterClass("CellObject");
|
||||||
|
doorC = new MonsterClass("Door");
|
||||||
|
lightC = new MonsterClass("Light");
|
||||||
|
lockedC = new MonsterClass("LockedObject");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadReferences()
|
||||||
{
|
{
|
||||||
with(esFile)
|
with(esFile)
|
||||||
{
|
{
|
||||||
|
@ -315,19 +300,18 @@ class CellData
|
||||||
Item itm = cellRefs.lookup(refr);
|
Item itm = cellRefs.lookup(refr);
|
||||||
ItemT it = ItemT(itm);
|
ItemT it = ItemT(itm);
|
||||||
|
|
||||||
// Create a new base object that holds all our reference
|
MonsterObject *mo;
|
||||||
// data.
|
|
||||||
LiveObjectBase *base = reg.newT!(LiveObjectBase)();
|
|
||||||
|
|
||||||
// These should be ordered according to how commonly they
|
// These should be ordered according to how commonly they
|
||||||
// occur and how large the reference lists are.
|
// occur.
|
||||||
|
|
||||||
// Static mesh - probably the most common
|
// Static mesh - probably the most common object type
|
||||||
if(Static *s = it.getStatic())
|
if(Static *s = it.getStatic())
|
||||||
{
|
{
|
||||||
LiveStatic ls;
|
LiveStatic ls;
|
||||||
ls.m = s;
|
ls.m = s;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
statics.insert(ls);
|
statics.insert(ls);
|
||||||
stat = true;
|
stat = true;
|
||||||
}
|
}
|
||||||
|
@ -336,7 +320,8 @@ class CellData
|
||||||
{
|
{
|
||||||
LiveMisc ls;
|
LiveMisc ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
miscItems.insert(ls);
|
miscItems.insert(ls);
|
||||||
}
|
}
|
||||||
// Lights and containers too
|
// Lights and containers too
|
||||||
|
@ -344,20 +329,25 @@ class CellData
|
||||||
{
|
{
|
||||||
LiveLight ls;
|
LiveLight ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = lightC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
|
|
||||||
ls.time = m.data.time;
|
mo.setFloat("lifetime", m.data.time);
|
||||||
|
bool carry = (m.data.flags&Light.Flags.Carry) != 0;
|
||||||
|
mo.setBool("carry", carry);
|
||||||
|
|
||||||
if(m.data.flags&Light.Flags.Carry)
|
if(carry)
|
||||||
lights.insert(ls);
|
lights.insert(ls);
|
||||||
else
|
else
|
||||||
statLights.insert(ls);
|
statLights.insert(ls);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(Container *c = it.getContainer())
|
else if(Container *c = it.getContainer())
|
||||||
{
|
{
|
||||||
LiveContainer ls;
|
LiveContainer ls;
|
||||||
ls.m = c;
|
ls.m = c;
|
||||||
ls.base = base;
|
ls.obj = lockedC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
containers.insert(ls);
|
containers.insert(ls);
|
||||||
container = true;
|
container = true;
|
||||||
}
|
}
|
||||||
|
@ -366,7 +356,8 @@ class CellData
|
||||||
{
|
{
|
||||||
LiveDoor ls;
|
LiveDoor ls;
|
||||||
ls.m = d;
|
ls.m = d;
|
||||||
ls.base = base;
|
ls.obj = doorC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
doors.insert(ls);
|
doors.insert(ls);
|
||||||
door = true;
|
door = true;
|
||||||
}
|
}
|
||||||
|
@ -375,7 +366,8 @@ class CellData
|
||||||
{
|
{
|
||||||
LiveActivator ls;
|
LiveActivator ls;
|
||||||
ls.m = a;
|
ls.m = a;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
activators.insert(ls);
|
activators.insert(ls);
|
||||||
activator = true;
|
activator = true;
|
||||||
}
|
}
|
||||||
|
@ -384,84 +376,96 @@ class CellData
|
||||||
{
|
{
|
||||||
LiveNPC ls;
|
LiveNPC ls;
|
||||||
ls.m = n;
|
ls.m = n;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
npcs.insert(ls);
|
npcs.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Potion *p = it.getPotion())
|
else if(Potion *p = it.getPotion())
|
||||||
{
|
{
|
||||||
LivePotion ls;
|
LivePotion ls;
|
||||||
ls.m = p;
|
ls.m = p;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
potions.insert(ls);
|
potions.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Apparatus *m = it.getApparatus())
|
else if(Apparatus *m = it.getApparatus())
|
||||||
{
|
{
|
||||||
LiveApparatus ls;
|
LiveApparatus ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
appas.insert(ls);
|
appas.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Ingredient *m = it.getIngredient())
|
else if(Ingredient *m = it.getIngredient())
|
||||||
{
|
{
|
||||||
LiveIngredient ls;
|
LiveIngredient ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
ingredients.insert(ls);
|
ingredients.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Armor *m = it.getArmor())
|
else if(Armor *m = it.getArmor())
|
||||||
{
|
{
|
||||||
LiveArmor ls;
|
LiveArmor ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
armors.insert(ls);
|
armors.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Weapon *m = it.getWeapon())
|
else if(Weapon *m = it.getWeapon())
|
||||||
{
|
{
|
||||||
LiveWeapon ls;
|
LiveWeapon ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
weapons.insert(ls);
|
weapons.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Book *m = it.getBook())
|
else if(Book *m = it.getBook())
|
||||||
{
|
{
|
||||||
LiveBook ls;
|
LiveBook ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
books.insert(ls);
|
books.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Clothing *m = it.getClothing())
|
else if(Clothing *m = it.getClothing())
|
||||||
{
|
{
|
||||||
LiveClothing ls;
|
LiveClothing ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
clothes.insert(ls);
|
clothes.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Tool *m = it.getPick())
|
else if(Tool *m = it.getPick())
|
||||||
{
|
{
|
||||||
LiveTool ls;
|
LiveTool ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
tools.insert(ls);
|
tools.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Tool *m = it.getProbe())
|
else if(Tool *m = it.getProbe())
|
||||||
{
|
{
|
||||||
LiveTool ls;
|
LiveTool ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
tools.insert(ls);
|
tools.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Tool *m = it.getRepair())
|
else if(Tool *m = it.getRepair())
|
||||||
{
|
{
|
||||||
LiveTool ls;
|
LiveTool ls;
|
||||||
ls.m = m;
|
ls.m = m;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
tools.insert(ls);
|
tools.insert(ls);
|
||||||
}
|
}
|
||||||
else if(Creature *c = it.getCreature())
|
else if(Creature *c = it.getCreature())
|
||||||
{
|
{
|
||||||
LiveCreature ls;
|
LiveCreature ls;
|
||||||
ls.m = c;
|
ls.m = c;
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject;
|
||||||
|
mo = ls.obj;
|
||||||
creatures.insert(ls);
|
creatures.insert(ls);
|
||||||
}
|
}
|
||||||
else if(LeveledCreatures *l = it.getCreatureList)
|
else if(LeveledCreatures *l = it.getCreatureList)
|
||||||
|
@ -471,7 +475,7 @@ class CellData
|
||||||
ls.m = l.instCreature(playerData.level);
|
ls.m = l.instCreature(playerData.level);
|
||||||
if(ls.m != null)
|
if(ls.m != null)
|
||||||
{
|
{
|
||||||
ls.base = base;
|
ls.obj = cellObjC.createObject; mo = ls.obj;
|
||||||
creatures.insert(ls);
|
creatures.insert(ls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -480,16 +484,10 @@ class CellData
|
||||||
// Now that the object has found it's place, load data
|
// Now that the object has found it's place, load data
|
||||||
// into base.
|
// into base.
|
||||||
|
|
||||||
with(*base)
|
with(*mo)
|
||||||
{
|
{
|
||||||
// ALL variables must be initialized here
|
|
||||||
disabled = false;
|
|
||||||
deleted = false;
|
|
||||||
transient = false;
|
|
||||||
teleport = false;
|
|
||||||
|
|
||||||
// Scale
|
// Scale
|
||||||
scale = getHNOFloat("XSCL", 1.0);
|
setFloat("scale", getHNOFloat("XSCL", 1.0));
|
||||||
|
|
||||||
// Statics only need the position data. Skip the
|
// Statics only need the position data. Skip the
|
||||||
// unneeded calls to isNextSub() as an optimization.
|
// unneeded calls to isNextSub() as an optimization.
|
||||||
|
@ -497,25 +495,28 @@ class CellData
|
||||||
|
|
||||||
// An NPC that owns this object (and will get angry if
|
// An NPC that owns this object (and will get angry if
|
||||||
// you steal it)
|
// you steal it)
|
||||||
owner = getHNOString("ANAM");
|
setString8("owner", getHNOString("ANAM"));
|
||||||
|
|
||||||
// ??? I have no idea, link to a global variable
|
// I have no idea, link to a global variable perhaps?
|
||||||
global = getHNOString("BNAM");
|
setString8("global", getHNOString("BNAM"));
|
||||||
|
|
||||||
// ID of creature trapped in a soul gem (?)
|
// ID of creature trapped in a soul gem (?)
|
||||||
soul = getHNOString("XSOL");
|
setString8("soul", getHNOString("XSOL"));
|
||||||
|
|
||||||
// ?? CNAM has a faction name, might be for
|
// ?? CNAM has a faction name, might be for
|
||||||
// objects/beds etc belonging to a faction.
|
// objects/beds etc belonging to a faction.
|
||||||
cnam = getHNOString("CNAM");
|
{
|
||||||
|
char[] cnam = getHNOString("CNAM");
|
||||||
|
setString8("cnam", cnam);
|
||||||
|
|
||||||
// INDX might be PC faction rank required to use the
|
// INDX might be PC faction rank required to use the
|
||||||
// item? Sometimes is -1.
|
// item? Sometimes is -1.
|
||||||
if(cnam.length) indx = getHNInt("INDX");
|
if(cnam.length) setInt("indx", getHNInt("INDX"));
|
||||||
|
}
|
||||||
|
|
||||||
// Possibly weapon health, number of uses left or
|
// Possibly weapon health, number of uses left or
|
||||||
// weapon magic charge?
|
// weapon magic charge?
|
||||||
xchg = getHNOFloat("XCHG", 0.0);
|
setFloat("xchg", getHNOFloat("XCHG", 0.0));
|
||||||
|
|
||||||
// I have no idea, these are present some times, often
|
// I have no idea, these are present some times, often
|
||||||
// along with owner (ANAM) and sometimes otherwise. Is
|
// along with owner (ANAM) and sometimes otherwise. Is
|
||||||
|
@ -523,57 +524,71 @@ class CellData
|
||||||
// lights. Perhaps something to do with remaining
|
// lights. Perhaps something to do with remaining
|
||||||
// light "charge". I haven't tried reading it as a
|
// light "charge". I haven't tried reading it as a
|
||||||
// float in those cases.
|
// float in those cases.
|
||||||
intv = getHNOInt("INTV", 0);
|
setInt("intv", getHNOInt("INTV", 0));
|
||||||
nam9 = getHNOInt("NAM9", 0);
|
setInt("nam9", getHNOInt("NAM9", 0));
|
||||||
|
|
||||||
// Present for doors that teleport you to another
|
// Present for doors that teleport you to another
|
||||||
// cell.
|
// cell.
|
||||||
if(door && isNextSub("DODT"))
|
if(door && isNextSub("DODT"))
|
||||||
{
|
{
|
||||||
teleport = true;
|
setBool("teleport", true);
|
||||||
readHExact(&destPos, destPos.sizeof);
|
|
||||||
|
|
||||||
// Destination cell (optitional?)
|
// Warning, HACK! Will be fixed when we implement
|
||||||
destCell = getHNOString("DNAM");
|
// structs in Monster.
|
||||||
|
Placement *p = cast(Placement*)getFloatPtr("destx");
|
||||||
|
readHExact(p, Placement.sizeof);
|
||||||
|
|
||||||
|
// Destination cell (optional?)
|
||||||
|
setString8("destCell", getHNOString("DNAM"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(door || container)
|
if(door || container)
|
||||||
{
|
{
|
||||||
// Lock level (I think)
|
// Lock level (I think)
|
||||||
fltv = getHNOInt("FLTV", 0);
|
setInt("lockLevel", getHNOInt("FLTV", 0));
|
||||||
|
|
||||||
// For locked doors and containers
|
// For locked doors and containers
|
||||||
key = getHNOString("KNAM");
|
setString8("key", getHNOString("KNAM"));
|
||||||
trap = getHNOString("TNAM");
|
setString8("trap", getHNOString("TNAM"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(activator)
|
if(activator)
|
||||||
{
|
{
|
||||||
// Occurs ONCE in Morrowind.esm, for an activator.
|
// Occurs ONCE in Morrowind.esm, for an activator.
|
||||||
unam = getHNOByte("UNAM", 0);
|
setInt("unam", getHNOByte("UNAM", 0));
|
||||||
|
|
||||||
// Occurs in Tribunal.esm, eg. in the cell
|
// Occurs in Tribunal.esm, eg. in the cell
|
||||||
// "Mournhold, Plaza Brindisi Dorom", where it has
|
// "Mournhold, Plaza Brindisi Dorom", where it has
|
||||||
// the value 100.
|
// the value 100.
|
||||||
fltv = getHNOInt("FLTV", 0);
|
setInt("fltv", getHNOInt("FLTV", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
readpos:
|
readpos:
|
||||||
// Position of this object within the cell
|
|
||||||
readHNExact(&pos, Placement.sizeof, "DATA");
|
// Position and rotation of this object within the
|
||||||
|
// cell
|
||||||
|
|
||||||
|
// TODO: This is a HACK. It assumes the class variable
|
||||||
|
// floats are placed consecutively in memory in the
|
||||||
|
// right order. This is true, but is a very bug prone
|
||||||
|
// method of doing this. Will fix when Monster gets
|
||||||
|
// structs. (See the DODT record above also.)
|
||||||
|
Placement *pos = cast(Placement*)getFloatPtr("x");
|
||||||
|
readHNExact(pos, Placement.sizeof, "DATA");
|
||||||
|
|
||||||
// TODO/FIXME: Very temporary. Set player position at
|
// TODO/FIXME: Very temporary. Set player position at
|
||||||
// the first door we find.
|
// the first door we find.
|
||||||
if(door && !playerData.posSet)
|
if(door && !playerData.posSet)
|
||||||
{
|
{
|
||||||
playerData.posSet = true;
|
playerData.posSet = true;
|
||||||
playerData.position = pos;
|
playerData.position = *pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // End of while(hasMoreSubs)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
} // End of loadReferences()
|
||||||
|
} // End of class CellData
|
||||||
|
|
||||||
CellFreelist cellList;
|
CellFreelist cellList;
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,6 @@ class SoundException : Exception
|
||||||
this(char[] caller, char[] msg) { super(caller ~ " SoundException: " ~ msg); }
|
this(char[] caller, char[] msg) { super(caller ~ " SoundException: " ~ msg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
MusicManager jukebox;
|
|
||||||
MusicManager battleMusic;
|
|
||||||
|
|
||||||
ALCdevice *Device = null;
|
ALCdevice *Device = null;
|
||||||
ALCcontext *Context = null;
|
ALCcontext *Context = null;
|
||||||
|
|
||||||
|
@ -54,16 +51,12 @@ void initializeSound()
|
||||||
|
|
||||||
alcMakeContextCurrent(Context);
|
alcMakeContextCurrent(Context);
|
||||||
|
|
||||||
MusicManager.sinit();
|
Music.init();
|
||||||
|
|
||||||
jukebox.initialize("Main");
|
|
||||||
battleMusic.initialize("Battle");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void shutdownSound()
|
void shutdownSound()
|
||||||
{
|
{
|
||||||
jukebox.shutdown();
|
Music.shutdown();
|
||||||
battleMusic.shutdown();
|
|
||||||
|
|
||||||
alcMakeContextCurrent(null);
|
alcMakeContextCurrent(null);
|
||||||
if(Context) alcDestroyContext(Context);
|
if(Context) alcDestroyContext(Context);
|
||||||
|
@ -72,10 +65,17 @@ void shutdownSound()
|
||||||
Device = null;
|
Device = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkALError(char[] what = "")
|
float saneVol(float vol)
|
||||||
|
{
|
||||||
|
if(!(vol >= 0)) vol = 0;
|
||||||
|
else if(!(vol <= 1)) vol = 1;
|
||||||
|
return vol;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkALError(char[] what)
|
||||||
{
|
{
|
||||||
ALenum err = alGetError();
|
ALenum err = alGetError();
|
||||||
if(what.length) what = " while " ~ what;
|
what = " while " ~ what;
|
||||||
if(err != AL_NO_ERROR)
|
if(err != AL_NO_ERROR)
|
||||||
throw new Exception(format("OpenAL error%s: (%x) %s", what, err,
|
throw new Exception(format("OpenAL error%s: (%x) %s", what, err,
|
||||||
toString(alGetString(err))));
|
toString(alGetString(err))));
|
||||||
|
|
251
sound/music.d
251
sound/music.d
|
@ -42,30 +42,121 @@ class Idle_waitUntilFinished : IdleFunction
|
||||||
|
|
||||||
bool hasFinished(MonsterObject *mo)
|
bool hasFinished(MonsterObject *mo)
|
||||||
{
|
{
|
||||||
MusicManager *mgr = cast(MusicManager*)mo.extra;
|
Jukebox mgr = cast(Jukebox)mo.extra;
|
||||||
|
|
||||||
// Return when the music is no longer playing
|
// Return when the music is no longer playing
|
||||||
return !mgr.isPlaying();
|
return !mgr.isPlaying();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple music player, has a playlist and can pause/resume music.
|
struct Music
|
||||||
struct MusicManager
|
{
|
||||||
|
private:
|
||||||
|
static:
|
||||||
|
|
||||||
|
// The Monster classes and objects
|
||||||
|
MonsterClass jukeC, controlC;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
MonsterObject *controlM;
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
assert(jukeC is null);
|
||||||
|
assert(controlC is null);
|
||||||
|
assert(controlM is null);
|
||||||
|
|
||||||
|
jukeC = MonsterClass.get("Jukebox");
|
||||||
|
jukeC.bind("waitUntilFinished",
|
||||||
|
new Idle_waitUntilFinished);
|
||||||
|
|
||||||
|
jukeC.bind("setSound", { Jukebox.get().setSound(); });
|
||||||
|
jukeC.bind("setVolume", { Jukebox.get().setVolume(); });
|
||||||
|
jukeC.bind("playSound", { Jukebox.get().playSound(); });
|
||||||
|
jukeC.bind("stopSound", { Jukebox.get().stopSound(); });
|
||||||
|
|
||||||
|
jukeC.bindConst({new Jukebox(params.obj()); });
|
||||||
|
|
||||||
|
controlC = MonsterClass.get("Music");
|
||||||
|
controlM = controlC.createObject;
|
||||||
|
controlM.call("setup");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pushSArray(char[][] strs)
|
||||||
|
{
|
||||||
|
AIndex arr[];
|
||||||
|
arr.length = strs.length;
|
||||||
|
|
||||||
|
// Create the array indices for each element string
|
||||||
|
foreach(i, ref elm; arr)
|
||||||
|
elm = arrays.create(strs[i]).getIndex();
|
||||||
|
|
||||||
|
// Push the final array
|
||||||
|
stack.pushArray(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPlaylists(char[][] normal, char[][] battle)
|
||||||
|
{
|
||||||
|
pushSArray(normal);
|
||||||
|
pushSArray(battle);
|
||||||
|
controlM.call("setPlaylists");
|
||||||
|
}
|
||||||
|
|
||||||
|
void play()
|
||||||
|
{
|
||||||
|
if(controlM !is null)
|
||||||
|
controlM.call("play");
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle()
|
||||||
|
{
|
||||||
|
if(controlM !is null)
|
||||||
|
controlM.call("toggle");
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleMute()
|
||||||
|
{
|
||||||
|
if(controlM !is null)
|
||||||
|
controlM.call("toggleMute");
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBuffers()
|
||||||
|
{
|
||||||
|
foreach(ref MonsterObject b; jukeC)
|
||||||
|
Jukebox.get(b).updateBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown()
|
||||||
|
{
|
||||||
|
foreach(ref MonsterObject b; jukeC)
|
||||||
|
Jukebox.get(b).shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple music player, has a playlist and can pause/resume
|
||||||
|
// music. Most of the high-level code is in mscripts/jukebox.mn.
|
||||||
|
class Jukebox
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// Maximum buffer length, divided up among OpenAL buffers
|
// Maximum buffer length, divided up among OpenAL buffers
|
||||||
const uint bufLength = 128*1024;
|
const uint bufLength = 128*1024;
|
||||||
|
const uint numBufs = 4;
|
||||||
|
|
||||||
char[] name;
|
char[] getName()
|
||||||
|
{
|
||||||
|
if(mo is null) return "(no name)";
|
||||||
|
else return mo.getString8("name");
|
||||||
|
}
|
||||||
|
|
||||||
void fail(char[] msg)
|
void fail(char[] msg)
|
||||||
{
|
{
|
||||||
throw new SoundException(name ~ " Jukebox", msg);
|
throw new SoundException(getName() ~ " Jukebox", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
ALuint sID; // Sound id
|
ALuint sID; // Sound id
|
||||||
ALuint bIDs[4]; // Buffers
|
ALuint bIDs[numBufs]; // Buffers
|
||||||
|
|
||||||
ALenum bufFormat;
|
ALenum bufFormat;
|
||||||
ALint bufRate;
|
ALint bufRate;
|
||||||
|
@ -75,82 +166,61 @@ struct MusicManager
|
||||||
|
|
||||||
static ubyte[] outData;
|
static ubyte[] outData;
|
||||||
|
|
||||||
// The Jukebox class
|
static this()
|
||||||
static MonsterClass mc;
|
{
|
||||||
|
outData.length = bufLength / numBufs;
|
||||||
|
}
|
||||||
|
|
||||||
// The jukebox Monster object
|
// The jukebox Monster object
|
||||||
MonsterObject *mo;
|
MonsterObject *mo;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static MusicManager *get()
|
this(MonsterObject *m)
|
||||||
{ return cast(MusicManager*)params.obj().extra; }
|
|
||||||
|
|
||||||
static void sinit()
|
|
||||||
{
|
{
|
||||||
assert(mc is null);
|
mo = m;
|
||||||
mc = new MonsterClass("Jukebox", "jukebox.mn");
|
m.extra = cast(void*)this;
|
||||||
mc.bind("waitUntilFinished",
|
|
||||||
new Idle_waitUntilFinished);
|
|
||||||
|
|
||||||
mc.bind("setSound", { get().setSound(); });
|
|
||||||
mc.bind("setVolume", { get().setVolume(); });
|
|
||||||
mc.bind("playSound", { get().playSound(); });
|
|
||||||
mc.bind("stopSound", { get().stopSound(); });
|
|
||||||
|
|
||||||
outData.length = bufLength / bIDs.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the jukebox
|
|
||||||
void initialize(char[] name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
|
||||||
sID = 0;
|
sID = 0;
|
||||||
bIDs[] = 0;
|
bIDs[] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Jukebox get(MonsterObject *m)
|
||||||
|
{
|
||||||
|
auto j = cast(Jukebox)m.extra;
|
||||||
|
assert(j !is null);
|
||||||
|
assert(j.mo == m);
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Jukebox get(ref MonsterObject m)
|
||||||
|
{ return get(&m); }
|
||||||
|
|
||||||
|
static Jukebox get()
|
||||||
|
{ return get(params.obj()); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Disable music
|
||||||
|
void shutdown()
|
||||||
|
{
|
||||||
|
mo.call("stop");
|
||||||
|
|
||||||
|
if(fileHandle) avc_closeAVFile(fileHandle);
|
||||||
fileHandle = null;
|
fileHandle = null;
|
||||||
|
audioHandle = null;
|
||||||
|
|
||||||
mo = mc.createObject();
|
if(sID)
|
||||||
mo.extra = this;
|
{
|
||||||
}
|
alSourceStop(sID);
|
||||||
|
alDeleteSources(1, &sID);
|
||||||
|
checkALError("disabling music");
|
||||||
|
sID = 0;
|
||||||
|
|
||||||
// Called whenever the volume configuration values are changed by
|
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
||||||
// the user.
|
checkALError("deleting buffers");
|
||||||
void updateVolume()
|
bIDs[] = 0;
|
||||||
{
|
}
|
||||||
stack.pushFloat(config.calcMusicVolume());
|
|
||||||
mo.call("updateVolume");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give a music play list
|
|
||||||
void setPlaylist(char[][] pl)
|
|
||||||
{
|
|
||||||
AIndex arr[];
|
|
||||||
arr.length = pl.length;
|
|
||||||
|
|
||||||
// Create the array indices for each element string
|
|
||||||
foreach(i, ref elm; arr)
|
|
||||||
elm = arrays.create(pl[i]).getIndex();
|
|
||||||
|
|
||||||
// Push the final array
|
|
||||||
stack.pushArray(arr);
|
|
||||||
|
|
||||||
mo.call("setPlaylist");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause current track
|
|
||||||
void pause() { mo.call("pause"); }
|
|
||||||
|
|
||||||
// Resume. Starts playing sound, with fade in
|
|
||||||
void resume()
|
|
||||||
{
|
|
||||||
if(!config.useMusic) return;
|
|
||||||
mo.call("resume");
|
|
||||||
}
|
|
||||||
|
|
||||||
void play()
|
|
||||||
{
|
|
||||||
if(!config.useMusic) return;
|
|
||||||
mo.call("play");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSound()
|
void setSound()
|
||||||
|
@ -158,7 +228,7 @@ struct MusicManager
|
||||||
char[] fname = stack.popString8();
|
char[] fname = stack.popString8();
|
||||||
|
|
||||||
// Generate a source to play back with if needed
|
// Generate a source to play back with if needed
|
||||||
if(!sID)
|
if(sID == 0)
|
||||||
{
|
{
|
||||||
alGenSources(1, &sID);
|
alGenSources(1, &sID);
|
||||||
checkALError("generating buffers");
|
checkALError("generating buffers");
|
||||||
|
@ -167,17 +237,17 @@ struct MusicManager
|
||||||
alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE);
|
alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||||
|
|
||||||
alGenBuffers(bIDs.length, bIDs.ptr);
|
alGenBuffers(bIDs.length, bIDs.ptr);
|
||||||
|
|
||||||
updateVolume();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Kill current track, but keep the sID source.
|
// Kill current track, but keep the sID source.
|
||||||
alSourceStop(sID);
|
alSourceStop(sID);
|
||||||
|
checkALError("stopping current track");
|
||||||
|
|
||||||
alSourcei(sID, AL_BUFFER, 0);
|
alSourcei(sID, AL_BUFFER, 0);
|
||||||
//alDeleteBuffers(bIDs.length, bIDs.ptr);
|
//alDeleteBuffers(bIDs.length, bIDs.ptr);
|
||||||
//bIDs[] = 0;
|
//bIDs[] = 0;
|
||||||
checkALError("killing current track");
|
checkALError("resetting buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fileHandle) avc_closeAVFile(fileHandle);
|
if(fileHandle) avc_closeAVFile(fileHandle);
|
||||||
|
@ -230,7 +300,8 @@ struct MusicManager
|
||||||
}
|
}
|
||||||
|
|
||||||
if(bufFormat == 0)
|
if(bufFormat == 0)
|
||||||
fail(format("Unhandled format (%d channels, %d bits) for music track %s", ch, bits, fname));
|
fail(format("Unhandled format (%d channels, %d bits) for music track %s",
|
||||||
|
ch, bits, fname));
|
||||||
|
|
||||||
// Fill the buffers
|
// Fill the buffers
|
||||||
foreach(int i, ref b; bIDs)
|
foreach(int i, ref b; bIDs)
|
||||||
|
@ -257,14 +328,15 @@ struct MusicManager
|
||||||
{
|
{
|
||||||
float volume = stack.popFloat();
|
float volume = stack.popFloat();
|
||||||
|
|
||||||
|
volume = saneVol(volume);
|
||||||
|
|
||||||
// Set the new volume
|
// Set the new volume
|
||||||
if(sID) alSourcef(sID, AL_GAIN, volume);
|
if(sID) alSourcef(sID, AL_GAIN, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
void playSound()
|
void playSound()
|
||||||
{
|
{
|
||||||
if(!sID || !config.useMusic)
|
if(!sID) return;
|
||||||
return;
|
|
||||||
|
|
||||||
alSourcePlay(sID);
|
alSourcePlay(sID);
|
||||||
checkALError("starting music");
|
checkALError("starting music");
|
||||||
|
@ -280,6 +352,7 @@ struct MusicManager
|
||||||
{
|
{
|
||||||
ALint state;
|
ALint state;
|
||||||
alGetSourcei(sID, AL_SOURCE_STATE, &state);
|
alGetSourcei(sID, AL_SOURCE_STATE, &state);
|
||||||
|
checkALError("getting status");
|
||||||
|
|
||||||
return state == AL_PLAYING;
|
return state == AL_PLAYING;
|
||||||
}
|
}
|
||||||
|
@ -293,7 +366,7 @@ struct MusicManager
|
||||||
ALint count;
|
ALint count;
|
||||||
alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count);
|
alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count);
|
||||||
|
|
||||||
checkALError();
|
checkALError("getting number of unprocessed buffers");
|
||||||
|
|
||||||
for(int i = 0;i < count;i++)
|
for(int i = 0;i < count;i++)
|
||||||
{
|
{
|
||||||
|
@ -307,30 +380,8 @@ struct MusicManager
|
||||||
{
|
{
|
||||||
alBufferData(bid, bufFormat, outData.ptr, length, bufRate);
|
alBufferData(bid, bufFormat, outData.ptr, length, bufRate);
|
||||||
alSourceQueueBuffers(sID, 1, &bid);
|
alSourceQueueBuffers(sID, 1, &bid);
|
||||||
checkALError();
|
checkALError("queueing buffers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable music
|
|
||||||
void shutdown()
|
|
||||||
{
|
|
||||||
mo.call("stop");
|
|
||||||
|
|
||||||
if(fileHandle) avc_closeAVFile(fileHandle);
|
|
||||||
fileHandle = null;
|
|
||||||
audioHandle = null;
|
|
||||||
|
|
||||||
if(sID)
|
|
||||||
{
|
|
||||||
alSourceStop(sID);
|
|
||||||
alDeleteSources(1, &sID);
|
|
||||||
checkALError("disabling music");
|
|
||||||
sID = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
|
||||||
checkALError();
|
|
||||||
bIDs[] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,6 @@ struct RegionList(Value)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
||||||
alias Node* Iterator;
|
alias Node* Iterator;
|
||||||
|
|
||||||
// Equivalent to calling remove() on all elements
|
// Equivalent to calling remove() on all elements
|
||||||
|
|
Loading…
Reference in a new issue