Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions engine/src/main/java/org/destinationsol/SolApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
import org.destinationsol.location.components.Angle;
import org.destinationsol.location.components.Position;
import org.destinationsol.location.components.Velocity;
import org.destinationsol.material.MaterialType;
import org.destinationsol.material.components.Material;
import org.destinationsol.menu.MenuScreens;
import org.destinationsol.menu.background.MenuBackgroundManager;
import org.destinationsol.modules.ModuleManager;
Expand Down Expand Up @@ -284,7 +286,7 @@ private void draw() {
if (DebugOptions.SPAWN_ECS_ASTEROID && !entityCreated) {

Size size = new Size();
size.size = 2;
size.size = 1;

RenderableElement element = new RenderableElement();
element.texture = SolRandom.randomElement(Assets.listTexturesMatching("engine:asteroid_.*"));
Expand All @@ -298,13 +300,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +33,7 @@
import org.destinationsol.game.UpdateAwareSystem;
import org.destinationsol.game.planet.Planet;
import org.destinationsol.game.sound.DebugHintDrawer;
import org.terasology.gestalt.entitysystem.entity.EntityRef;

import javax.inject.Inject;
import javax.inject.Provider;
Expand Down Expand Up @@ -72,7 +74,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<SolObject, Map<OggSound, Float>> loopedSoundMap;
private final Map<SolObject, Map<OggSound, Float>> 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<EntityRef, Map<OggSound, Float>> loopedSoundMapOfEntities;
/**
* Used for drawing debug hints when {@link DebugOptions#SOUND_INFO} flag is set. See
* {@link #drawDebug(GameDrawer, SolCam)} for more info.
Expand All @@ -92,7 +101,8 @@ public class OggSoundManager implements UpdateAwareSystem {
@Inject
public OggSoundManager(Provider<SolApplication> applicationProvider) {
soundMap = new HashMap<>();
loopedSoundMap = new HashMap<>();
loopedSoundMapOfSolObjects = new HashMap<>();
loopedSoundMapOfEntities = new HashMap<>();
debugHintDrawer = new DebugHintDrawer();
this.applicationProvider = applicationProvider;
}
Expand Down Expand Up @@ -172,25 +182,87 @@ public void play(SolGame game, PlayableSound playableSound, @Nullable Vector2 po
position = source.getPosition();
}

float volume = getVolume(game, position, volumeMultiplier, sound, game.getCam());
if (play(game, sound, playableSound.getBasePitch(), position, volumeMultiplier, loopedSoundMapOfSolObjects, source)
&& DebugOptions.SOUND_INFO) {
debugHintDrawer.add(source, position, sound.toString());
}
}

if (volume <= 0) {
/**
* 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.
*/
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");
}

// Calculate the pitch for the sound
float pitch = SolRandom.randomFloat(.97f, 1.03f) * game.getTimeFactor() * playableSound.getBasePitch();
OggSound sound = playableSound.getOggSound();

if (skipLooped(source, sound, game.getTime())) {
return;
if (play(game, sound, playableSound.getBasePitch(), position, volumeMultiplier, loopedSoundMapOfEntities, soundSource)
&& DebugOptions.SOUND_INFO) {
debugHintDrawer.add(soundSource, position, sound.toString());
}
}

if (DebugOptions.SOUND_INFO) {
debugHintDrawer.add(source, position, sound.toString());
/**
* The shared body of the public {@code play} overloads. Everything about playing a sound is identical between a
* {@link SolObject} source and an {@link EntityRef} source, except for which map the loop bookkeeping lives in -
* so the caller supplies that map along with the key to use within it.
* <p>
* The already-resolved {@link OggSound} is taken rather than a {@link PlayableSound}, because a
* {@link PlayableSound} may pick a different sound on every call (see {@link OggSoundSet}) and so must only be
* resolved once per playback.
*
* @param game Game to play the sound in.
* @param sound The sound to play.
* @param basePitch The base pitch of the {@link PlayableSound} the sound was resolved from.
* @param position Position to play the sound at. Must not be null.
* @param volumeMultiplier Multiplier for sound volume.
* @param loopedSounds The loop bookkeeping map appropriate for {@code source}.
* @param source Bearer of the sound, used as the key into {@code loopedSounds}.
* @param <T> The type of the sound's bearer.
* @return true if the sound was played, false if it was inaudible or suppressed by its loop.
*/
private <T> boolean play(SolGame game, OggSound sound, float basePitch, Vector2 position, float volumeMultiplier,
Map<T, Map<OggSound, Float>> loopedSounds, @Nullable T source) {
float volume = getVolume(game, position, volumeMultiplier, sound, game.getCam());
if (volume <= 0) {
return false;
}

// Calculate the pitch for the sound
float pitch = SolRandom.randomFloat(.97f, 1.03f) * game.getTimeFactor() * basePitch;

if (skipLooped(loopedSounds, source, sound, game.getTime())) {
return false;
}

Sound gdxSound = sound.getSound();
gdxSound.play(volume, pitch, 0);
return true;
}

/**
Expand Down Expand Up @@ -237,20 +309,22 @@ private float getVolume(SolGame game, Vector2 position, float volumeMultiplier,
* 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.
* @param loopedSounds The loop bookkeeping map appropriate for {@code source}.
* @param source Object or entity playing this sound.
* @param sound Sound to be played.
* @param time Game's current time.
* @param <T> The type of the sound's bearer.
* @return true when sound should not be played because of loop, false otherwise.
*/
private boolean skipLooped(SolObject source, OggSound sound, float time) {
private <T> boolean skipLooped(Map<T, Map<OggSound, Float>> loopedSounds, T source, OggSound sound, float time) {
if (sound.getLoopTime() == 0) {
return false;
}

Map<OggSound, Float> looped = loopedSoundMap.get(source);
Map<OggSound, Float> looped = loopedSounds.get(source);
if (looped == null) {
looped = new HashMap<>();
loopedSoundMap.put(source, looped);
loopedSounds.put(source, looped);
return false;
} else {
Float endTime = looped.get(sound);
Expand Down Expand Up @@ -294,14 +368,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.
* <p>
* (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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@

import com.badlogic.gdx.math.Vector2;
import org.destinationsol.Const;
import org.destinationsol.common.Nullable;
import org.destinationsol.game.DmgType;
import org.destinationsol.game.SolGame;
import org.destinationsol.game.SolObject;
import org.destinationsol.material.MaterialType;
import org.terasology.gestalt.entitysystem.entity.EntityRef;

import javax.inject.Inject;
import java.util.Arrays;
import java.util.Optional;

public class SpecialSounds {

/**
* Collisions gentler than this do not make a sound.
*/
private static final float MIN_COLLISION_IMPULSE = .1f;

public final PlayableSound metalColl;
public final PlayableSound metalEnergyHit;
public final PlayableSound rockColl;
Expand Down Expand Up @@ -73,39 +82,109 @@ public SpecialSounds(OggSoundManager soundManager) {
transcendentMove = new OggSoundSet(soundManager, Arrays.asList("core:transcendentMove", "core:transcendentMove2", "core:transcendentMove3", "core:transcendentMove4"));
}

public PlayableSound hitSound(boolean forMetal, DmgType dmgType) {
/**
* The sound made when something of the given material is hit by the given kind of damage.
* <p>
* This is the single place where hit sounds are selected: both the {@link SolObject} and the entity code paths
* resolve their sound through it.
*
* @param materialType the material of the thing being hit, or null if it is not made of a known material
* @param dmgType the kind of damage being dealt, or null if it is not known
* @return the sound to play, or empty if no sound is defined for that combination
*/
public Optional<PlayableSound> hitSound(@Nullable MaterialType materialType, @Nullable DmgType dmgType) {
if (materialType == null || dmgType == null) {
return Optional.empty();
}
boolean metal = materialType == MaterialType.METAL;
if (dmgType == DmgType.ENERGY) {
return forMetal ? metalEnergyHit : rockEnergyHit;
return Optional.of(metal ? metalEnergyHit : rockEnergyHit);
}
if (dmgType == DmgType.BULLET) {
return forMetal ? metalBulletHit : rockBulletHit;
return Optional.of(metal ? metalBulletHit : rockBulletHit);
}
return Optional.empty();
}

/**
* The sound made when something of the given material collides with something else.
* <p>
* This is the single place where collision sounds are selected: both the {@link SolObject} and the entity code
* paths resolve their sound through it.
*
* @param materialType the material of the colliding thing, or null if it is not made of a known material
* @return the sound to play, or empty if no sound is defined for that material
*/
public Optional<PlayableSound> collisionSound(@Nullable MaterialType materialType) {
if (materialType == null) {
return Optional.empty();
}
return null;
return Optional.of(materialType == MaterialType.METAL ? metalColl : rockColl);
}

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) {
hitSound(materialTypeOf(o), dmgType)
.ifPresent(sound -> game.getSoundManager().play(game, sound, position, o));
}

/**
* The entity-based counterpart of {@link #playHit(SolGame, SolObject, Vector2, DmgType)}. An entity carries its
* material as a component rather than through {@link SolObject#isMetal()}, and its position is not derivable from
* the sound's bearer, so both are passed in; sound selection is shared.
*
* @param game Game to play the sound in.
* @param entity The entity that was hit; the sound is attached to it for looping and debug purposes.
* @param position Where the hit happened.
* @param dmgType The kind of damage dealt.
* @param materialType The material the entity is made of.
*/
public void playHit(SolGame game, EntityRef entity, Vector2 position, @Nullable DmgType dmgType, @Nullable MaterialType materialType) {
hitSound(materialType, dmgType)
.ifPresent(sound -> game.getSoundManager().play(game, sound, position, entity));
}

public void playColl(SolGame game, float absImpulse, SolObject o, Vector2 position) {
if (o == null || absImpulse < MIN_COLLISION_IMPULSE) {
return;
}
game.getSoundManager().play(game, sound, position, o);
collisionSound(materialTypeOf(o))
.ifPresent(sound -> game.getSoundManager().play(game, sound, position, o, absImpulse * Const.IMPULSE_TO_COLL_VOL));
}

public void playColl(SolGame game, float absImpulse, SolObject o, Vector2 position) {
if (o == null || absImpulse < .1f) {
/**
* The entity-based counterpart of {@link #playColl(SolGame, float, SolObject, Vector2)}. See
* {@link #playHit(SolGame, EntityRef, Vector2, DmgType, MaterialType)} for why the material and position are
* passed in rather than read off the sound's bearer.
*
* @param game Game to play the sound in.
* @param absImpulse The magnitude of the impulse of the collision.
* @param entity The entity that collided; the sound is attached to it for looping and debug purposes.
* @param position Where the collision happened.
* @param materialType The material the entity is made of.
*/
public void playColl(SolGame game, float absImpulse, EntityRef entity, Vector2 position, @Nullable MaterialType materialType) {
if (absImpulse < MIN_COLLISION_IMPULSE) {
return;
}
Boolean metal = o.isMetal();
collisionSound(materialType)
.ifPresent(sound -> game.getSoundManager().play(game, sound, position, entity, absImpulse * Const.IMPULSE_TO_COLL_VOL));
}

/**
* Bridges the {@link SolObject} representation of a material - a nullable {@link Boolean} "is it metal?" - to the
* {@link MaterialType} used by entities.
*
* @return the object's material, or null if the object does not declare one
*/
@Nullable
private static MaterialType materialTypeOf(SolObject solObject) {
Boolean metal = solObject.isMetal();
if (metal == null) {
return;
return null;
}
game.getSoundManager().play(game, metal ? metalColl : rockColl, position, o, absImpulse * Const.IMPULSE_TO_COLL_VOL);
return metal ? MaterialType.METAL : MaterialType.ROCK;
}
}
Loading