mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-04 15:45:34 +00:00
- 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.stdio;
|
||||
|
||||
import monster.monster;
|
||||
import monster.util.string;
|
||||
|
||||
import core.inifile;
|
||||
|
@ -38,10 +39,6 @@ import sound.audio;
|
|||
import input.keys;
|
||||
import input.ois;
|
||||
|
||||
//import sdl.Keysym;
|
||||
|
||||
//import input.events : updateMouseSensitivity;
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
/*
|
||||
|
@ -52,20 +49,25 @@ ConfigManager config;
|
|||
* from Morrowind.ini and for configuring OGRE. It doesn't currently
|
||||
* DO all of this, but it is supposed to in the future.
|
||||
*/
|
||||
|
||||
struct ConfigManager
|
||||
{
|
||||
MonsterObject *mo;
|
||||
|
||||
IniWriter iniWriter;
|
||||
|
||||
// Sound setting
|
||||
/*
|
||||
float musicVolume;
|
||||
float sfxVolume;
|
||||
float mainVolume;
|
||||
bool useMusic;
|
||||
//*/
|
||||
|
||||
// Mouse sensitivity
|
||||
float mouseSensX;
|
||||
float mouseSensY;
|
||||
bool flipMouseY;
|
||||
float *mouseSensX;
|
||||
float *mouseSensY;
|
||||
bool *flipMouseY;
|
||||
|
||||
// Ogre configuration
|
||||
bool showOgreConfig; // The configuration setting
|
||||
|
@ -97,50 +99,31 @@ struct ConfigManager
|
|||
// Cell to load at startup
|
||||
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
|
||||
// take notice.
|
||||
void setMusicVolume(float vol)
|
||||
{
|
||||
musicVolume = saneVol(vol);
|
||||
|
||||
jukebox.updateVolume();
|
||||
battleMusic.updateVolume();
|
||||
stack.pushFloat(vol);
|
||||
mo.call("setMusicVolume");
|
||||
}
|
||||
float getMusicVolume()
|
||||
{ return mo.getFloat("musicVolume"); }
|
||||
|
||||
void setSfxVolume(float vol)
|
||||
{
|
||||
sfxVolume = saneVol(vol);
|
||||
|
||||
// TODO: Call some sound manager here to adjust all active sounds
|
||||
stack.pushFloat(vol);
|
||||
mo.call("setSfxVolume");
|
||||
}
|
||||
float getSfxVolume()
|
||||
{ return mo.getFloat("sfxVolume"); }
|
||||
|
||||
void setMainVolume(float vol)
|
||||
{
|
||||
mainVolume = saneVol(vol);
|
||||
|
||||
// Update the sound managers
|
||||
setMusicVolume(musicVolume);
|
||||
setSfxVolume(sfxVolume);
|
||||
}
|
||||
|
||||
// These calculate the "effective" volumes.
|
||||
float calcMusicVolume()
|
||||
{
|
||||
return musicVolume * mainVolume;
|
||||
}
|
||||
|
||||
float calcSfxVolume()
|
||||
{
|
||||
return sfxVolume * mainVolume;
|
||||
stack.pushFloat(vol);
|
||||
mo.call("setMainVolume");
|
||||
}
|
||||
float getMainVolume()
|
||||
{ return mo.getFloat("mainVolume"); }
|
||||
|
||||
// Initialize the config manager. Send a 'true' parameter to reset
|
||||
// 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.
|
||||
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
|
||||
keyBindings.initKeys();
|
||||
|
||||
|
@ -189,14 +178,19 @@ struct ConfigManager
|
|||
ini.readFile(confFile);
|
||||
|
||||
screenShotNum = ini.getInt("General", "Screenshots", 0);
|
||||
mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
|
||||
musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
|
||||
sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
|
||||
useMusic = ini.getBool("Sound", "Enable Music", true);
|
||||
float mainVolume = saneVol(ini.getFloat("Sound", "Main Volume", 0.7));
|
||||
float musicVolume = saneVol(ini.getFloat("Sound", "Music Volume", 0.5));
|
||||
float sfxVolume = saneVol(ini.getFloat("Sound", "SFX Volume", 0.5));
|
||||
bool useMusic = ini.getBool("Sound", "Enable Music", true);
|
||||
|
||||
mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
|
||||
mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
|
||||
flipMouseY = ini.getBool("Controls", "Flip Mouse Y Axis", false);
|
||||
*mouseSensX = ini.getFloat("Controls", "Mouse Sensitivity X", 0.2);
|
||||
*mouseSensY = ini.getFloat("Controls", "Mouse Sensitivity Y", 0.2);
|
||||
*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");
|
||||
|
||||
|
@ -356,9 +350,9 @@ struct ConfigManager
|
|||
writeBool("First Run", false);
|
||||
|
||||
section("Controls");
|
||||
writeFloat("Mouse Sensitivity X", mouseSensX);
|
||||
writeFloat("Mouse Sensitivity Y", mouseSensY);
|
||||
writeBool("Flip Mouse Y Axis", flipMouseY);
|
||||
writeFloat("Mouse Sensitivity X", *mouseSensX);
|
||||
writeFloat("Mouse Sensitivity Y", *mouseSensY);
|
||||
writeBool("Flip Mouse Y Axis", *flipMouseY);
|
||||
|
||||
section("Bindings");
|
||||
comment("Key bindings. The strings must match exactly.");
|
||||
|
@ -370,10 +364,10 @@ struct ConfigManager
|
|||
}
|
||||
|
||||
section("Sound");
|
||||
writeFloat("Main Volume", mainVolume);
|
||||
writeFloat("Music Volume", musicVolume);
|
||||
writeFloat("SFX Volume", sfxVolume);
|
||||
writeBool("Enable Music", useMusic);
|
||||
writeFloat("Main Volume", mo.getFloat("mainVolume"));
|
||||
writeFloat("Music Volume", mo.getFloat("musicVolume"));
|
||||
writeFloat("SFX Volume", mo.getFloat("sfxVolume"));
|
||||
writeBool("Enable Music", mo.getBool("useMusic"));
|
||||
|
||||
section("Game Files");
|
||||
foreach(int i, ref s; gameFiles)
|
||||
|
|
|
@ -152,8 +152,8 @@ struct ResourceManager
|
|||
return res;
|
||||
}
|
||||
|
||||
jukebox.setPlaylist(getDir(config.musDir));
|
||||
battleMusic.setPlaylist(getDir(config.musDir2));
|
||||
Music.setPlaylists(getDir(config.musDir),
|
||||
getDir(config.musDir2));
|
||||
|
||||
meshLookup.reset();
|
||||
textureLookup.reset();
|
||||
|
|
|
@ -50,50 +50,9 @@ import input.ois;
|
|||
// TODO: Jukebox controls and other state-related data will later be
|
||||
// handled entirely in script code, as will some of the key bindings.
|
||||
|
||||
// Are we currently playing battle music?
|
||||
bool battle = false;
|
||||
|
||||
// Pause?
|
||||
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()
|
||||
{
|
||||
ogre_toggleFullscreen();
|
||||
|
@ -105,24 +64,24 @@ void musVolume(bool increase)
|
|||
{
|
||||
float diff = -volDiff;
|
||||
if(increase) diff = -diff;
|
||||
config.setMusicVolume(diff + config.musicVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.musicVolume);
|
||||
config.setMusicVolume(diff + config.getMusicVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " music volume to ", config.getMusicVolume);
|
||||
}
|
||||
|
||||
void sfxVolume(bool increase)
|
||||
{
|
||||
float diff = -volDiff;
|
||||
if(increase) diff = -diff;
|
||||
config.setSfxVolume(diff + config.sfxVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.sfxVolume);
|
||||
config.setSfxVolume(diff + config.getSfxVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " sound effect volume to ", config.getSfxVolume);
|
||||
}
|
||||
|
||||
void mainVolume(bool increase)
|
||||
{
|
||||
float diff = -volDiff;
|
||||
if(increase) diff = -diff;
|
||||
config.setMainVolume(diff + config.mainVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.mainVolume);
|
||||
config.setMainVolume(diff + config.getMainVolume);
|
||||
writefln(increase?"Increasing":"Decreasing", " main volume to ", config.getMainVolume);
|
||||
}
|
||||
|
||||
void takeScreenShot()
|
||||
|
@ -137,9 +96,9 @@ float effMX, effMY;
|
|||
|
||||
void updateMouseSensitivity()
|
||||
{
|
||||
effMX = config.mouseSensX;
|
||||
effMY = config.mouseSensY;
|
||||
if(config.flipMouseY) effMY = -effMY;
|
||||
effMX = *config.mouseSensX;
|
||||
effMY = *config.mouseSensY;
|
||||
if(*config.flipMouseY) effMY = -effMY;
|
||||
}
|
||||
|
||||
void togglePause()
|
||||
|
@ -212,7 +171,9 @@ extern(C) void d_handleKey(KC keycode, dchar text = 0)
|
|||
if(k)
|
||||
switch(k)
|
||||
{
|
||||
case Keys.ToggleBattleMusic: toggleBattle(); break;
|
||||
case Keys.ToggleBattleMusic:
|
||||
Music.toggle();
|
||||
break;
|
||||
|
||||
case Keys.MainVolUp: mainVolume(true); 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.SfxVolUp: sfxVolume(true); 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.PhysMode: bullet_nextMode(); break;
|
||||
|
@ -298,8 +259,7 @@ extern(C) int d_frameStarted(float time)
|
|||
musCumTime += time;
|
||||
if(musCumTime > musRefresh)
|
||||
{
|
||||
jukebox.updateBuffers();
|
||||
battleMusic.updateBuffers();
|
||||
Music.updateBuffers();
|
||||
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
|
||||
// to disable rendering.
|
||||
print();
|
||||
print("Some random GSMT variables:");
|
||||
print("sThief: ", sThief);
|
||||
print("iAlchemyMod: ", iAlchemyMod);
|
||||
print("sBribe_100_Gold: ", sBribe_100_Gold);
|
||||
print("fMaxWalkSpeed: ", fMaxWalkSpeed);
|
||||
print("fWereWolfMagicka: ", fWereWolfMagicka);
|
||||
print("sEffectSummonFabricant: ", sEffectSummonFabricant);
|
||||
print("Some random GMST variables:");
|
||||
print("sThief:", sThief);
|
||||
print("iAlchemyMod:", iAlchemyMod);
|
||||
print("sBribe_100_Gold:", sBribe_100_Gold);
|
||||
print("fMaxWalkSpeed:", fMaxWalkSpeed);
|
||||
print("fWereWolfMagicka:", fWereWolfMagicka);
|
||||
print("sEffectSummonFabricant:", sEffectSummonFabricant);
|
||||
print();
|
||||
}
|
||||
|
|
|
@ -41,12 +41,15 @@ float fadeInterval = 0.2;
|
|||
// List of sounds to play
|
||||
char[][] playlist;
|
||||
|
||||
// Name used for error messages
|
||||
char[] name;
|
||||
|
||||
// Index of current song
|
||||
int index;
|
||||
|
||||
// The music volume, set by the user. Does NOT change to adjust for
|
||||
// fading, etc. TODO: This should be stored in a configuration class,
|
||||
// not here.
|
||||
// fading, it is only updated when the user changes the volume
|
||||
// settings. Updated from Config through updateVolume().
|
||||
float musVolume;
|
||||
|
||||
bool isPlaying; // Is a song currently playing?
|
||||
|
@ -69,6 +72,8 @@ pause()
|
|||
|
||||
resume()
|
||||
{
|
||||
if(!config().useMusic) return;
|
||||
|
||||
if(isPaused) playSound();
|
||||
else next();
|
||||
|
||||
|
@ -89,10 +94,13 @@ stop()
|
|||
|
||||
play()
|
||||
{
|
||||
if(!config().useMusic) return;
|
||||
|
||||
if(index >= playlist.length)
|
||||
return;
|
||||
|
||||
setSound(playlist[index]);
|
||||
setVolume(musVolume*fadeLevel);
|
||||
playSound();
|
||||
|
||||
isPlaying = true;
|
||||
|
@ -104,6 +112,8 @@ play()
|
|||
// Play the next song in the playlist
|
||||
next()
|
||||
{
|
||||
if(!config().useMusic) return;
|
||||
|
||||
if(isPlaying)
|
||||
stop();
|
||||
|
||||
|
@ -119,11 +129,10 @@ next()
|
|||
play();
|
||||
}
|
||||
|
||||
// Set the new music volume setting. TODO: This should be read from a
|
||||
// config object instead.
|
||||
updateVolume(float vol)
|
||||
// Set the new music volume setting.
|
||||
updateVolume(float f)
|
||||
{
|
||||
musVolume = vol;
|
||||
musVolume = f;
|
||||
if(isPlaying)
|
||||
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.date;
|
||||
import core.resource : rnd;
|
||||
import core.config;
|
||||
import sound.music;
|
||||
|
||||
// Set up the base Monster classes we need in OpenMW
|
||||
void initMonsterScripts()
|
||||
|
@ -37,6 +39,10 @@ void initMonsterScripts()
|
|||
// Make sure the Object class is loaded
|
||||
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
|
||||
mc.bind("print", { print(); });
|
||||
mc.bind("randInt",
|
||||
|
@ -44,6 +50,10 @@ void initMonsterScripts()
|
|||
(stack.popInt,stack.popInt));});
|
||||
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
|
||||
mc = new MonsterClass("Test");
|
||||
mc.createObject().call("test");
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
// This is the base class of all OpenMW Monster classes.
|
||||
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
|
||||
idle sleep(float seconds);
|
||||
|
||||
|
|
|
@ -10,8 +10,11 @@ state printMessage
|
|||
{
|
||||
// This state code will run as long as the object is in this state.
|
||||
begin:
|
||||
sleep(15);
|
||||
sleep(10);
|
||||
|
||||
loop:
|
||||
print("Howdy from the world of Monster scripts!");
|
||||
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
|
||||
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
|
||||
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);
|
||||
if(!noSound)
|
||||
{
|
||||
|
@ -274,16 +274,19 @@ void main(char[][] args)
|
|||
writefln("Dynamic light %s has sound %s", ls.m.id, s.id);
|
||||
ls.loopSound = soundScene.insert(s, true);
|
||||
if(ls.loopSound)
|
||||
ls.loopSound.setPos(ls.base.pos.position[0],
|
||||
ls.base.pos.position[1],
|
||||
ls.base.pos.position[2]);
|
||||
{
|
||||
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.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);
|
||||
if(!noSound)
|
||||
{
|
||||
|
@ -293,56 +296,59 @@ void main(char[][] args)
|
|||
writefln("Static light %s has sound %s", ls.m.id, s.id);
|
||||
ls.loopSound = soundScene.insert(s, true);
|
||||
if(ls.loopSound)
|
||||
ls.loopSound.setPos(ls.base.pos.position[0],
|
||||
ls.base.pos.position[1],
|
||||
ls.base.pos.position[2]);
|
||||
{
|
||||
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.base.pos, ls.base.scale);
|
||||
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.base.pos, ls.base.scale);
|
||||
putObject(ls.m.model, ls.getPos(), ls.getScale());
|
||||
*/
|
||||
// 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
|
||||
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)
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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)
|
||||
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
|
||||
// lower-level input system (OIS) is initialized by the
|
||||
|
@ -350,7 +356,8 @@ void main(char[][] args)
|
|||
initializeInput();
|
||||
|
||||
// Start swangin'
|
||||
if(!noSound) jukebox.play();
|
||||
if(!noSound)
|
||||
Music.play();
|
||||
|
||||
// Run it until the user tells us to quit
|
||||
startRendering();
|
||||
|
|
269
scene/celldata.d
269
scene/celldata.d
|
@ -28,6 +28,7 @@ import std.stdio;
|
|||
public import esm.esmmain;
|
||||
|
||||
import core.memory;
|
||||
import monster.monster;
|
||||
|
||||
import util.reglist;
|
||||
|
||||
|
@ -38,71 +39,26 @@ import sound.audio;
|
|||
|
||||
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
|
||||
struct GenLive(T)
|
||||
{
|
||||
// This HAS to be a pointer, since we load the LOB after copying the
|
||||
// LiveWhatever into the linked list.
|
||||
LiveObjectBase *base;
|
||||
// Instance of class CellObject or a derived class (depending on
|
||||
// object type)
|
||||
MonsterObject *obj;
|
||||
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;
|
||||
|
@ -121,16 +77,27 @@ alias GenLive!(Door) LiveDoor;
|
|||
alias GenLive!(Misc) LiveMisc;
|
||||
alias GenLive!(Container) LiveContainer;
|
||||
|
||||
// TODO: This is sort of redundant. Eliminate or rework it later.
|
||||
struct LiveLight
|
||||
{
|
||||
LiveObjectBase *base;
|
||||
MonsterObject *obj;
|
||||
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;
|
||||
SoundInstance *loopSound;
|
||||
|
||||
// Lifetime left, in seconds?
|
||||
float time;
|
||||
}
|
||||
|
||||
class CellData
|
||||
|
@ -172,6 +139,9 @@ class CellData
|
|||
{
|
||||
reg = r;
|
||||
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.
|
||||
|
@ -286,7 +256,22 @@ class CellData
|
|||
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)
|
||||
{
|
||||
|
@ -315,19 +300,18 @@ class CellData
|
|||
Item itm = cellRefs.lookup(refr);
|
||||
ItemT it = ItemT(itm);
|
||||
|
||||
// Create a new base object that holds all our reference
|
||||
// data.
|
||||
LiveObjectBase *base = reg.newT!(LiveObjectBase)();
|
||||
MonsterObject *mo;
|
||||
|
||||
// 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())
|
||||
{
|
||||
LiveStatic ls;
|
||||
ls.m = s;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
statics.insert(ls);
|
||||
stat = true;
|
||||
}
|
||||
|
@ -336,7 +320,8 @@ class CellData
|
|||
{
|
||||
LiveMisc ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
miscItems.insert(ls);
|
||||
}
|
||||
// Lights and containers too
|
||||
|
@ -344,20 +329,25 @@ class CellData
|
|||
{
|
||||
LiveLight ls;
|
||||
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);
|
||||
else
|
||||
statLights.insert(ls);
|
||||
}
|
||||
|
||||
else if(Container *c = it.getContainer())
|
||||
{
|
||||
LiveContainer ls;
|
||||
ls.m = c;
|
||||
ls.base = base;
|
||||
ls.obj = lockedC.createObject;
|
||||
mo = ls.obj;
|
||||
containers.insert(ls);
|
||||
container = true;
|
||||
}
|
||||
|
@ -366,7 +356,8 @@ class CellData
|
|||
{
|
||||
LiveDoor ls;
|
||||
ls.m = d;
|
||||
ls.base = base;
|
||||
ls.obj = doorC.createObject;
|
||||
mo = ls.obj;
|
||||
doors.insert(ls);
|
||||
door = true;
|
||||
}
|
||||
|
@ -375,7 +366,8 @@ class CellData
|
|||
{
|
||||
LiveActivator ls;
|
||||
ls.m = a;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
activators.insert(ls);
|
||||
activator = true;
|
||||
}
|
||||
|
@ -384,84 +376,96 @@ class CellData
|
|||
{
|
||||
LiveNPC ls;
|
||||
ls.m = n;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
npcs.insert(ls);
|
||||
}
|
||||
else if(Potion *p = it.getPotion())
|
||||
{
|
||||
LivePotion ls;
|
||||
ls.m = p;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
potions.insert(ls);
|
||||
}
|
||||
else if(Apparatus *m = it.getApparatus())
|
||||
{
|
||||
LiveApparatus ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
appas.insert(ls);
|
||||
}
|
||||
else if(Ingredient *m = it.getIngredient())
|
||||
{
|
||||
LiveIngredient ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
ingredients.insert(ls);
|
||||
}
|
||||
else if(Armor *m = it.getArmor())
|
||||
{
|
||||
LiveArmor ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
armors.insert(ls);
|
||||
}
|
||||
else if(Weapon *m = it.getWeapon())
|
||||
{
|
||||
LiveWeapon ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
weapons.insert(ls);
|
||||
}
|
||||
else if(Book *m = it.getBook())
|
||||
{
|
||||
LiveBook ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
books.insert(ls);
|
||||
}
|
||||
else if(Clothing *m = it.getClothing())
|
||||
{
|
||||
LiveClothing ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
clothes.insert(ls);
|
||||
}
|
||||
else if(Tool *m = it.getPick())
|
||||
{
|
||||
LiveTool ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
tools.insert(ls);
|
||||
}
|
||||
else if(Tool *m = it.getProbe())
|
||||
{
|
||||
LiveTool ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
tools.insert(ls);
|
||||
}
|
||||
else if(Tool *m = it.getRepair())
|
||||
{
|
||||
LiveTool ls;
|
||||
ls.m = m;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
tools.insert(ls);
|
||||
}
|
||||
else if(Creature *c = it.getCreature())
|
||||
{
|
||||
LiveCreature ls;
|
||||
ls.m = c;
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject;
|
||||
mo = ls.obj;
|
||||
creatures.insert(ls);
|
||||
}
|
||||
else if(LeveledCreatures *l = it.getCreatureList)
|
||||
|
@ -471,7 +475,7 @@ class CellData
|
|||
ls.m = l.instCreature(playerData.level);
|
||||
if(ls.m != null)
|
||||
{
|
||||
ls.base = base;
|
||||
ls.obj = cellObjC.createObject; mo = ls.obj;
|
||||
creatures.insert(ls);
|
||||
}
|
||||
}
|
||||
|
@ -480,16 +484,10 @@ class CellData
|
|||
// Now that the object has found it's place, load data
|
||||
// into base.
|
||||
|
||||
with(*base)
|
||||
with(*mo)
|
||||
{
|
||||
// ALL variables must be initialized here
|
||||
disabled = false;
|
||||
deleted = false;
|
||||
transient = false;
|
||||
teleport = false;
|
||||
|
||||
// Scale
|
||||
scale = getHNOFloat("XSCL", 1.0);
|
||||
setFloat("scale", getHNOFloat("XSCL", 1.0));
|
||||
|
||||
// Statics only need the position data. Skip the
|
||||
// unneeded calls to isNextSub() as an optimization.
|
||||
|
@ -497,25 +495,28 @@ class CellData
|
|||
|
||||
// An NPC that owns this object (and will get angry if
|
||||
// you steal it)
|
||||
owner = getHNOString("ANAM");
|
||||
setString8("owner", getHNOString("ANAM"));
|
||||
|
||||
// ??? I have no idea, link to a global variable
|
||||
global = getHNOString("BNAM");
|
||||
// I have no idea, link to a global variable perhaps?
|
||||
setString8("global", getHNOString("BNAM"));
|
||||
|
||||
// ID of creature trapped in a soul gem (?)
|
||||
soul = getHNOString("XSOL");
|
||||
setString8("soul", getHNOString("XSOL"));
|
||||
|
||||
// ?? CNAM has a faction name, might be for
|
||||
// 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
|
||||
// item? Sometimes is -1.
|
||||
if(cnam.length) indx = getHNInt("INDX");
|
||||
// INDX might be PC faction rank required to use the
|
||||
// item? Sometimes is -1.
|
||||
if(cnam.length) setInt("indx", getHNInt("INDX"));
|
||||
}
|
||||
|
||||
// Possibly weapon health, number of uses left or
|
||||
// weapon magic charge?
|
||||
xchg = getHNOFloat("XCHG", 0.0);
|
||||
setFloat("xchg", getHNOFloat("XCHG", 0.0));
|
||||
|
||||
// I have no idea, these are present some times, often
|
||||
// along with owner (ANAM) and sometimes otherwise. Is
|
||||
|
@ -523,57 +524,71 @@ class CellData
|
|||
// lights. Perhaps something to do with remaining
|
||||
// light "charge". I haven't tried reading it as a
|
||||
// float in those cases.
|
||||
intv = getHNOInt("INTV", 0);
|
||||
nam9 = getHNOInt("NAM9", 0);
|
||||
setInt("intv", getHNOInt("INTV", 0));
|
||||
setInt("nam9", getHNOInt("NAM9", 0));
|
||||
|
||||
// Present for doors that teleport you to another
|
||||
// cell.
|
||||
if(door && isNextSub("DODT"))
|
||||
{
|
||||
teleport = true;
|
||||
readHExact(&destPos, destPos.sizeof);
|
||||
setBool("teleport", true);
|
||||
|
||||
// Destination cell (optitional?)
|
||||
destCell = getHNOString("DNAM");
|
||||
// Warning, HACK! Will be fixed when we implement
|
||||
// structs in Monster.
|
||||
Placement *p = cast(Placement*)getFloatPtr("destx");
|
||||
readHExact(p, Placement.sizeof);
|
||||
|
||||
// Destination cell (optional?)
|
||||
setString8("destCell", getHNOString("DNAM"));
|
||||
}
|
||||
|
||||
if(door || container)
|
||||
{
|
||||
// Lock level (I think)
|
||||
fltv = getHNOInt("FLTV", 0);
|
||||
setInt("lockLevel", getHNOInt("FLTV", 0));
|
||||
|
||||
// For locked doors and containers
|
||||
key = getHNOString("KNAM");
|
||||
trap = getHNOString("TNAM");
|
||||
setString8("key", getHNOString("KNAM"));
|
||||
setString8("trap", getHNOString("TNAM"));
|
||||
}
|
||||
|
||||
if(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
|
||||
// "Mournhold, Plaza Brindisi Dorom", where it has
|
||||
// the value 100.
|
||||
fltv = getHNOInt("FLTV", 0);
|
||||
setInt("fltv", getHNOInt("FLTV", 0));
|
||||
}
|
||||
|
||||
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
|
||||
// the first door we find.
|
||||
if(door && !playerData.posSet)
|
||||
{
|
||||
playerData.posSet = true;
|
||||
playerData.position = pos;
|
||||
playerData.position = *pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // End of while(hasMoreSubs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of loadReferences()
|
||||
} // End of class CellData
|
||||
|
||||
CellFreelist cellList;
|
||||
|
||||
|
|
|
@ -37,9 +37,6 @@ class SoundException : Exception
|
|||
this(char[] caller, char[] msg) { super(caller ~ " SoundException: " ~ msg); }
|
||||
}
|
||||
|
||||
MusicManager jukebox;
|
||||
MusicManager battleMusic;
|
||||
|
||||
ALCdevice *Device = null;
|
||||
ALCcontext *Context = null;
|
||||
|
||||
|
@ -54,16 +51,12 @@ void initializeSound()
|
|||
|
||||
alcMakeContextCurrent(Context);
|
||||
|
||||
MusicManager.sinit();
|
||||
|
||||
jukebox.initialize("Main");
|
||||
battleMusic.initialize("Battle");
|
||||
Music.init();
|
||||
}
|
||||
|
||||
void shutdownSound()
|
||||
{
|
||||
jukebox.shutdown();
|
||||
battleMusic.shutdown();
|
||||
Music.shutdown();
|
||||
|
||||
alcMakeContextCurrent(null);
|
||||
if(Context) alcDestroyContext(Context);
|
||||
|
@ -72,10 +65,17 @@ void shutdownSound()
|
|||
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();
|
||||
if(what.length) what = " while " ~ what;
|
||||
what = " while " ~ what;
|
||||
if(err != AL_NO_ERROR)
|
||||
throw new Exception(format("OpenAL error%s: (%x) %s", what, err,
|
||||
toString(alGetString(err))));
|
||||
|
|
251
sound/music.d
251
sound/music.d
|
@ -42,30 +42,121 @@ class Idle_waitUntilFinished : IdleFunction
|
|||
|
||||
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 !mgr.isPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
// Simple music player, has a playlist and can pause/resume music.
|
||||
struct MusicManager
|
||||
struct Music
|
||||
{
|
||||
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:
|
||||
|
||||
// Maximum buffer length, divided up among OpenAL buffers
|
||||
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)
|
||||
{
|
||||
throw new SoundException(name ~ " Jukebox", msg);
|
||||
throw new SoundException(getName() ~ " Jukebox", msg);
|
||||
}
|
||||
|
||||
ALuint sID; // Sound id
|
||||
ALuint bIDs[4]; // Buffers
|
||||
ALuint bIDs[numBufs]; // Buffers
|
||||
|
||||
ALenum bufFormat;
|
||||
ALint bufRate;
|
||||
|
@ -75,82 +166,61 @@ struct MusicManager
|
|||
|
||||
static ubyte[] outData;
|
||||
|
||||
// The Jukebox class
|
||||
static MonsterClass mc;
|
||||
static this()
|
||||
{
|
||||
outData.length = bufLength / numBufs;
|
||||
}
|
||||
|
||||
// The jukebox Monster object
|
||||
MonsterObject *mo;
|
||||
|
||||
public:
|
||||
|
||||
static MusicManager *get()
|
||||
{ return cast(MusicManager*)params.obj().extra; }
|
||||
|
||||
static void sinit()
|
||||
this(MonsterObject *m)
|
||||
{
|
||||
assert(mc is null);
|
||||
mc = new MonsterClass("Jukebox", "jukebox.mn");
|
||||
mc.bind("waitUntilFinished",
|
||||
new Idle_waitUntilFinished);
|
||||
mo = m;
|
||||
m.extra = cast(void*)this;
|
||||
|
||||
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;
|
||||
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;
|
||||
audioHandle = null;
|
||||
|
||||
mo = mc.createObject();
|
||||
mo.extra = this;
|
||||
}
|
||||
if(sID)
|
||||
{
|
||||
alSourceStop(sID);
|
||||
alDeleteSources(1, &sID);
|
||||
checkALError("disabling music");
|
||||
sID = 0;
|
||||
|
||||
// Called whenever the volume configuration values are changed by
|
||||
// the user.
|
||||
void updateVolume()
|
||||
{
|
||||
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");
|
||||
alDeleteBuffers(bIDs.length, bIDs.ptr);
|
||||
checkALError("deleting buffers");
|
||||
bIDs[] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void setSound()
|
||||
|
@ -158,7 +228,7 @@ struct MusicManager
|
|||
char[] fname = stack.popString8();
|
||||
|
||||
// Generate a source to play back with if needed
|
||||
if(!sID)
|
||||
if(sID == 0)
|
||||
{
|
||||
alGenSources(1, &sID);
|
||||
checkALError("generating buffers");
|
||||
|
@ -167,17 +237,17 @@ struct MusicManager
|
|||
alSourcei(sID, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
|
||||
alGenBuffers(bIDs.length, bIDs.ptr);
|
||||
|
||||
updateVolume();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Kill current track, but keep the sID source.
|
||||
alSourceStop(sID);
|
||||
checkALError("stopping current track");
|
||||
|
||||
alSourcei(sID, AL_BUFFER, 0);
|
||||
//alDeleteBuffers(bIDs.length, bIDs.ptr);
|
||||
//bIDs[] = 0;
|
||||
checkALError("killing current track");
|
||||
checkALError("resetting buffer");
|
||||
}
|
||||
|
||||
if(fileHandle) avc_closeAVFile(fileHandle);
|
||||
|
@ -230,7 +300,8 @@ struct MusicManager
|
|||
}
|
||||
|
||||
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
|
||||
foreach(int i, ref b; bIDs)
|
||||
|
@ -257,14 +328,15 @@ struct MusicManager
|
|||
{
|
||||
float volume = stack.popFloat();
|
||||
|
||||
volume = saneVol(volume);
|
||||
|
||||
// Set the new volume
|
||||
if(sID) alSourcef(sID, AL_GAIN, volume);
|
||||
}
|
||||
|
||||
void playSound()
|
||||
{
|
||||
if(!sID || !config.useMusic)
|
||||
return;
|
||||
if(!sID) return;
|
||||
|
||||
alSourcePlay(sID);
|
||||
checkALError("starting music");
|
||||
|
@ -280,6 +352,7 @@ struct MusicManager
|
|||
{
|
||||
ALint state;
|
||||
alGetSourcei(sID, AL_SOURCE_STATE, &state);
|
||||
checkALError("getting status");
|
||||
|
||||
return state == AL_PLAYING;
|
||||
}
|
||||
|
@ -293,7 +366,7 @@ struct MusicManager
|
|||
ALint count;
|
||||
alGetSourcei(sID, AL_BUFFERS_PROCESSED, &count);
|
||||
|
||||
checkALError();
|
||||
checkALError("getting number of unprocessed buffers");
|
||||
|
||||
for(int i = 0;i < count;i++)
|
||||
{
|
||||
|
@ -307,30 +380,8 @@ struct MusicManager
|
|||
{
|
||||
alBufferData(bid, bufFormat, outData.ptr, length, bufRate);
|
||||
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:
|
||||
|
||||
|
||||
alias Node* Iterator;
|
||||
|
||||
// Equivalent to calling remove() on all elements
|
||||
|
|
Loading…
Reference in a new issue