diff --git a/engine/src/main/java/org/destinationsol/SolApplication.java b/engine/src/main/java/org/destinationsol/SolApplication.java index 58830fc60..267c06eb2 100644 --- a/engine/src/main/java/org/destinationsol/SolApplication.java +++ b/engine/src/main/java/org/destinationsol/SolApplication.java @@ -41,6 +41,8 @@ import org.destinationsol.health.components.Health; import org.destinationsol.location.components.Angle; import org.destinationsol.location.components.Velocity; +import org.destinationsol.material.MaterialType; +import org.destinationsol.material.components.Material; import org.destinationsol.moneyDropping.components.DropsMoneyOnDestruction; import org.destinationsol.rendering.RenderableElement; import org.destinationsol.rendering.components.Renderable; @@ -272,7 +274,7 @@ private void draw() { if (!entityCreated) { Size size = new Size(); - size.size = 2; + size.size = 1; RenderableElement element = new RenderableElement(); element.texture = SolRandom.randomElement(Assets.listTexturesMatching("engine:asteroid_.*")); @@ -286,13 +288,16 @@ private void draw() { Position position = new Position(); position.position = solGame.getHero().getShip().getPosition().cpy(); - position.position.y += 3; + position.position.y += 1; Health health = new Health(); health.currentHealth = 1; + Material material = new Material(); + material.materialType = MaterialType.ROCK; EntityRef entityRef = entitySystemManager.getEntityManager().createEntity(graphicsComponent, position, size, - new Angle(), new Velocity(), new AsteroidMesh(), health, new DropsMoneyOnDestruction(), new CreatesRubbleOnDestruction()); + new Angle(), new Velocity(), new AsteroidMesh(), health, new DropsMoneyOnDestruction(), + new CreatesRubbleOnDestruction(), material); entityRef.setComponent(new BodyLinked()); entityCreated = true; diff --git a/engine/src/main/java/org/destinationsol/assets/sound/OggSoundManager.java b/engine/src/main/java/org/destinationsol/assets/sound/OggSoundManager.java index b5b1482b4..054568060 100644 --- a/engine/src/main/java/org/destinationsol/assets/sound/OggSoundManager.java +++ b/engine/src/main/java/org/destinationsol/assets/sound/OggSoundManager.java @@ -20,6 +20,7 @@ import org.destinationsol.Const; import org.destinationsol.SolApplication; import org.destinationsol.assets.Assets; +import org.destinationsol.common.NotNull; import org.destinationsol.common.Nullable; import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; @@ -33,6 +34,7 @@ import org.destinationsol.game.context.Context; import org.destinationsol.game.planet.Planet; import org.destinationsol.game.sound.DebugHintDrawer; +import org.terasology.gestalt.entitysystem.entity.EntityRef; import java.util.HashMap; import java.util.Map; @@ -69,7 +71,14 @@ public class OggSoundManager implements UpdateAwareSystem { * {@code SolObject} is the object the sound belongs to, inner map's {@code OggSound} is the sound in question, * {@code Float} is an absolute time the sound will stop playing. (Absolute as in not relative to the current time) */ - private final Map> loopedSoundMap; + private final Map> loopedSoundMapOfSolObjects; + /** + * A container for working with looping sounds. Looped sounds are stored here per-entity, and this map is every so often + * cleared, on the basis provided by calling each entity's {@link EntityRef#exists()} method. + * {@code EntityRef} is the object the sound belongs to, inner map's {@code OggSound} is the sound in question, + * {@code Float} is an absolute time the sound will stop playing. (Absolute as in not relative to the current time) + */ + private final Map> loopedSoundMapOfEntities; /** * Used for drawing debug hints when {@link DebugOptions#SOUND_INFO} flag is set. See * {@link #drawDebug(GameDrawer, SolCam)} for more info. @@ -90,7 +99,8 @@ public class OggSoundManager implements UpdateAwareSystem { public OggSoundManager(Context context) { soundMap = new HashMap<>(); - loopedSoundMap = new HashMap<>(); + loopedSoundMapOfSolObjects = new HashMap<>(); + loopedSoundMapOfEntities = new HashMap<>(); debugHintDrawer = new DebugHintDrawer(); solApplication = context.get(SolApplication.class); this.solCam = context.get(SolCam.class); @@ -192,6 +202,63 @@ public void play(SolGame game, PlayableSound playableSound, @Nullable Vector2 po gdxSound.play(volume, pitch, 0); } + /** + * Plays a sound at a particular position. If the sound has an associated loop, this will loop the sound, coming + * from the entity. + *

+ * {@code source} must not be null if the sound is specified to loop, and at least one of {@code source} or + * {@code position} must be specified. + * + * @param game Game to play the sound in + * @param playableSound The sound to play + * @param position Position to play the sound at + * @param soundSource Bearer of a sound. Must not be null for looped sounds. + */ + public void play(SolGame game, PlayableSound playableSound, @NotNull Vector2 position, @NotNull EntityRef soundSource) { + play(game, playableSound, position, soundSource, 1f); + } + + /** + * Plays a sound at a particular position. If the sound has an associated loop, this will loop the sound, coming + * from the entity. + * + * @param game Game to play the sound in + * @param playableSound The sound to play + * @param position Position to play the sound at + * @param soundSource Bearer of a sound. Must not be null for looped sounds. + * @param volumeMultiplier Multiplier for sound volume + */ + public void play(SolGame game, PlayableSound playableSound, @NotNull Vector2 position, @NotNull EntityRef soundSource, float volumeMultiplier) { + + if (playableSound == null) { + return; + } + if (soundSource == null || position == null) { + throw new AssertionError("Position and source must be non-null"); + } + + OggSound sound = playableSound.getOggSound(); + + float volume = getVolume(game, position, volumeMultiplier, sound); + if (volume <= 0) { + return; + } + + // Calculate the pitch for the sound + float pitch = SolRandom.randomFloat(.97f, 1.03f) * game.getTimeFactor() * playableSound.getBasePitch(); + + if (skipLooped(soundSource, sound, game.getTime())) { + return; + } + + if (DebugOptions.SOUND_INFO) { + debugHintDrawer.add(soundSource, position, sound.toString()); + } + + Sound gdxSound = sound.getSound(); + gdxSound.play(volume, pitch, 0); + } + /** * Calculates the volume a sound should be played at. * This method takes several factors in account, more exactly: global game's volume, spreading of sound in vacuum @@ -246,10 +313,43 @@ private boolean skipLooped(SolObject source, OggSound sound, float time) { return false; } - Map looped = loopedSoundMap.get(source); + Map looped = loopedSoundMapOfSolObjects.get(source); + if (looped == null) { + looped = new HashMap<>(); + loopedSoundMapOfSolObjects.put(source, looped); + return false; + } else { + Float endTime = looped.get(sound); + if (endTime == null || endTime <= time) { + looped.put(sound, time + sound.getLoopTime()); // argh, performance loss + return false; + } else { + return true; + } + } + } + + /** + * Returns true when sound should not be played because of loop, false otherwise. + *

+ * Sound should not be played when its {@code loopTime > 0} and {@code loopTime} time units have not yet passed + * since it was last played on the object. + * TODO: now handles even adding the sound to the list of looping sounds. Possibly extract that? + * + * @param source Object playing this sound. + * @param sound Sound to be played. + * @param time Game's current time. + * @return true when sound should not be played because of loop, false otherwise. + */ + private boolean skipLooped(EntityRef source, OggSound sound, float time) { + if (sound.getLoopTime() == 0) { + return false; + } + + Map looped = loopedSoundMapOfEntities.get(source); if (looped == null) { looped = new HashMap<>(); - loopedSoundMap.put(source, looped); + loopedSoundMapOfEntities.put(source, looped); return false; } else { Float endTime = looped.get(sound); @@ -293,14 +393,16 @@ public void update(SolGame game, float timeStep) { } /** - * Iterates {@link #loopedSoundMap} and removes any entries that are no longer in the game. + * Iterates {@link #loopedSoundMapOfSolObjects} and {@link #loopedSoundMapOfEntities} and removes any entries that + * are no longer in the game. *

- * (See {@link SolObject#shouldBeRemoved(SolGame)}) + * (See {@link SolObject#shouldBeRemoved(SolGame)} and {@link EntityRef#exists()}) * * @param game Game currently in progress. */ private void cleanLooped(SolGame game) { - loopedSoundMap.keySet().removeIf(o -> o.shouldBeRemoved(game)); + loopedSoundMapOfSolObjects.keySet().removeIf(o -> o.shouldBeRemoved(game)); + loopedSoundMapOfEntities.keySet().removeIf(entity -> !entity.exists()); } /** diff --git a/engine/src/main/java/org/destinationsol/assets/sound/SpecialSounds.java b/engine/src/main/java/org/destinationsol/assets/sound/SpecialSounds.java index c9638d5d1..f06ef5204 100644 --- a/engine/src/main/java/org/destinationsol/assets/sound/SpecialSounds.java +++ b/engine/src/main/java/org/destinationsol/assets/sound/SpecialSounds.java @@ -20,6 +20,7 @@ import org.destinationsol.game.DmgType; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; +import org.destinationsol.material.MaterialType; import java.util.Arrays; @@ -106,4 +107,48 @@ public void playColl(SolGame game, float absImpulse, SolObject o, Vector2 positi } game.getSoundManager().play(game, metal ? metalColl : rockColl, position, o, absImpulse * Const.IMPULSE_TO_COLL_VOL); } + + /** + * Gets the damage sound associated with the given {@link MaterialType} and {@link DmgType}. If no sound is defined, + * null is returned. + * + * @param materialType the material type of the damaged entity + * @param damageType the type of damage done + * @return the sound of the damage + */ + public PlayableSound getHitSound(MaterialType materialType, DmgType damageType) { + if (damageType == DmgType.ENERGY) { + if (materialType == MaterialType.METAL) { + return metalEnergyHit; + } + if (materialType == MaterialType.ROCK) { + return rockEnergyHit; + } + } + if (damageType == DmgType.BULLET) { + if (materialType == MaterialType.METAL) { + return metalBulletHit; + } + if (materialType == MaterialType.ROCK) { + return rockBulletHit; + } + } + return null; + } + + /** + * Gets the collision sound associated with the given {@link MaterialType}. If no sound is defined, null is returned. + * + * @param materialType the material type of the entity + * @return the sound of the collision + */ + public PlayableSound getCollisionSound(MaterialType materialType) { + if (materialType == MaterialType.METAL) { + return metalColl; + } + if (materialType == MaterialType.ROCK) { + return rockColl; + } + return null; + } } diff --git a/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidSoundSystem.java b/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidSoundSystem.java new file mode 100644 index 000000000..024afe393 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/asteroids/systems/AsteroidSoundSystem.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.destinationsol.asteroids.systems; + +import org.destinationsol.assets.sound.SpecialSounds; +import org.destinationsol.asteroids.components.AsteroidMesh; +import org.destinationsol.common.In; +import org.destinationsol.common.SolMath; +import org.destinationsol.entitysystem.EntitySystemManager; +import org.destinationsol.entitysystem.EventReceiver; +import org.destinationsol.location.components.Position; +import org.destinationsol.removal.events.DeletionEvent; +import org.destinationsol.removal.systems.DestructionSystem; +import org.destinationsol.size.components.Size; +import org.destinationsol.sound.events.SoundEvent; +import org.terasology.gestalt.entitysystem.entity.EntityRef; +import org.terasology.gestalt.entitysystem.event.Before; +import org.terasology.gestalt.entitysystem.event.EventResult; +import org.terasology.gestalt.entitysystem.event.ReceiveEvent; + +/** + * This system plays asteroid-specific sounds. + */ +public class AsteroidSoundSystem implements EventReceiver { + + @In + private EntitySystemManager entitySystemManager; + + @In + private SpecialSounds specialSounds; + + /** + * When an asteroid is destroyed, this plays the asteroid destruction sound. + */ + @ReceiveEvent(components = {AsteroidMesh.class, Position.class}) + @Before(DestructionSystem.class) + public EventResult playDeathSound(DeletionEvent event, EntityRef entity) { + float volumeMultiplier = 1; + if (entity.hasComponent(Size.class)) { + float size = entity.getComponent(Size.class).get().size; + volumeMultiplier = SolMath.clamp(size / .5f); + } + entitySystemManager.sendEvent(new SoundEvent(specialSounds.asteroidCrack, volumeMultiplier), entity); + return EventResult.CONTINUE; + } +} diff --git a/engine/src/main/java/org/destinationsol/game/SolGame.java b/engine/src/main/java/org/destinationsol/game/SolGame.java index 5e9c93b03..d2063b988 100644 --- a/engine/src/main/java/org/destinationsol/game/SolGame.java +++ b/engine/src/main/java/org/destinationsol/game/SolGame.java @@ -126,6 +126,7 @@ public SolGame(String shipName, boolean isTutorial, boolean isNewGame, CommonDra //TODO this no longer needs to be instantiated in SolGame soundManager = solApplication.getSoundManager(); + context.put(OggSoundManager.class, soundManager); SpecialSounds specialSounds = new SpecialSounds(soundManager); context.put(SpecialSounds.class, specialSounds); diff --git a/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java b/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java index d855798a5..185563203 100644 --- a/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java +++ b/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java @@ -132,12 +132,12 @@ public void update(SolGame game) { while (iterator.next()) { Vector2 entityPosition = iterator.getEntity().getComponent(Position.class).get().position; if (getPosition().dst2(entityPosition) <= config.aoeRadius) { - game.getEntitySystemManager().sendEvent(new DamageEvent(config.dmg), entity); + game.getEntitySystemManager().sendEvent(new DamageEvent(config.dmg, config.dmgType), entity); } } } else { - game.getEntitySystemManager().sendEvent(new DamageEvent(config.dmg), entity); + game.getEntitySystemManager().sendEvent(new DamageEvent(config.dmg, config.dmgType), entity); } } diff --git a/engine/src/main/java/org/destinationsol/game/sound/DebugHint.java b/engine/src/main/java/org/destinationsol/game/sound/DebugHint.java index 11aa15896..1a9b75b84 100644 --- a/engine/src/main/java/org/destinationsol/game/sound/DebugHint.java +++ b/engine/src/main/java/org/destinationsol/game/sound/DebugHint.java @@ -22,6 +22,8 @@ import org.destinationsol.game.SolCam; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; +import org.destinationsol.location.components.Position; +import org.terasology.gestalt.entitysystem.entity.EntityRef; import java.util.HashMap; import java.util.Iterator; @@ -35,8 +37,11 @@ public class DebugHint { private SolObject myOwner; private String myMsg; - public DebugHint(SolObject owner, Vector2 position) { + private EntityRef entity; + + public DebugHint(SolObject owner, EntityRef entity, Vector2 position) { myOwner = owner; + this.entity = entity; this.position = new Vector2(position); myMsgs = new HashMap<>(); } @@ -66,6 +71,16 @@ public void update(SolGame game) { } } + if (entity != null) { + if (!entity.exists()) { + entity = null; + } else { + entity.getComponent(Position.class).ifPresent(entityPosition -> { + position.set(entityPosition.position); + }); + } + } + long now = TimeUtils.millis(); boolean needsRebuild = false; Iterator> it = myMsgs.entrySet().iterator(); diff --git a/engine/src/main/java/org/destinationsol/game/sound/DebugHintDrawer.java b/engine/src/main/java/org/destinationsol/game/sound/DebugHintDrawer.java index 0b4d2703c..8521f8801 100644 --- a/engine/src/main/java/org/destinationsol/game/sound/DebugHintDrawer.java +++ b/engine/src/main/java/org/destinationsol/game/sound/DebugHintDrawer.java @@ -21,33 +21,43 @@ import org.destinationsol.game.SolCam; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; +import org.terasology.gestalt.entitysystem.entity.EntityRef; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class DebugHintDrawer { - private final Map myTracedNotes; - private final Map myFreeNotes; + private final Map tracedSolObjectNotes; + private final Map freeNotes; + private final Map tracedEntityNotes; public DebugHintDrawer() { - myTracedNotes = new HashMap<>(); - myFreeNotes = new HashMap<>(); + tracedSolObjectNotes = new HashMap<>(); + freeNotes = new HashMap<>(); + tracedEntityNotes = new HashMap<>(); } public void add(@Nullable SolObject owner, Vector2 position, String value) { DebugHint dh; if (owner == null) { - dh = myFreeNotes.computeIfAbsent(position, p -> new DebugHint(null, p)); + dh = freeNotes.computeIfAbsent(position, p -> new DebugHint(null, null, p)); } else { - dh = myTracedNotes.computeIfAbsent(owner, o -> new DebugHint(o, o.getPosition())); + dh = tracedSolObjectNotes.computeIfAbsent(owner, o -> new DebugHint(o, null, o.getPosition())); } dh.add(value); } + public void add(EntityRef entity, Vector2 position, String value) { + DebugHint debugHint; + debugHint = tracedEntityNotes.computeIfAbsent(entity, entityRef -> new DebugHint(null, entityRef, position)); + debugHint.add(value); + } + public void update(SolGame game) { - updateEach(game, myTracedNotes.values().iterator()); - updateEach(game, myFreeNotes.values().iterator()); + updateEach(game, tracedSolObjectNotes.values().iterator()); + updateEach(game, freeNotes.values().iterator()); + updateEach(game, tracedEntityNotes.values().iterator()); } private void updateEach(SolGame game, Iterator it) { @@ -61,10 +71,13 @@ private void updateEach(SolGame game, Iterator it) { } public void draw(GameDrawer drawer, SolCam solCam) { - for (DebugHint n : myTracedNotes.values()) { + for (DebugHint n : tracedSolObjectNotes.values()) { + n.draw(drawer, solCam); + } + for (DebugHint n : freeNotes.values()) { n.draw(drawer, solCam); } - for (DebugHint n : myFreeNotes.values()) { + for (DebugHint n : tracedEntityNotes.values()) { n.draw(drawer, solCam); } } diff --git a/engine/src/main/java/org/destinationsol/game/sound/OggSoundManager.java b/engine/src/main/java/org/destinationsol/game/sound/OggSoundManager.java deleted file mode 100644 index 2034c0d1a..000000000 --- a/engine/src/main/java/org/destinationsol/game/sound/OggSoundManager.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2016 MovingBlocks - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.destinationsol.game.sound; - -import com.badlogic.gdx.audio.Sound; -import com.badlogic.gdx.math.Vector2; -import org.destinationsol.Const; -import org.destinationsol.assets.Assets; -import org.destinationsol.assets.sound.OggSound; -import org.destinationsol.assets.sound.PlayableSound; -import org.destinationsol.common.Nullable; -import org.destinationsol.common.SolMath; -import org.destinationsol.common.SolRandom; -import org.destinationsol.game.DebugOptions; -import org.destinationsol.game.GameDrawer; -import org.destinationsol.game.Hero; -import org.destinationsol.game.SolCam; -import org.destinationsol.game.SolGame; -import org.destinationsol.game.SolObject; -import org.destinationsol.game.planet.Planet; - -import java.util.HashMap; -import java.util.Map; - -public class OggSoundManager { - // private static Logger logger = LoggerFactory.getLogger(OggSoundManager.class); - private final Map soundMap; - private final Map> loopedSoundMap; - private final DebugHintDrawer debugHintDrawer; - - private float myLoopAwait; - - public OggSoundManager() { - this.soundMap = new HashMap<>(); - this.loopedSoundMap = new HashMap<>(); - this.debugHintDrawer = new DebugHintDrawer(); - } - - public OggSound getSound(String path) { - return getSound(path, 1.0f); - } - - public OggSound getSound(String path, float basePitch) { - if (soundMap.containsKey(path)) { - return soundMap.get(path); - } - - OggSound sound = Assets.getSound(path); - sound.setBasePitch(basePitch); - soundMap.put(path, sound); - return sound; - } - - /** - * Plays a sound. Source must not be null. - * - * @param position position of a sound. If null, source.getPosition() will be used - * @param source bearer of a sound. Must not be null for looped sounds - * @param volumeMultiplier multiplier for sound volume - */ - public void play(SolGame game, PlayableSound playableSound, @Nullable Vector2 position, @Nullable SolObject source, float volumeMultiplier) { - if (playableSound == null) { - return; - } - - OggSound sound = playableSound.getOggSound(); - // logger.debug("Playing sound: {}", sound.getUrn().toString()); - - // Perform some initial argument validation - if (source == null && position == null) { - throw new AssertionError("Either position or source must be non-null"); - } - if (source == null && sound.getLoopTime() > 0) { - throw new AssertionError("Attempted to loop a sound without a parent object: " + sound.getUrn()); - } - if (position == null) { - position = source.getPosition(); - } - - // Calculate the volume multiplier for the sound - float globalVolumeMultiplier = game.getSolApplication().getOptions().sfxVolume.getVolume(); - if (globalVolumeMultiplier == 0) { - return; - } - - Vector2 cameraPosition = game.getCam().getPosition(); - Planet nearestPlanet = game.getPlanetManager().getNearestPlanet(); - - float airPercentage = 0; - if (nearestPlanet.getConfig().skyConfig != null) { - float distanceToAtmosphere = cameraPosition.dst(nearestPlanet.getPosition()) - nearestPlanet.getGroundHeight() - Const.ATM_HEIGHT / 2; - airPercentage = SolMath.clamp(1 - distanceToAtmosphere / (Const.ATM_HEIGHT / 2)); - } - if (DebugOptions.SOUND_IN_SPACE) { - airPercentage = 1; - } - - float maxSoundDist = 1 + 1.5f * Const.CAM_VIEW_DIST_GROUND * airPercentage; - - Hero hero = game.getHero(); - float soundRadius = hero.isTranscendent() ? 0 : hero.getHull().config.getApproxRadius(); - float distance = position.dst(cameraPosition) - soundRadius; - float distanceMultiplier = SolMath.clamp(1 - distance / maxSoundDist); - - float volume = sound.getBaseVolume() * volumeMultiplier * distanceMultiplier * globalVolumeMultiplier; - - if (volume <= 0) { - return; - } - - // Calculate the pitch for the sound - float pitch = SolRandom.randomFloat(.97f, 1.03f) * game.getTimeFactor() * playableSound.getBasePitch(); - - if (skipLooped(source, sound, game.getTime())) { - return; - } - - if (DebugOptions.SOUND_INFO) { - debugHintDrawer.add(source, position, sound.toString()); - } - - Sound gdxSound = sound.getSound(); - gdxSound.play(volume, pitch, 0); - } - - /** - * Plays a sound. Source must not be null. - * - * @param position position of a sound. If null, source.getPosition() will be used - * @param source bearer of a sound. Must not be null for looped sounds - */ - public void play(SolGame game, PlayableSound sound, @Nullable Vector2 position, @Nullable SolObject source) { - this.play(game, sound, position, source, 1f); - } - - private boolean skipLooped(SolObject source, OggSound sound, float time) { - if (sound.getLoopTime() == 0) { - return false; - } - - boolean playing; - Map looped = loopedSoundMap.get(source); - if (looped == null) { - looped = new HashMap<>(); - loopedSoundMap.put(source, looped); - playing = false; - } else { - Float endTime = looped.get(sound); - if (endTime == null || endTime <= time) { - looped.put(sound, time + sound.getLoopTime()); // argh, performance loss - playing = false; - } else { - playing = time < endTime; - } - } - return playing; - } - - public void drawDebug(GameDrawer drawer, SolCam solCam) { - if (DebugOptions.SOUND_INFO) { - debugHintDrawer.draw(drawer, solCam); - } - } - - public void update(SolGame game) { - if (DebugOptions.SOUND_INFO) { - debugHintDrawer.update(game); - } - - myLoopAwait -= game.getTimeStep(); - if (myLoopAwait <= 0) { - myLoopAwait = 30; - cleanLooped(game); - } - } - - private void cleanLooped(SolGame game) { - loopedSoundMap.keySet().removeIf(o -> o.shouldBeRemoved(game)); - } - - public void dispose() { - for (OggSound sound : soundMap.values()) { - sound.doDispose(); - } - } -} diff --git a/engine/src/main/java/org/destinationsol/game/sound/OggSoundSet.java b/engine/src/main/java/org/destinationsol/game/sound/OggSoundSet.java deleted file mode 100644 index 93be81378..000000000 --- a/engine/src/main/java/org/destinationsol/game/sound/OggSoundSet.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2016 MovingBlocks - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.destinationsol.game.sound; - -import org.destinationsol.assets.sound.OggSound; -import org.destinationsol.assets.sound.PlayableSound; -import org.destinationsol.common.SolRandom; - -import java.util.List; - -/** - * Represents a set of random OggSound urns with a single basePitch assigned to every of them. - *

- * This is an alternative to sounds being randomly fetched from a specified folder - - * a workflow that isn't viable with gestalt. - */ -public class OggSoundSet implements PlayableSound { - private final OggSoundManager oggSoundManager; - private final List urnList; - private final float basePitch; - - public OggSoundSet(OggSoundManager oggSoundManager, List urnList, float basePitch) { - this.oggSoundManager = oggSoundManager; - this.urnList = urnList; - this.basePitch = basePitch; - } - - public OggSoundSet(OggSoundManager oggSoundManager, List urnList) { - this(oggSoundManager, urnList, 1.0f); - } - - @Override - public OggSound getOggSound() { - return oggSoundManager.getSound(SolRandom.randomElement(urnList)); - } - - @Override - public float getBasePitch() { - return basePitch; - } -} \ No newline at end of file diff --git a/engine/src/main/java/org/destinationsol/game/sound/SpecialSounds.java b/engine/src/main/java/org/destinationsol/game/sound/SpecialSounds.java deleted file mode 100644 index ef2aa2cfd..000000000 --- a/engine/src/main/java/org/destinationsol/game/sound/SpecialSounds.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2017 MovingBlocks - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.destinationsol.game.sound; - -import com.badlogic.gdx.math.Vector2; -import org.destinationsol.Const; -import org.destinationsol.assets.sound.PlayableSound; -import org.destinationsol.game.DmgType; -import org.destinationsol.game.SolGame; -import org.destinationsol.game.SolObject; - -import java.util.Arrays; - -public class SpecialSounds { - - public final PlayableSound metalColl; - public final PlayableSound metalEnergyHit; - public final PlayableSound rockColl; - public final PlayableSound rockEnergyHit; - public final PlayableSound asteroidCrack; - public final PlayableSound shipExplosion; - public final PlayableSound forceBeaconWork; - public final PlayableSound doorMove; - public final PlayableSound abilityRecharged; - public final PlayableSound abilityRefused; - public final PlayableSound controlDisabled; - public final PlayableSound controlEnabled; - public final PlayableSound lootThrow; - public final PlayableSound transcendentCreated; - public final PlayableSound transcendentFinished; - - public final PlayableSound metalBulletHit; - public final PlayableSound rockBulletHit; - public final PlayableSound burning; - public final PlayableSound transcendentMove; - - public SpecialSounds(OggSoundManager soundManager) { - // OggSound - metalColl = soundManager.getSound("core:metalCollision"); - metalEnergyHit = soundManager.getSound("core:empty"); - rockColl = soundManager.getSound("core:rockCollision"); - rockEnergyHit = soundManager.getSound("core:empty"); - asteroidCrack = soundManager.getSound("core:asteroidCrack"); - shipExplosion = soundManager.getSound("core:shipExplosion"); - forceBeaconWork = soundManager.getSound("core:forceBeaconWork"); - doorMove = soundManager.getSound("core:controlEnabled"); - abilityRecharged = soundManager.getSound("core:abilityRecharged"); - abilityRefused = soundManager.getSound("core:abilityRefused"); - controlDisabled = soundManager.getSound("core:controlDisabled"); - controlEnabled = soundManager.getSound("core:controlEnabled"); - lootThrow = soundManager.getSound("core:rocketLauncherShoot"); - transcendentCreated = soundManager.getSound("core:teleport"); - transcendentFinished = soundManager.getSound("core:teleport"); - - // OggSoundSet - metalBulletHit = new OggSoundSet(soundManager, Arrays.asList("core:metalBulletHit0", "core:metalBulletHit1", "core:metalBulletHit2"), 1.1f); - rockBulletHit = new OggSoundSet(soundManager, Arrays.asList("core:rockBulletHit0", "core:rockBulletHit1")); - burning = new OggSoundSet(soundManager, Arrays.asList("core:burning2", "core:burning3", "core:burning4")); - transcendentMove = new OggSoundSet(soundManager, Arrays.asList("core:transcendentMove", "core:transcendentMove2", "core:transcendentMove3", "core:transcendentMove4")); - } - - public PlayableSound hitSound(boolean forMetal, DmgType dmgType) { - if (dmgType == DmgType.ENERGY) { - return forMetal ? metalEnergyHit : rockEnergyHit; - } - if (dmgType == DmgType.BULLET) { - return forMetal ? metalBulletHit : rockBulletHit; - } - return null; - } - - public void playHit(SolGame game, SolObject o, Vector2 position, DmgType dmgType) { - if (o == null) { - return; - } - Boolean metal = o.isMetal(); - if (metal == null) { - return; - } - PlayableSound sound = hitSound(metal, dmgType); - if (sound == null) { - return; - } - game.getSoundManager().play(game, sound, position, o); - } - - public void playColl(SolGame game, float absImpulse, SolObject o, Vector2 position) { - if (o == null || absImpulse < .1f) { - return; - } - Boolean metal = o.isMetal(); - if (metal == null) { - return; - } - game.getSoundManager().play(game, metal ? metalColl : rockColl, position, o, absImpulse * Const.IMPULSE_TO_COLL_VOL); - } -} diff --git a/engine/src/main/java/org/destinationsol/health/events/DamageEvent.java b/engine/src/main/java/org/destinationsol/health/events/DamageEvent.java index c46719829..ae5ed57ae 100644 --- a/engine/src/main/java/org/destinationsol/health/events/DamageEvent.java +++ b/engine/src/main/java/org/destinationsol/health/events/DamageEvent.java @@ -15,6 +15,7 @@ */ package org.destinationsol.health.events; +import org.destinationsol.game.DmgType; import org.terasology.gestalt.entitysystem.event.Event; /** @@ -23,12 +24,22 @@ public class DamageEvent implements Event { private float damage; + private DmgType damageType; public DamageEvent(float damage) { this.damage = damage; } + public DamageEvent(float damage, DmgType damageType) { + this.damage = damage; + this.damageType = damageType; + } + public float getDamage() { return damage; } + + public DmgType getDamageType() { + return damageType; + } } diff --git a/engine/src/main/java/org/destinationsol/material/MaterialType.java b/engine/src/main/java/org/destinationsol/material/MaterialType.java new file mode 100644 index 000000000..adae0b62b --- /dev/null +++ b/engine/src/main/java/org/destinationsol/material/MaterialType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.destinationsol.material; + +/** + * The types of materials that an entity can be composed of. + */ +public enum MaterialType { + METAL, ROCK +} diff --git a/engine/src/main/java/org/destinationsol/material/components/Material.java b/engine/src/main/java/org/destinationsol/material/components/Material.java new file mode 100644 index 000000000..61f664ab3 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/material/components/Material.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.destinationsol.material.components; + +import org.destinationsol.material.MaterialType; +import org.terasology.gestalt.entitysystem.component.Component; + +/** + * Indicates what type of material the entity is made of. + */ +public class Material implements Component { + + public MaterialType materialType; + + @Override + public void copy(Material other) { + this.materialType = other.materialType; + } +} diff --git a/engine/src/main/java/org/destinationsol/sound/events/SoundEvent.java b/engine/src/main/java/org/destinationsol/sound/events/SoundEvent.java new file mode 100644 index 000000000..f429f8427 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/sound/events/SoundEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.destinationsol.sound.events; + +import org.destinationsol.assets.sound.PlayableSound; +import org.terasology.gestalt.entitysystem.event.Event; + +/** + * Plays a sound emitting from an entity. + */ +public class SoundEvent implements Event { + + public final PlayableSound playableSound; + public final float volumeMultplier; + + public SoundEvent(PlayableSound playableSound, float volumeMultplier) { + this.playableSound = playableSound; + this.volumeMultplier = volumeMultplier; + } +} diff --git a/engine/src/main/java/org/destinationsol/sound/systems/SoundPlayingSystem.java b/engine/src/main/java/org/destinationsol/sound/systems/SoundPlayingSystem.java new file mode 100644 index 000000000..44c1cfb59 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/sound/systems/SoundPlayingSystem.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.destinationsol.sound.systems; + +import com.badlogic.gdx.math.Vector2; +import org.destinationsol.Const; +import org.destinationsol.assets.sound.OggSoundManager; +import org.destinationsol.assets.sound.PlayableSound; +import org.destinationsol.assets.sound.SpecialSounds; +import org.destinationsol.common.In; +import org.destinationsol.entitysystem.EventReceiver; +import org.destinationsol.force.events.ImpulseEvent; +import org.destinationsol.game.DmgType; +import org.destinationsol.game.SolGame; +import org.destinationsol.health.events.DamageEvent; +import org.destinationsol.location.components.Position; +import org.destinationsol.material.MaterialType; +import org.destinationsol.material.components.Material; +import org.destinationsol.sound.events.SoundEvent; +import org.terasology.gestalt.entitysystem.entity.EntityRef; +import org.terasology.gestalt.entitysystem.event.EventResult; +import org.terasology.gestalt.entitysystem.event.ReceiveEvent; + +/** + * This system plays sounds emitting from entities with a {@link Position} component, using the {@link OggSoundManager}. + */ +public class SoundPlayingSystem implements EventReceiver { + + @In + private SolGame game; + + @In + private OggSoundManager soundManager; + + @In + private SpecialSounds specialSounds; + + /** + * Plays a given sound emitting from an entity, at that entity's {@link Position}. + */ + @ReceiveEvent(components = Position.class) + public EventResult playSound(SoundEvent event, EntityRef entity) { + Vector2 position = entity.getComponent(Position.class).get().position; + soundManager.play(game, event.playableSound, position, entity, event.volumeMultplier); + return EventResult.CONTINUE; + } + + /** + * When an entity takes damage, this plays a sound based on the type of damage taken and the type of material that + * the entity is. No sound will be played if there is no defined sound for the {@link DmgType}/{@link MaterialType} + * combination, or if either the {@link DmgType} or {@link MaterialType} is null. + */ + @ReceiveEvent(components = {Position.class, Material.class}) + public EventResult playDamageSound(DamageEvent event, EntityRef entity) { + MaterialType materialType = entity.getComponent(Material.class).get().materialType; + PlayableSound sound = specialSounds.getHitSound(materialType, event.getDamageType()); + if (sound != null) { + Vector2 position = entity.getComponent(Position.class).get().position; + soundManager.play(game, sound, position, entity); + } + return EventResult.CONTINUE; + } + + /** + * When an entity experiences a collision, this plays a sound based on the type of material that the entity is. No + * sound will be played if there is no defined collision sound for the {@link MaterialType}, or if the + * {@link MaterialType} is null. + */ + @ReceiveEvent(components = {Position.class, Material.class}) + public EventResult playCollisionSound(ImpulseEvent event, EntityRef entity) { + float magnitude = event.getMagnitude(); + if (magnitude >= .1f) { + Vector2 position = entity.getComponent(Position.class).get().position; + MaterialType materialType = entity.getComponent(Material.class).get().materialType; + PlayableSound collisionSound = specialSounds.getCollisionSound(materialType); + if (collisionSound != null) { + soundManager.play(game, collisionSound, position, entity, magnitude * Const.IMPULSE_TO_COLL_VOL); + } + } + return EventResult.CONTINUE; + } +}