Random in-game music, untested

This commit is contained in:
Anuken 2019-08-10 18:56:20 -04:00
parent 4ef60af4a8
commit 2341da995e
10 changed files with 199 additions and 82 deletions

View File

@ -25,7 +25,7 @@ public class Annotations{
} }
/** Indicates that a method return or field cannot be null.*/ /** Indicates that a method return or field cannot be null.*/
@Target({ElementType.METHOD, ElementType.FIELD}) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface NonNull{ public @interface NonNull{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

View File

@ -1448,7 +1448,7 @@ public class Blocks implements ContentList{
size = 2; size = 2;
range = 150f; range = 150f;
reload = 30f; reload = 38f;
restitution = 0.03f; restitution = 0.03f;
ammoEjectBack = 3f; ammoEjectBack = 3f;
cooldown = 0.03f; cooldown = 0.03f;

View File

@ -358,18 +358,7 @@ public class Control implements ApplicationListener{
//autosave global data if it's modified //autosave global data if it's modified
data.checkSave(); data.checkSave();
if(state.is(State.menu)){ music.update();
if(ui.deploy.isShown()){
music.play(Musics.launch);
}else if(ui.editor.isShown()){
music.play(Musics.editor);
}else{
music.play(Musics.menu);
}
}else{
//TODO game music
music.silence();
}
if(!state.is(State.menu)){ if(!state.is(State.menu)){
input.update(); input.update();

View File

@ -3,44 +3,162 @@ package io.anuke.mindustry.game;
import io.anuke.annotations.Annotations.*; import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.audio.*; import io.anuke.arc.audio.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*;
import static io.anuke.mindustry.Vars.*;
/** Controls playback of multiple music tracks.*/ /** Controls playback of multiple music tracks.*/
public class MusicControl{ public class MusicControl{
private static final float finTime = 120f, foutTime = 120f; private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.25f, musicWaveChance = 0.2f;
/** normal, ambient music, plays at any time */
public final Array<Music> ambientMusic = Array.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6);
/** darker music, used in times of conflict */
public final Array<Music> darkMusic = Array.with(Musics.game2, Musics.game5, Musics.game7);
/** all music, both dark and ambient */
public final Array<Music> allMusic = Array.withArrays(ambientMusic, darkMusic);
private Music lastRandomPlayed;
private Interval timer = new Interval();
private @Nullable Music current; private @Nullable Music current;
private float fade; private float fade;
private boolean silenced;
public void play(@Nullable Music music){ public MusicControl(){
if(current != null){ Events.on(WaveEvent.class, e -> Time.run(60f * 10f, () -> {
current.setVolume(fade * Core.settings.getInt("musicvol") / 100f); if(Mathf.chance(musicWaveChance)){
} playRandom();
}
}));
}
if(current == null && music != null){ /** Update and play the right music track.*/
current = music; public void update(){
current.setLooping(true); if(state.is(State.menu)){
current.setVolume(fade = 0f); if(ui.deploy.isShown()){
current.play(); play(Musics.launch);
}else if(current == music && music != null){ }else if(ui.editor.isShown()){
fade = Mathf.clamp(fade + Time.delta()/finTime); play(Musics.editor);
}else if(current != null){ }else{
fade = Mathf.clamp(fade - Time.delta()/foutTime); play(Musics.menu);
}
}else if(state.rules.editor){
play(Musics.editor);
}else{
//this just fades out the last track to make way for ingame music
silence();
if(fade <= 0.01f){ //play music at intervals
current.stop(); if(timer.get(musicInterval)){
current = null; //chance to play it per interval
if(music != null){ if(Mathf.chance(musicChance)){
current = music; playRandom();
current.setVolume(fade = 0f);
current.setLooping(true);
current.play();
} }
} }
} }
} }
public void silence(){ /** Plays a random track.*/
private void playRandom(){
if(isDark()){
playOnce(darkMusic.random(lastRandomPlayed));
}else{
playOnce(ambientMusic.random(lastRandomPlayed));
}
}
/** Whether to play dark music.*/
private boolean isDark(){
if(!state.teams.get(player.getTeam()).cores.isEmpty() && state.teams.get(player.getTeam()).cores.first().entity.healthf() < 0.85f){
//core damaged -> dark
return true;
}
if(state.enemies() > 25){
//many enemies -> dark
return true;
}
//it may be dark based on wave
if(Mathf.chance((float)(Math.log10((state.wave - 17f)/19f) + 1) / 4f)){
return true;
}
return false;
}
/** Plays and fades in a music track. This must be called every frame.
* If something is already playing, fades out that track and fades in this new music.*/
private void play(@Nullable Music music){
//update volume of current track
if(current != null){
current.setVolume(fade * Core.settings.getInt("musicvol") / 100f);
}
//do not update once the track has faded out completely, just stop
if(silenced){
return;
}
if(current == null && music != null){
//begin playing in a new track
current = music;
current.setLooping(true);
current.setVolume(fade = 0f);
current.play();
silenced = false;
}else if(current == music && music != null){
//fade in the playing track
fade = Mathf.clamp(fade + Time.delta()/finTime);
}else if(current != null){
//fade out the current track
fade = Mathf.clamp(fade - Time.delta()/foutTime);
if(fade <= 0.01f){
//stop current track when it hits 0 volume
current.stop();
current = null;
silenced = true;
if(music != null){
//play newly scheduled track
current = music;
current.setVolume(fade = 0f);
current.setLooping(true);
current.play();
silenced = false;
}
}
}
}
/** Plays a music track once and only once. If something is already playing, does nothing.*/
private void playOnce(@NonNull Music music){
if(current != null) return; //do not interrupt already-playing tracks
//save last random track played to prevent duplicates
lastRandomPlayed = music;
//set fade to 1 and play it, stopping the current when it's done
fade = 1f;
current = music;
current.setVolume(1f);
current.setLooping(false);
current.setCompletionListener(m -> {
if(current == m){
current = null;
fade = 0f;
}
});
current.play();
}
/** Fades out the current track, unless it has already been silenced. */
private void silence(){
play(null); play(null);
} }
} }

View File

@ -1,12 +1,14 @@
package io.anuke.mindustry.ui.dialogs; package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.collection.Array; import io.anuke.arc.*;
import io.anuke.arc.function.Predicate; import io.anuke.arc.collection.*;
import io.anuke.arc.function.Supplier; import io.anuke.arc.function.*;
import io.anuke.arc.scene.ui.TextButton; import io.anuke.arc.input.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
import static io.anuke.mindustry.Vars.content; import static io.anuke.mindustry.Vars.*;
public class LoadoutDialog extends FloatingDialog{ public class LoadoutDialog extends FloatingDialog{
private Runnable hider; private Runnable hider;
@ -14,24 +16,58 @@ public class LoadoutDialog extends FloatingDialog{
private Runnable resetter; private Runnable resetter;
private Runnable updater; private Runnable updater;
private Predicate<Item> filter; private Predicate<Item> filter;
private Table items;
private int capacity; private int capacity;
public LoadoutDialog(){ public LoadoutDialog(){
super("$configure"); super("$configure");
setFillParent(false); setFillParent(true);
addCloseButton();
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK){
Core.app.post(this::hide);
}
});
cont.add(items = new Table()).left();
shown(this::setup); shown(this::setup);
hidden(() -> { hidden(() -> {
if(hider != null){ if(hider != null){
hider.run(); hider.run();
} }
}); });
buttons.row();
buttons.addButton("$settings.reset", () -> { cont.row();
cont.addButton("$add", () -> {
FloatingDialog dialog = new FloatingDialog("");
dialog.setFillParent(false);
for(Item item : content.items().select(item -> filter.test(item) && item.type == ItemType.material && supplier.get().find(stack -> stack.item == item) == null)){
TextButton button = dialog.cont.addButton("", "clear", () -> {
dialog.hide();
supplier.get().add(new ItemStack(item, 0));
updater.run();
setup();
}).size(300f, 36f).get();
button.clearChildren();
button.left();
button.addImage(item.icon(Item.Icon.medium)).size(8 * 3).pad(4);
button.add(item.localizedName);
dialog.cont.row();
}
dialog.show();
}).size(100f, 40).left().disabled(b -> !content.items().contains(item -> filter.test(item) && !supplier.get().contains(stack -> stack.item == item)));
cont.row();
cont.addButton("$settings.reset", () -> {
resetter.run(); resetter.run();
updater.run(); updater.run();
setup(); setup();
}).size(210f, 64f); }).size(210f, 64f);
cont.row();
cont.addImageTextButton("$back", "icon-arrow-left", iconsize, this::hide).size(210f, 64f);
} }
public void show(int capacity, Supplier<Array<ItemStack>> supplier, Runnable reseter, Runnable updater, Runnable hider, Predicate<Item> filter){ public void show(int capacity, Supplier<Array<ItemStack>> supplier, Runnable reseter, Runnable updater, Runnable hider, Predicate<Item> filter){
@ -45,50 +81,31 @@ public class LoadoutDialog extends FloatingDialog{
} }
void setup(){ void setup(){
cont.clear(); items.clearChildren();
items.left();
float bsize = 40f; float bsize = 40f;
int step = 50; int step = 50;
for(ItemStack stack : supplier.get()){ for(ItemStack stack : supplier.get()){
cont.addButton("x", "clear-partial", () -> { items.addButton("x", "clear-partial", () -> {
supplier.get().remove(stack); supplier.get().remove(stack);
updater.run(); updater.run();
setup(); setup();
}).size(bsize); }).size(bsize);
cont.addButton("-", "clear-partial", () -> { items.addButton("-", "clear-partial", () -> {
stack.amount = Math.max(stack.amount - step, 0); stack.amount = Math.max(stack.amount - step, 0);
updater.run(); updater.run();
}).size(bsize); }).size(bsize);
cont.addButton("+", "clear-partial", () -> { items.addButton("+", "clear-partial", () -> {
stack.amount = Math.min(stack.amount + step, capacity); stack.amount = Math.min(stack.amount + step, capacity);
updater.run(); updater.run();
}).size(bsize); }).size(bsize);
cont.addImage(stack.item.icon(Item.Icon.medium)).size(8 * 3).padRight(4); items.addImage(stack.item.icon(Item.Icon.medium)).size(8 * 3).padRight(4).padLeft(4);
cont.label(() -> stack.amount + "").left(); items.label(() -> stack.amount + "").left();
cont.row(); items.row();
} }
cont.addButton("$add", () -> {
FloatingDialog dialog = new FloatingDialog("");
dialog.setFillParent(false);
for(Item item : content.items().select(item -> filter.test(item) && item.type == ItemType.material && supplier.get().find(stack -> stack.item == item) == null)){
TextButton button = dialog.cont.addButton("", "clear", () -> {
supplier.get().add(new ItemStack(item, 0));
updater.run();
setup();
dialog.hide();
}).size(300f, 36f).get();
button.clearChildren();
button.left();
button.addImage(item.icon(Item.Icon.medium)).size(8 * 3).pad(4);
button.add(item.localizedName);
dialog.cont.row();
}
dialog.show();
}).colspan(4).size(100f, bsize).left().disabled(b -> !content.items().contains(item -> filter.test(item) && !supplier.get().contains(stack -> stack.item == item)));
pack();
} }
} }

View File

@ -147,12 +147,7 @@ public class ServerControl implements ApplicationListener{
Array<Map> maps = world.maps.customMaps().size == 0 ? world.maps.defaultMaps() : world.maps.customMaps(); Array<Map> maps = world.maps.customMaps().size == 0 ? world.maps.defaultMaps() : world.maps.customMaps();
Map previous = world.getMap(); Map previous = world.getMap();
Map map = previous; Map map = maps.random(previous);
if(maps.size > 1){
while(map == previous) map = maps.random();
}else if(!previous.custom && !world.maps.customMaps().isEmpty()){
map = maps.first();
}
Call.onInfoMessage((state.rules.pvp Call.onInfoMessage((state.rules.pvp
? "[YELLOW]The " + event.winner.name() + " team is victorious![]" : "[SCARLET]Game over![]") ? "[YELLOW]The " + event.winner.name() + " team is victorious![]" : "[SCARLET]Game over![]")
@ -162,9 +157,7 @@ public class ServerControl implements ApplicationListener{
info("Selected next map to be {0}.", map.name()); info("Selected next map to be {0}.", map.name());
Map fmap = map; play(true, () -> world.loadMap(map));
play(true, () -> world.loadMap(fmap));
} }
}else{ }else{
netServer.kickAll(KickReason.gameover); netServer.kickAll(KickReason.gameover);