- 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
pull/7/head
nkorslund 16 years ago
parent e5fc473731
commit f194b91b60

@ -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;
}

@ -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

@ -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; }

@ -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);
}

@ -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;

@ -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;

@ -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;
}

@ -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();

@ -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);
// Warning, HACK! Will be fixed when we implement
// structs in Monster.
Placement *p = cast(Placement*)getFloatPtr("destx");
readHExact(p, Placement.sizeof);
// Destination cell (optitional?)
destCell = getHNOString("DNAM");
// 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))));

@ -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);
mc.bind("setSound", { get().setSound(); });
mc.bind("setVolume", { get().setVolume(); });
mc.bind("playSound", { get().playSound(); });
mc.bind("stopSound", { get().stopSound(); });
mo = m;
m.extra = cast(void*)this;
outData.length = bufLength / bIDs.length;
}
// Initialize the jukebox
void initialize(char[] name)
{
this.name = name;
sID = 0;
bIDs[] = 0;
fileHandle = null;
mo = mc.createObject();
mo.extra = this;
}
// Called whenever the volume configuration values are changed by
// the user.
void updateVolume()
{
stack.pushFloat(config.calcMusicVolume());
mo.call("updateVolume");
}
static Jukebox get(MonsterObject *m)
{
auto j = cast(Jukebox)m.extra;
assert(j !is null);
assert(j.mo == m);
return j;
}
// Give a music play list
void setPlaylist(char[][] pl)
{
AIndex arr[];
arr.length = pl.length;
static Jukebox get(ref MonsterObject m)
{ return get(&m); }
// Create the array indices for each element string
foreach(i, ref elm; arr)
elm = arrays.create(pl[i]).getIndex();
static Jukebox get()
{ return get(params.obj()); }
// Push the final array
stack.pushArray(arr);
private:
mo.call("setPlaylist");
}
// Disable music
void shutdown()
{
mo.call("stop");
// Pause current track
void pause() { mo.call("pause"); }
if(fileHandle) avc_closeAVFile(fileHandle);
fileHandle = null;
audioHandle = null;
// Resume. Starts playing sound, with fade in
void resume()
{
if(!config.useMusic) return;
mo.call("resume");
}
if(sID)
{
alSourceStop(sID);
alDeleteSources(1, &sID);
checkALError("disabling music");
sID = 0;
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…
Cancel
Save