Compare commits

...

10 Commits

Author SHA1 Message Date
Mysaa
a41f44080d Réécriture du serveur, avec des clés d'encryption partout. Semble compliqué. Risque de disparaître. 2021-08-27 02:17:17 +02:00
Mysaa
668e472e71 Ajout de commandes pour l'accès aux parties. 2021-08-20 15:47:11 +02:00
149911f430 Ajout d'un README 2021-07-08 15:47:25 +02:00
7567602c61 Ajout des paramètres, et déplacement des classes Util dans BernardLibs 2021-07-06 19:48:14 +02:00
11c91fe87e Mis a jour de la librairie de Yaml. C'est maintenant plus joli. 2021-07-02 22:23:36 +02:00
2fdaf82782 Moins d'items dans la todolist:
Suppression de pas mal de messages polluant le sysout, notamment des
dumps de games.
Les layouts sont mieux gérés, notamment avec plus de miniels,
pré-compatible avec la configuration.
Ajout des menus clic-droit des actions et des inventaires, permettant de
reset/supprimer/créer ...
Ajout du line wrapping de blocs «notes» qui manquait.
2021-07-02 01:18:31 +02:00
2fe6235869 La sauvegarde automatique de tous les miniels, notament le ServerMiniel.
Le programme demande aussi avant de se fermer et supprime alors les
sauvegardes automatiques maintenant inutiles.
2021-06-30 22:38:01 +02:00
3969b66c68 Une interface plus jolie, la capacité de désactiver les enceintes depuis
Master (NON TESTÉ !)
2021-06-27 11:03:13 +02:00
Mysaa
23c25f0e05 La communication des salles d'écoutes est normalement fonctionelle, il manque des tests plus approfondits 2021-06-26 21:40:40 +02:00
Mysaa
c17ccbfb71 Quelques fix sur le code du serveur 2021-05-26 22:14:56 +02:00
41 changed files with 2473 additions and 653 deletions

37
README.md Normal file
View File

@ -0,0 +1,37 @@
#Murderator
Ce programme permet d'organiser, de gérer et planifier des soirées «Murder» ou «huis-clos». L'objectif est d'aider le ou les opérateurs à garder une trace de quels objets sont dans quelles salles, qui a utilisé quel pouvoir quand.
##### Compilation
> Pour complier, une simple commande `gradle jar` crée un fichier jar fonctionnel dans le dossier `build/libs/`
### Utilisation
Pour chaque session ou partie, un ordinateur doit utiliser un terminal maître, qui sera créé un chargant un fichier décrivant l'état initial de la Murder. Un exemple de tel fichier est donné dans le dossier `src/test/resources`.
##### Objets
Un simple système de drag&drop permet de déplacer facilement les objets, même à travers les onglets, avec sélection au survol. Les menus au clic droit permettent de créer/renommer/supprimer les objets.
##### Actions
Un seul bouton à actionner et les actions sont «déclenchées». Elles peuvent aussi être réinitialisées avec le menu clic droit.
##### Enceinte et microphones
D'autres ordinateurs peuvent être utilisés comme serveur enceinte ou microphones. Afin de les connecter, il faut d'abord sélectionner l'interface audio à utiliser, puis indiquer l'adresse IP du terminal maître. L'appareil devrait alors apparaître sur le terminal maître. Ensuite, les enceintes peuvent se connecter à n'importe quel micro.
### Principe
L'application est écrite selon un système MVC (Model/Vue/Contrôlleur). Le modèle est un ensemble de classes Java décrivant la Partie, présentes dans le paquet `com.bernard.murder.model`
La vue est définie de manière modulaire, à travers des «Minels». Les minels, présents dans le paquet `com.bernard.murder.view.minel`, implémentent une fonction `genContentPane()` qui sera appelée une fois et qui renvoie un Panel qui sera présent dans la fenêtre.
Les minels et les onglets présents sont définis dans la classe `MinelsCreator`.
Dans le cas où plusieurs terminal sont présents, il n'y a qu'un seul terminal maître qui a l'assurance de l'intégrité des données.
### Fonctionalités
##### Partie
* Stoquage et déplacement des objets dans les salles et les inventaires
* Actions des joueurs
##### Application
* Sauvegardes automatiques de la partie et des minels
* Allumage et Extinction propre des serveurs

View File

@ -12,10 +12,22 @@ apply plugin: 'eclipse'
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
}
task apiJar(type: Jar) {
archiveFileName = "MurderatorAPI.jar"
group 'build'
description "Fait un jar avec juste l'api"
from(sourceSets.main.output) {
include "com/bernard/murder/model/**"
include "com/bernard/murder/audio/Serveur*.class"
include "com/bernard/murder/audio/Codes*.class"
}
}
jar {
manifest {
attributes(
@ -29,7 +41,9 @@ jar {
dependencies {
implementation 'com.amihaiemil.web:eo-yaml:5.1.3'
implementation 'com.formdev:flatlaf:0.37'
implementation 'com.amihaiemil.web:eo-yaml:5.2.1'
implementation 'com.formdev:flatlaf:1.3'
implementation files('../../bernard/BernardLibs.git/build/libs/BernardLibs.jar')
}

11
murderator-config.yml Normal file
View File

@ -0,0 +1,11 @@
class: com.bernard.murder.Parametres
minielTitleFont:
name: Dialog
style: 1
size: 12
textPanMinielBackgroundColor: 0xffffe085
textPanMinielTextColor: 0xff274290
minielMinSize: 200x300
minielParLigne: 2
lignesDeMinielAvantScroll: 2
lookAndFeel: "flatlaf-light"

View File

@ -1,20 +0,0 @@
package com.bernard.murder;
import java.nio.ByteBuffer;
public class BytesUtils {
public static String readString(ByteBuffer buffer) {
int stringLength = buffer.getInt();
byte[] stringData = new byte[stringLength];
buffer.get(stringData);
return new String(stringData);
}
public static void writeString(ByteBuffer buffer, String s) {
byte[] data = s.getBytes();
buffer.putInt(data.length);
buffer.put(data);
}
}

View File

@ -0,0 +1,42 @@
package com.bernard.murder;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.font.TextAttribute;
import java.util.HashMap;
import java.util.Map;
import javax.swing.UIManager;
import com.bernard.configurator.annotations.ConfigClass;
@ConfigClass(requireOption = false)
public class Parametres {
public static Parametres instance;
public Font minielTitleFont = UIManager.getFont("Label.font");
public Color textPanMinielBackgroundColor = new Color(0xFFE085);
public Color textPanMinielTextColor = new Color(0x274290);
public Dimension minielMinSize = new Dimension(200, 300);
public int minielParLigne = 2;
public int lignesDeMinielAvantScroll = 2;
public String lookAndFeel = "flatlaf-darcula";
public String encryptionAlg = "RSA";
public int encryptionKeySize = 2048;
public Parametres() {
Map<TextAttribute, Object> ftAttrs = new HashMap<>(minielTitleFont.getAttributes());
ftAttrs.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
minielTitleFont = minielTitleFont.deriveFont(Font.BOLD).deriveFont(ftAttrs);
}
}

View File

@ -1,147 +0,0 @@
package com.bernard.murder;
import java.awt.Color;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.YamlSequence;
import com.amihaiemil.eoyaml.YamlSequenceBuilder;
public class ParseUtils {
static Pattern timeLengthPattern = Pattern.compile("^(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s?)?$");
public static long parseTimeLength(String tl) {
Matcher mtch = timeLengthPattern.matcher(tl);
if(!mtch.matches())throw new IllegalArgumentException("La chaine de caractères «"+tl+"» ne décrit pas un intervalle de temps normalisé");
int h = mtch.group(2)==null?0:Integer.parseInt(mtch.group(2));
int m = mtch.group(4)==null?0:Integer.parseInt(mtch.group(4));
int s = mtch.group(6)==null?0:Integer.parseInt(mtch.group(6));
return (h*3600+m*60+s)*1000;
}
public static <T> T watch(T el) {
System.out.println(el);
return el;
}
public static String dumpTimeLength(long t) {
long h=t/1000/3600, m=t/1000/60%60, s=t/1000%60;
return (h!=0?h+"h":"" )+ (m!=0?m+"m":"")+(s!=0?s+"s":"");
}
public static String dumpHourDate(long t) {
return DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.ofInstant(Instant.ofEpochMilli(t-t%1000), ZoneId.systemDefault()));
}
public static <T> Set<T> union(Collection<T> c1, Collection<T> c2){
Set<T> out = c1.stream().collect(Collectors.toSet());
out.addAll(c2);
return out;
}
public static boolean isSubWord(String word, String subword) {
int i=0,j=0;
while(true) {
if(i==subword.length())return true;
if(j==word.length())return false;
if(subword.charAt(i)==word.charAt(j))i++;
j++;
}
}
public static boolean and(boolean[] bb) {
for(boolean b : bb)
if(!b)return false;
return true;
}
public static Set<String> mappingKeys(YamlMapping mapping) throws IOException{
return mapping.keys().stream().map(n ->n.asScalar().value()).collect(Collectors.toSet());
}
public static Stream<YamlNode> sequenceStream(YamlSequence sequence){
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(sequence.iterator(),Spliterator.ORDERED),false);
}
public static <T> Function<YamlNode,T> wetherMapping(Function<YamlNode,T> fnot,Function<YamlMapping,T> fyes){
return n -> (n instanceof YamlMapping)?fyes.apply((YamlMapping)n):fnot.apply(n);
}
public static Color randColor() {
return Color.getHSBColor((float) Math.random(), 1.0f, 1.0f);
}
public static Color randDarkColor() {
return Color.getHSBColor((float) Math.random(), 1.0f, 0.3f);
}
public static Color randDarkBlueColor() {
// 180° to 240°
return Color.getHSBColor( (float) (0.5f + 0.2f*Math.random()), 1.0f, 0.3f);
}
public static Color getContrastColor(Color color) {
double y = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;
return y >= 128 ? Color.black : Color.white;
}
public static final <M> YamlMapping setToMapSS(Set<M> nodes, Function<M,String> key, Function<M,String> value) {
YamlMappingBuilder builder = Yaml.createYamlMappingBuilder();
for(M n : nodes)
builder = builder.add(key.apply(n), value.apply(n));
return builder.build();
}
public static final <M> YamlMapping setToMapSY(Set<M> nodes, Function<M,String> key, Function<M,YamlNode> value) {
YamlMappingBuilder builder = Yaml.createYamlMappingBuilder();
for(M n : nodes)
builder = builder.add(key.apply(n), value.apply(n));
return builder.build();
}
public static final <M> YamlMapping setToMapYS(Set<M> nodes, Function<M,YamlNode> key, Function<M,String> value) {
YamlMappingBuilder builder = Yaml.createYamlMappingBuilder();
for(M n : nodes)
builder = builder.add(key.apply(n), value.apply(n));
return builder.build();
}
public static final <M> YamlMapping setToMapYY(Set<M> nodes, Function<M,YamlNode> key, Function<M,YamlNode> value) {
YamlMappingBuilder builder = Yaml.createYamlMappingBuilder();
for(M n : nodes)
builder = builder.add(key.apply(n), value.apply(n));
return builder.build();
}
public static final <M> YamlSequence setToSeqY(Set<YamlNode> nodes) {
YamlSequenceBuilder builder = Yaml.createYamlSequenceBuilder();
for(YamlNode n : nodes)
builder = builder.add(n);
return builder.build();
}
public static final <M> YamlSequence setToSeqS(Set<String> nodes) {
YamlSequenceBuilder builder = Yaml.createYamlSequenceBuilder();
for(String n : nodes)
builder = builder.add(n);
return builder.build();
}
}

View File

@ -1,120 +0,0 @@
package com.bernard.murder;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import com.amihaiemil.eoyaml.Node;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.YamlSequence;
import com.amihaiemil.eoyaml.YamlSequenceBuilder;
public class YamlUtils {
public static final YamlSequence listToSeq(List<YamlNode> nodes) {
YamlSequenceBuilder ysb = Yaml.createYamlSequenceBuilder();
for(YamlNode n : nodes)
ysb = ysb.add(n);
return ysb.build();
}
public static final YamlSequence listToSeqString(List<String> nodes) {
YamlSequenceBuilder ysb = Yaml.createYamlSequenceBuilder();
for(String n : nodes)
ysb = ysb.add(n);
return ysb.build();
}
public static final YamlMapping mapToMapping(Map<YamlNode,YamlNode> nodes) {
YamlMappingBuilder ysb = Yaml.createYamlMappingBuilder();
for(Entry<YamlNode, YamlNode> n : nodes.entrySet())
ysb = ysb.add(n.getKey(),n.getValue());
return ysb.build();
}
public static final YamlSequence getSequence(YamlNode node) {
if(node.type()==Node.SEQUENCE)return node.asSequence();
if(node.type()==Node.SCALAR && node.asScalar().value().contentEquals("[]"))return Yaml.createYamlSequenceBuilder().build();
throw new IllegalArgumentException("Le noeud n'est pas une séquence");
}
public static final YamlMapping getMapping(YamlNode node) {
if(node.type()==Node.MAPPING)return node.asMapping();
if(node.type()==Node.SCALAR && node.asScalar().value().contentEquals("{}"))return Yaml.createYamlMappingBuilder().build();
throw new IllegalArgumentException("Le noeud n'est pas une séquence");
}
public static final boolean isMapping(YamlNode node) {
return (node.type()==Node.MAPPING) || (node.type()==Node.SCALAR && node.asScalar().value().contentEquals("{}"));
}
public static final boolean isSequence(YamlNode node) {
return (node.type()==Node.SEQUENCE) || (node.type()==Node.SCALAR && node.asScalar().value().contentEquals("[]"));
}
/**
* (Copied from Collectors class)
* Simple implementation class for {@code Collector}.
*
* @param <T> the type of elements to be collected
* @param <R> the type of the result
*/
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
@Override
public BiConsumer<A, T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A, R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
}

View File

@ -6,103 +6,148 @@ import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ThreadLocalRandom;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.sound.sampled.AudioFormat;
import javax.swing.JOptionPane;
import com.bernard.murder.BytesUtils;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.YamlSequenceBuilder;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.game.GameNetworkInterface;
import com.bernard.murder.model.Personnage;
import com.bernard.util.BytesUtils;
import com.bernard.util.ParseUtils;
import com.bernard.util.YamlUtils;
public class AudioServer {
// Format des paquets [commande 1, UUID identifier, byte deviceType, String name]
public static final byte DECLARE_NUMBER = 0x02;
// Format des paquets [commande 1, UUID identifier, byte deviceType, int id]
public static final byte OK_ID = 0x03;
// Format des paquets [commande 1]
public static final byte ASK_AUDIO_LIST= 0x04;
// Format des paquets [commande 1, int Count, {int id, String name}]
public static final byte GIVE_AUDIO_LIST = 0x05;
// Format des paquets: [commande 1, int listenId, int myId]
public static final byte ASK_STREAMING = 0x06;
// Format des paquets: [commande 1, int id]
public static final byte START_STREAMING = 0x07;
// Format des paquets: [commande 1, int listenId, int myId]
public static final byte ASK_STOP_STREAMING = 0x09;
// Format des paquets: [commande 1, int id]
public static final byte STOP_STREAMING = 0x08;
// Format des paquets [commande 1, int id, ~ data]
public static final byte AUDIO_STREAM = 0x01;
public static final byte SPEAKER_DEVICE = 0x01;
public static final byte MIC_DEVICE = 0x02;
public static AudioFormat formatAudio = new AudioFormat(8000f, 16, 1, true, true);
public static int packetMaxSize = 97282;
public static int communicationPort = 35295;
public Serveur serveur;
int micId = 0;
int speakerId = 0;
private GameNetworkInterface gameInterface;
Map<Integer,String> mics;
Map<Integer,String> speakers;
Map<Integer,SocketAddress> micsAddr;
Map<Integer,SocketAddress> speakersAddr;
Map<Integer,List<Integer>> listening; // micId, List<speakerId>
public AudioServer() {
private Set<Runnable> changeListeners;
private Set<Consumer<Exception>> serverErrorException;
public AudioServer(GameManager manager) throws SocketException, UnknownHostException {
mics = new HashMap<Integer, String>();
micsAddr = new HashMap<Integer, SocketAddress>();
speakers = new HashMap<Integer, String>();
speakersAddr = new HashMap<Integer, SocketAddress>();
listening = new HashMap<Integer, List<Integer>>();
changeListeners = new HashSet<>();
serverErrorException = new HashSet<>();
try {
initServer();
} catch (SocketException | UnknownHostException e) {
e.printStackTrace();
gameInterface = new GameNetworkInterface(manager, this);
initServer();
}
public AudioServer(GameManager manager, YamlMapping data) throws SocketException, UnknownHostException {
this(manager);
for(YamlNode spkn : data.yamlMapping("enceintes").values()) {
try {
YamlMapping spk = spkn.asMapping();
int id = spk.integer("id");
String name = spk.string("name");
SocketAddress address = (SocketAddress) ParseUtils.fromBase64(spk.string("address"));
speakers.put(id, name);
speakersAddr.put(id, address);
} catch (ClassNotFoundException e) {
System.err.println("Impossible de récupérer l'adresse de cette enceinte");
e.printStackTrace();
}
}
for(YamlNode micn : data.yamlMapping("micros").values()) {
try {
YamlMapping mic = micn.asMapping();
int id = mic.integer("id");
String name = mic.string("name");
SocketAddress address = (SocketAddress) ParseUtils.fromBase64(mic.string("address"));
mics.put(id, name);
micsAddr.put(id, address);
listening.put(id, new ArrayList<>());
for(YamlNode ecId : mic.yamlSequence("listening").values()) {
listening.get(id).add(Integer.parseInt(ecId.asScalar().value()));
}
} catch (ClassNotFoundException e) {
System.err.println("Impossible de récupérer l'adresse de ce micro");
e.printStackTrace();
}
}
micId = mics.keySet().stream().collect(Collectors.maxBy(Integer::compare)).orElse(-1)+1;
speakerId = speakers.keySet().stream().collect(Collectors.maxBy(Integer::compare)).orElse(-1)+1;
publishAudioList(speakersAddr.values());
changeListeners.forEach(Runnable::run);
//XXX: Ajouter des tests demandant si toutes les enceintes sont toujours connéctées
// et savoir quel micro est connécté.
}
public void initServer() throws SocketException, UnknownHostException {
serveur = new Serveur(this::receiveCommand, AudioServer.communicationPort);
serveur = new Serveur(this::receiveCommand, Codes.communicationPort);
}
public void receiveCommand(ByteBuffer data,SocketAddress senderAddress) {
byte commande = data.get();
System.out.println("Commande reçue : "+commande);
System.out.println("Commande reçue : "+commande+" de "+senderAddress+" de taille "+(data.limit()+1));
switch (commande) {
case AudioServer.DECLARE_NUMBER:
case Codes.DECLARE_NUMBER:
UUID uuid = new UUID(data.getLong(), data.getLong());
byte deviceType = data.get();
String deviceName = BytesUtils.readString(data);
int newId;
switch (deviceType) {
case AudioServer.MIC_DEVICE:
case Codes.Device.MIC_DEVICE:
newId = micId++;
mics.put(newId, deviceName);
micsAddr.put(newId, senderAddress);
listening.put(newId, new ArrayList<>());
publishAudioList(speakersAddr.values());
break;
case AudioServer.SPEAKER_DEVICE:
case Codes.Device.SPEAKER_DEVICE:
newId = speakerId++;
speakers.put(newId, deviceName);
speakersAddr.put(newId, senderAddress);
break;
default:return;
}
ByteBuffer out = ByteBuffer.allocate(AudioServer.packetMaxSize);
out.put(AudioServer.OK_ID);
ByteBuffer out = ByteBuffer.allocate(Codes.packetMaxSize);
out.put(Codes.OK_ID);
out.putLong(uuid.getMostSignificantBits());
out.putLong(uuid.getLeastSignificantBits());
out.put(deviceType);
@ -112,71 +157,353 @@ public class AudioServer {
} catch (IOException e1) {
e1.printStackTrace();
}
changeListeners.forEach(Runnable::run);
System.out.println("Accepting request from "+senderAddress);
break;
case AudioServer.ASK_STREAMING:
case Codes.ASK_STREAMING:
int listened = data.getInt();
int listener = data.getInt();
listening.get(listened).add(listener);
ByteBuffer out3 = ByteBuffer.allocate(AudioServer.packetMaxSize);
out3.put(AudioServer.START_STREAMING);
if(listening.get(listened).contains(listener))
break;
ByteBuffer out3 = ByteBuffer.allocate(Codes.packetMaxSize);
out3.put(Codes.START_STREAMING);
out3.putInt(listened);
try {
serveur.sendData(out3, micsAddr.get(listened));
listening.get(listened).add(listener);
changeListeners.forEach(Runnable::run);
} catch (IOException e2) {
e2.printStackTrace();
}
break;
case AudioServer.STOP_STREAMING:
case Codes.ASK_STOP_STREAMING:
int listened2 = data.getInt();
int listener2 = data.getInt();
listening.get(listener2).remove(listened2);
ByteBuffer out4 = ByteBuffer.allocate(AudioServer.packetMaxSize);
out4.put(AudioServer.STOP_STREAMING);
out4.putInt(listened2);
try {
serveur.sendData(out4, micsAddr.get(listened2));
} catch (IOException e2) {
e2.printStackTrace();
if(listened2==-1)break;
try{
listening.get(listened2).remove(listener2);
}catch(IndexOutOfBoundsException e) {
System.err.println("Les données enregistrées par ce serveur ne sont pas intègres !");
}
changeListeners.forEach(Runnable::run);
if(listening.get(listened2).isEmpty()) {
try {
ByteBuffer out4 = ByteBuffer.allocate(Codes.packetMaxSize);
out4.put(Codes.STOP_STREAMING);
out4.putInt(listened2);
serveur.sendData(out4, micsAddr.get(listened2));
} catch (IOException e2) {
e2.printStackTrace();
}
}
break;
case AudioServer.AUDIO_STREAM:
case Codes.AUDIO_STREAM:
int micId = data.getInt();
byte[] audioData = new byte[data.remaining()];
data.get(audioData);
data.position(data.limit());
for(int spck : listening.get(micId)) {
data.clear();
SocketAddress dest = speakersAddr.get(spck);
try {
serveur.sendData(data, dest);
} catch (IOException e1) {
e1.printStackTrace();
JOptionPane.showMessageDialog(null, "Impossible de transmettre le son !","Son impossible !",JOptionPane.ERROR_MESSAGE);
}
}
break;
case AudioServer.ASK_AUDIO_LIST:
ByteBuffer out2 = ByteBuffer.allocate(AudioServer.packetMaxSize);
out2.put(AudioServer.GIVE_AUDIO_LIST);
out2.putInt(mics.size());
for(Entry<Integer, String> e : mics.entrySet()) {
out2.putInt(e.getKey());
BytesUtils.writeString(out2, e.getValue());
case Codes.ASK_AUDIO_LIST:
System.out.println("Sending audio list to "+senderAddress+" : ");
publishAudioList(Collections.singleton(senderAddress));
break;
case Codes.DISCONNECTING:
byte deviceType2 = data.get();
int deviceId = data.getInt();
if(deviceType2==Codes.Device.MIC_DEVICE) {
// On le déréférence
if(!mics.containsKey(deviceId)) {
System.out.println("Le micro d'id "+deviceId+" est déjà désinscrit");
break;
}
System.out.println("Déconnection du microphone "+mics.get(deviceId));
mics.remove(deviceId);
micsAddr.remove(deviceId);
changeListeners.forEach(Runnable::run);
// On enlève tous les liens d'écoute
for(int spkId : listening.get(deviceId)) {
// Annoncer à ce speaker que le micro est déconnécté et qu'il ne peut plus l'écouter.
ByteBuffer out5 = ByteBuffer.allocate(Codes.packetMaxSize);
out5.put(Codes.DISCONNECTING);
out5.put(Codes.Device.MIC_DEVICE);
out5.putInt(deviceId);
try {
serveur.sendData(out5, speakersAddr.get(spkId));
} catch (IOException e1) {
e1.printStackTrace();
}
}
listening.remove(deviceId);
publishAudioList(speakersAddr.values());
}else if(deviceType2==Codes.Device.SPEAKER_DEVICE) {
// On le déréférence
System.out.println("Déconnection de l'enceinte "+speakers.get(deviceId));
speakers.remove(deviceId);
speakersAddr.remove(deviceId);
changeListeners.forEach(Runnable::run);
int lstTo = listens(deviceId);
if(lstTo!=-1) {
// On enlève le lien d'écoute
listening.get(lstTo).remove((Integer)deviceId);
if(listening.get(lstTo).isEmpty()) {
// Si il n'y a plus rien à écouter.
try {
ByteBuffer out4 = ByteBuffer.allocate(Codes.packetMaxSize);
out4.put(Codes.STOP_STREAMING);
out4.putInt(lstTo);
serveur.sendData(out4, micsAddr.get(lstTo));
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}else {
System.err.println("Je ne sais pas comment réagir à la déconnection d'un appareil de type "+deviceType2);
}
break;
case Codes.PING:
ByteBuffer outPong = ByteBuffer.allocate(Codes.packetMaxSize);
outPong.put(Codes.PONG);
try {
serveur.sendData(out2, senderAddress);
} catch (IOException e1) {
e1.printStackTrace();
serveur.sendData(outPong, senderAddress);
} catch (IOException e) {
e.printStackTrace();
}
break;
default:
case Codes.ACCES_PARTIE:
// On délègue
gameInterface.receiveCommand(data, senderAddress);
break;
default:
System.out.println("Je ne devait pas recevoir cette commade !");
}
}
public void addChangeListener(Runnable listener) {
changeListeners.add(listener);
}
public void removeChangeListener(Runnable listener) {
changeListeners.remove(listener);
}
public Map<Integer, String> getMics() {
return Collections.unmodifiableMap(mics);
}
public Map<Integer, String> getSpeakers() {
return Collections.unmodifiableMap(speakers);
}
public int listens(int spk) {
for(int mic : listening.keySet()) {
if(listening.get(mic).contains(spk))
return mic;
}
return -1;
}
public void publishAudioList(Collection<SocketAddress> to) {
ByteBuffer out = ByteBuffer.allocate(Codes.packetMaxSize);
out.put(Codes.GIVE_AUDIO_LIST);
out.putInt(mics.size());
for(Entry<Integer, String> mic : mics.entrySet()) {
out.putInt(mic.getKey());
BytesUtils.writeString(out, mic.getValue());
}
for(SocketAddress addr : to) {
try {
serveur.sendData(out, addr);
} catch (Exception e) {
serverErrorException.forEach(c -> c.accept(e));
}
}
}
public int enceinteIdFromName(String name) {
for(int i:speakers.keySet()) {
if(name.equalsIgnoreCase(speakers.get(i)))
return i;
}
return -1;
}
public void forceSilence(int id) {
if(!speakers.containsKey(id))
return;
int lstTo = listens(id);
if(lstTo!=-1) {
// On enlève le lien d'écoute
listening.get(lstTo).remove(id);
// Si il n'y a plus rien à écouter.
ByteBuffer out4 = ByteBuffer.allocate(Codes.packetMaxSize);
out4.put(Codes.STOP_STREAMING);
out4.putInt(lstTo);
try {
serveur.sendData(out4, speakersAddr.get(id));
} catch (Exception e1) {
serverErrorException.forEach(c -> c.accept(e1));
}
}
}
public void disconnectEnceinte(int id) {
if(!speakers.containsKey(id))
return;
// On lui annonce notre déconnection, puis on la supprime des données
ByteBuffer decoBuf = ByteBuffer.allocate(Codes.packetMaxSize);
decoBuf.put(Codes.DISCONNECTING);
decoBuf.put(Codes.Device.MASTER_DEVICE);
decoBuf.putInt(0);
try {
serveur.sendData(decoBuf, speakersAddr.get(id));
} catch (Exception e) {
serverErrorException.forEach(c -> c.accept(e));
}
speakers.remove(id);
speakersAddr.remove(id);
changeListeners.forEach(Runnable::run);
int lstTo = listens(id);
if(lstTo!=-1) {
// On enlève le lien d'écoute
listening.get(lstTo).remove(id);
if(listening.get(lstTo).isEmpty()) {
// Si il n'y a plus rien à écouter.
ByteBuffer out4 = ByteBuffer.allocate(Codes.packetMaxSize);
out4.put(Codes.STOP_STREAMING);
out4.putInt(lstTo);
try {
serveur.sendData(out4, micsAddr.get(lstTo));
} catch (Exception e1) {
serverErrorException.forEach(c -> c.accept(e1));
}
}
}
}
public void disconnectMic(int id) {
if(!mics.containsKey(id))
return;
// On lui annonce notre déconnection, puis on la supprime des données
ByteBuffer decoBuf = ByteBuffer.allocate(Codes.packetMaxSize);
decoBuf.put(Codes.DISCONNECTING);
decoBuf.put(Codes.Device.MASTER_DEVICE);
decoBuf.putInt(0);
try {
serveur.sendData(decoBuf, micsAddr.get(id));
} catch (Exception e) {
serverErrorException.forEach(c -> c.accept(e));
}
mics.remove(id);
micsAddr.remove(id);
changeListeners.forEach(Runnable::run);
for(int lster : listening.get(id)) {
ByteBuffer out4 = ByteBuffer.allocate(Codes.packetMaxSize);
out4.put(Codes.STOP_STREAMING);
out4.putInt(id);
try {
serveur.sendData(out4, speakersAddr.get(lster));
} catch (Exception e1) {
serverErrorException.forEach(c -> c.accept(e1));
}
}
publishAudioList(listening.get(id).stream().map(speakersAddr::get).collect(Collectors.toSet()));
listening.remove(id);
}
public void dispose() {
speakers.keySet().forEach(this::disconnectEnceinte);
mics.keySet().forEach(this::disconnectMic);
mics.clear();
speakers.clear();
micsAddr.clear();
speakersAddr.clear();
listening.clear();
changeListeners.forEach(Runnable::run);
serveur.close();
}
public YamlMapping saveToYaml() {
YamlSequenceBuilder ymbMics = Yaml.createYamlSequenceBuilder();
for(int i : mics.keySet()) {
ymbMics = ymbMics.add(
Yaml.createYamlMappingBuilder()
.add("id", Integer.toString(i))
.add("name", mics.get(i))
.add("address", ParseUtils.toBase64(micsAddr.get(i)))
.add("listening", listening.get(i)
.stream()
.map(YamlUtils::scalar)
.collect(YamlUtils.sequenceCollector))
.build());
}
YamlSequenceBuilder ymbSpks = Yaml.createYamlSequenceBuilder();
for(int i : speakers.keySet()) {
ymbSpks = ymbSpks.add(
Yaml.createYamlMappingBuilder()
.add("id", Integer.toString(i))
.add("name", speakers.get(i))
.add("address", ParseUtils.toBase64(speakersAddr.get(i)))
.build());
}
return Yaml.createYamlMappingBuilder()
.add("micros", ymbMics.build())
.add("enceintes", ymbSpks.build())
.build();
}
public void genPasswords() { //XXX : Refaire ce système avec considération pour les OPés aussi ^^
for(Personnage perso : gameInterface.theManager.partie().personnages()) {
byte[] tokenBytes = new byte[6];
ThreadLocalRandom.current().nextBytes(tokenBytes);
String pass = Base64.getUrlEncoder().encodeToString(tokenBytes);
pass = "bb";
gameInterface.addPass(perso.getNom(),pass);
System.out.println("Mdp de "+perso.getNom()+": "+pass);
}
}
}

View File

@ -0,0 +1,101 @@
package com.bernard.murder.audio;
public class Codes {
public static class Device{
public static final byte MASTER_DEVICE = 0x42;
public static final byte SPEAKER_DEVICE = 0x01;
public static final byte MIC_DEVICE = 0x02;
}
public static int packetMaxSize = 97282;
public static int communicationPort = 35295;
// Format des paquets [commande 1, UUID identifier, byte deviceType, String name]
public static final byte DECLARE_NUMBER = 0x02;
// Format des paquets [commande 1, UUID identifier, byte deviceType, int id]
public static final byte OK_ID = 0x03;
// Format des paquets [commande 1]
public static final byte ASK_AUDIO_LIST= 0x04;
// Format des paquets [commande 1, int Count, {int id, String name}]
public static final byte GIVE_AUDIO_LIST = 0x05;
// Format des paquets: [commande 1, int listenId, int myId]
// -> Une enceinte demande à master le son d'un micro
public static final byte ASK_STREAMING = 0x06;
// Format des paquets: [commande 1, int id]
// -> Master demande à un micro d'envoyer du son
public static final byte START_STREAMING = 0x07;
// Format des paquets: [commande 1, int listenId, int myId]e.printStackTrace();
// -> Une enceinte demande à master de ne plus recevoir le son d'un micro
public static final byte ASK_STOP_STREAMING = 0x09;
// Format des paquets: [commande 1, int id]
// -> Master demande à un micro de ne plus émettre.
// -> Master annonce à une enceinte qu'il n'emmet plus le micro demandé.
public static final byte STOP_STREAMING = 0x08;
// Format des paquets [commande 1, int id, ~ data]
public static final byte AUDIO_STREAM = 0x01;
// Format des paquets [commande 1, byte deviceType, int deviceId]
// Un terminal indique qu'il se ferme à la connection
public static final byte DISCONNECTING = 0x0A;
// Envoie la commande au gamemanager afin que la commande accède à la partie.
public static final byte ACCES_PARTIE = 0x2A;
public static final byte PING = 0x3A;
public static final byte PONG = 0x3B;
public static class Partie {
// [ACCÈS_PARTIE, commandePartie 1, String opName, String mdp]
public static final byte ASK_OP_TOKEN = 0x71;
// [ACCÈS_PARTIE, commandePartie 1, String joueurName, String mdp]
public static final byte ASK_JOUEUR_TOKEN = 0x72;
// [ACCÈS_PARTIE, commandePartie 1, String token]
public static final byte GIVING_TOKEN = 0x73;
// [ACCÈS_PARTIE, commandePartie 1, String token]
public static final byte AUTH_ERROR = 0x75;
//Entête de tous le paquets: [ACCÈS_PARTIE, commandePartie 1]
// [] (pas besoin du token)
public static final byte ASK_PLAYER_LIST = 0x01;
// [int nombreDeJoueurs, nombreDeJoueurs*String joueursNames]
public static final byte GIVE_PLAYER_LIST = 0x02;
// [String token, String inventoryName]
public static final byte ASK_INVENTORY = 0x03;
// [String token, String inventoryName]
public static final byte ASK_INVENTORY_WATCH = 0x04;
// [String inventoryName, int objCount, objCount*String objNames]
public static final byte INVENTORY_CONTENT = 0x05;
// [String token, String persoName]
public static final byte ASK_ACTIONS = 0x06;
// [String token, String persoName]
public static final byte ASK_ACTIONS_WATCH = 0x07;
// [String persoName, int actionCount, (String actionName, long basetime, long triggertime)*actionCount]
public static final byte ACTIONS_STATUS = 0x08;
// [String token, UUID askCode, long startTimestamp]
public static final byte ASK_NEW_THREAD = 0x09;
// [UUID askCode, String persoName, long uid, long startTimestamp]
public static final byte CREATED_NEW_THREAD = 0x0A;
/// [String token, long uid, long closeTimestamp]
public static final byte CLOSE_NEW_THREAD = 0x0B;
// [long uid, long closeTimestamp]
public static final byte CLOSED_THREAD = 0x0C;
// [String token, long sendTimestamp, @Nullable long threadUid, String emmeteur, String texte]
public static final byte SEND_MESSAGE = 0x0D;
// [long sendTimestamp, @Nullable long threadUid, @Nullable int threadPosition, String emmeteur, String texte]
public static final byte NEW_MESSAGE = 0x0D;
// [String token, long threadUid, @Nullable String op]
public static final byte ASK_ASSIGNATION = 0x10;
// [String token, long threadUid, @Nulalble String newOp]
public static final byte CREATED_ASSIGNATION = 0x11;
}
}

View File

@ -5,12 +5,14 @@ import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import com.bernard.murder.BytesUtils;
import com.bernard.util.BytesUtils;
public class MicServer {
@ -34,11 +36,14 @@ public class MicServer {
Runnable serverAnswered;
Set<Runnable> disconnectListener;
public MicServer(SocketAddress adresse,String micName,TargetDataLine tdl) {
this.micName = micName;
this.masterAddress = adresse;
this.micLine = tdl;
this.disconnectListener = new HashSet<>();
try {
initServer();
initializeAudioId();
@ -51,22 +56,23 @@ public class MicServer {
public void initializeMicDevice() throws LineUnavailableException {
micLine.open(AudioServer.formatAudio);
packetLength = micLine.getBufferSize()/5;
System.out.println("Longueur du paquet: "+packetLength);
}
public void initServer() throws SocketException, UnknownHostException {
serveur = new Serveur(this::receiveCommand, AudioServer.communicationPort);
serveur = new Serveur(this::receiveCommand, Codes.communicationPort);
}
public void initializeAudioId() {
ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize);
ByteBuffer buffer = ByteBuffer.allocate(Codes.packetMaxSize);
askedUUID = UUID.randomUUID();
buffer.put(AudioServer.DECLARE_NUMBER);
buffer.put(Codes.DECLARE_NUMBER);
buffer.putLong(askedUUID.getMostSignificantBits());
buffer.putLong(askedUUID.getLeastSignificantBits());
buffer.put(AudioServer.MIC_DEVICE);
buffer.put(Codes.Device.MIC_DEVICE);
BytesUtils.writeString(buffer, micName);
@ -79,41 +85,55 @@ public class MicServer {
public void receiveCommand(ByteBuffer data) {
byte commande = data.get();
System.out.println("Commande reçue : "+commande);
switch (commande) {
case AudioServer.START_STREAMING:
int micId = data.getInt();
if(micId != this.micId)return;
case Codes.START_STREAMING:
int askedMicId = data.getInt();
if(askedMicId != this.micId)return;
shouldStream = true;
micLine.start();
launchDataStream();
break;
case AudioServer.STOP_STREAMING:
int micId2 = data.getInt();
if(micId2 != this.micId)return;
case Codes.STOP_STREAMING:
int askedMicId2 = data.getInt();
if(askedMicId2 != this.micId)return;
shouldStream = false;
micLine.stop();
break;
case AudioServer.OK_ID:
case Codes.OK_ID:
UUID uuid = new UUID(data.getLong(), data.getLong());
byte deviceType = data.get();
int deviceId = data.getInt();
if(!askedUUID.equals(uuid) || deviceType!=AudioServer.MIC_DEVICE)
if(!askedUUID.equals(uuid) || deviceType!=Codes.Device.MIC_DEVICE)
return;
micId = deviceId;
this.micId = deviceId;
serverAnswered.run();
new Thread(serverAnswered).start();
if(micLine==null)
if(!micLine.isOpen())
try {
initializeMicDevice();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
break;
case Codes.DISCONNECTING:
byte deviceType2 = data.get();
if(deviceType2==Codes.Device.MASTER_DEVICE) {
System.out.println("Le master s'est déconnécté, on fait de même !");
masterAddress=null;
this.dispose();
disconnectListener.forEach(Runnable::run);
}else {
System.out.println("Un appareil de type "+deviceType2+" s'est déconécté, mais je m'en fout ^^");
}
default:
System.out.println("Je ne devait pas recevoir cette commade !");
break;
}
}
@ -123,8 +143,10 @@ public class MicServer {
streamingThread = new Thread(()->{
byte[] packetData = new byte[1+4+packetLength];
ByteBuffer audioPacket = ByteBuffer.wrap(packetData);
audioPacket.put(AudioServer.AUDIO_STREAM);
audioPacket.put(Codes.AUDIO_STREAM);
audioPacket.putInt(micId);
audioPacket.position(audioPacket.position()+packetLength);
micLine.start();
while(shouldStream) {
micLine.read(packetData, 5, packetLength);
try {
@ -134,17 +156,38 @@ public class MicServer {
}
}
micLine.stop();
});
streamingThread.start();
}
public void dispose() {
if(masterAddress!=null) {
ByteBuffer decoPacket = ByteBuffer.allocate(Codes.packetMaxSize);
decoPacket.put(Codes.DISCONNECTING);
decoPacket.put(Codes.Device.MIC_DEVICE);
decoPacket.putInt(micId);
try {
serveur.sendData(decoPacket,masterAddress);
} catch (IOException e) {
e.printStackTrace();
}
}
shouldStream=false;
micLine.close();
serveur.dispose();
serveur.close();
}
public void setServerAnswered(Runnable serverAnswered) {
this.serverAnswered = serverAnswered;
}
public void addDisconnectListener(Runnable listener) {
disconnectListener.add(listener);
}
public void removeDisconnectListener(Runnable listener) {
disconnectListener.remove(listener);
}
}

View File

@ -14,7 +14,7 @@ import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.bernard.murder.ParseUtils;
import com.bernard.util.ParseUtils;
public class Serveur {
@ -28,19 +28,21 @@ public class Serveur {
volatile boolean isReceiving = false;
BiConsumer<ByteBuffer,SocketAddress> consumer;
boolean networkOnSeparatedThread = false;
BiConsumer<Object,SocketAddress> consumer; // Where object has type byte[]
public Serveur(Consumer<ByteBuffer> dataEater, int port) throws UnknownHostException, SocketException {
this((b,i)->dataEater.accept(b),new InetSocketAddress(port));
this((o,s) -> dataEater.accept(ByteBuffer.wrap((byte[])o)),new InetSocketAddress(port));
}
public Serveur(Consumer<ByteBuffer> dataEater,SocketAddress addresse) throws UnknownHostException, SocketException {
this((b,i)->dataEater.accept(b),addresse);
this((o,s) -> dataEater.accept(ByteBuffer.wrap((byte[])o)),addresse);
}
public Serveur(BiConsumer<ByteBuffer,SocketAddress> dataEater,int port) throws UnknownHostException, SocketException {
this(dataEater,new InetSocketAddress(port));
this((o,s) -> dataEater.accept(ByteBuffer.wrap((byte[])o),s),new InetSocketAddress(port));
}
public Serveur(BiConsumer<ByteBuffer,SocketAddress> dataEater,SocketAddress adresse) throws SocketException {
public Serveur(BiConsumer<Object,SocketAddress> dataEater,SocketAddress adresse) throws SocketException {
byteArrays = new HashMap<UUID, byte[]>();
receivedSlicesArray = new HashMap<UUID, boolean[]>();
@ -82,14 +84,14 @@ public class Serveur {
System.arraycopy(data, paquet.getOffset()+41, bigData, offset, paquet.getLength()-41);
recievedArray[sliceId] = true;
if(!ParseUtils.and(recievedArray)) {
ByteBuffer dataBuffer = ByteBuffer.wrap(bigData);
consumer.accept(dataBuffer,paquet.getSocketAddress());
consumer.accept(bigData,paquet.getSocketAddress());
byteArrays.remove(uuid);
receivedSlicesArray.remove(uuid);
}
}else {
ByteBuffer dataBuffer = ByteBuffer.wrap(data,paquet.getOffset(),paquet.getLength());
consumer.accept(dataBuffer,paquet.getSocketAddress());
byte[] realData = new byte[paquet.getLength()];
System.arraycopy(data, paquet.getOffset(), realData, 0, paquet.getLength());
consumer.accept(data,paquet.getSocketAddress());
}
} catch (IOException e) {
@ -98,22 +100,38 @@ public class Serveur {
}
}
});
},"Receveur de paquets");
isReceiving = true;
packetReceiver.start();
}
public void sendData(ByteBuffer buffer,SocketAddress address) throws IOException {
byte[] data = new byte[buffer.position()];
buffer.clear();
buffer.flip();
buffer.get(data);
sendData(data,address);
}
public void sendData(byte[] data, SocketAddress address) throws IOException {
if(data.length < packetMaxLength) {
DatagramPacket packet = new DatagramPacket(data, data.length,address);
socket.send(packet);
if(networkOnSeparatedThread) {
Thread subLauncher = new Thread(() -> {
try {
socket.send(packet);
} catch (IOException e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
});
subLauncher.start();
try {
subLauncher.join();
} catch (InterruptedException e) {}
}
else
socket.send(packet);
}else {
//XXX Ça, ca ne marche pas !
short packetCount = (short) (data.length / (packetMaxLength-42));
short packetLength = (short) (data.length / packetCount);
short lastPacketLength = (short) (data.length - (packetCount-1)*packetLength);
@ -149,10 +167,14 @@ public class Serveur {
}
}
public void dispose() {
public void close() {
isReceiving = false;
socket.close();
packetReceiver.interrupt();
}
public void setNetworkOnSeparatedThread(boolean networkOnSeparatedThread) {
this.networkOnSeparatedThread = networkOnSeparatedThread;
}
}

View File

@ -6,21 +6,22 @@ import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import com.bernard.murder.BytesUtils;
import com.bernard.util.BytesUtils;
public class SpeakerServer {
SourceDataLine speakerLine;
int packetLength = 9728;
int speakerId;
String speakerName;
@ -30,7 +31,6 @@ public class SpeakerServer {
Serveur serveur;
Map<Integer,String> mics;
volatile boolean isMicListUpToDate = false;
int listeningTo = -1;
@ -38,11 +38,21 @@ public class SpeakerServer {
Runnable serverAnswered;
Set<Runnable> disconnectListener;
Set<Runnable> brokenMicListener;
Set<Consumer<Map<Integer, String>>> micListUpdateListener;
public SpeakerServer(SocketAddress serveur,String speakerName,SourceDataLine speaker) {
this.speakerName = speakerName;
this.masterAddress = serveur;
this.speakerLine = speaker;
this.disconnectListener = new HashSet<>();
this.brokenMicListener = new HashSet<>();
this.micListUpdateListener = new HashSet<>();
try {
initServer();
initializeAudioId();
@ -52,24 +62,24 @@ public class SpeakerServer {
}
public void initServer() throws SocketException, UnknownHostException {
serveur = new Serveur(this::receiveCommand,AudioServer.communicationPort);
serveur = new Serveur(this::receiveCommand,Codes.communicationPort);
}
public void initializeSpeakerDevice() throws LineUnavailableException {
speakerLine.open(AudioServer.formatAudio);
packetLength = speakerLine.getBufferSize()/5;
speakerLine.start();
}
public void initializeAudioId() {
ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize);
ByteBuffer buffer = ByteBuffer.allocate(Codes.packetMaxSize);
askedUUID = UUID.randomUUID();
buffer.put(AudioServer.DECLARE_NUMBER);
buffer.put(Codes.DECLARE_NUMBER);
buffer.putLong(askedUUID.getMostSignificantBits());
buffer.putLong(askedUUID.getLeastSignificantBits());
buffer.put(AudioServer.SPEAKER_DEVICE);
buffer.put(Codes.Device.SPEAKER_DEVICE);
BytesUtils.writeString(buffer, speakerName);
@ -82,10 +92,15 @@ public class SpeakerServer {
}
public void askForStream(int micId) {
ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize);
if(listeningTo==micId)
return;
buffer.put(AudioServer.START_STREAMING);
ByteBuffer buffer = ByteBuffer.allocate(Codes.packetMaxSize);
buffer.put(Codes.ASK_STREAMING);
buffer.putInt(micId);
buffer.putInt(speakerId);
@ -97,96 +112,130 @@ public class SpeakerServer {
}
}
public void stopStreaming() {
ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize);
public void askStopStreaming() {
if(listeningTo==-1) {
System.out.println("J'arêtte d'écouter le vide");
return;
}
ByteBuffer buffer = ByteBuffer.allocate(Codes.packetMaxSize);
buffer.put(AudioServer.STOP_STREAMING);
buffer.put(Codes.ASK_STOP_STREAMING);
buffer.putInt(listeningTo);
buffer.putInt(speakerId);
try {
serveur.sendData(buffer,masterAddress);
listeningTo=-1;
} catch (IOException e) {
e.printStackTrace();
}
}
public void askAudioList() {
ByteBuffer buffer = ByteBuffer.allocate(AudioServer.packetMaxSize);
buffer.put(AudioServer.ASK_AUDIO_LIST);
ByteBuffer buffer = ByteBuffer.allocate(Codes.packetMaxSize);
buffer.put(Codes.ASK_AUDIO_LIST);
try {
serveur.sendData(buffer,masterAddress);
} catch (IOException e) {
e.printStackTrace();
}
}
public Map<Integer,String> getAudioList() {
return getAudioList(false);
}
public Map<Integer,String> getAudioList(boolean invalidate) {
isMicListUpToDate = !invalidate && isMicListUpToDate;
if(!isMicListUpToDate)askAudioList();
while(!isMicListUpToDate) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Voici les "+mics);
return mics;
}
public void receiveCommand(ByteBuffer data) {
byte commande = data.get();
System.out.println("Commande recue : "+commande);
switch (commande) {
case AudioServer.AUDIO_STREAM:
case Codes.AUDIO_STREAM:
int micId = data.getInt();
if(micId != listeningTo)return;
data.compact();
if(micId != listeningTo)break;
byte[] audioData=new byte[data.remaining()];
data.get(audioData);//XXX Check wether audio data starts at position and not at 0
speakerLine.write(audioData, 0, packetLength);
speakerLine.write(audioData, 0, audioData.length);
break;
case AudioServer.OK_ID:
case Codes.OK_ID:
UUID uuid = new UUID(data.getLong(), data.getLong());
byte deviceType = data.get();
int deviceId = data.getInt();
if(!askedUUID.equals(uuid) || deviceType!=AudioServer.SPEAKER_DEVICE)
if(!askedUUID.equals(uuid) || deviceType!=Codes.Device.SPEAKER_DEVICE)
return;
speakerId = deviceId;
serverAnswered.run();
if(speakerLine==null)
new Thread(serverAnswered).start();
if(!speakerLine.isOpen())
try {
initializeSpeakerDevice();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
askAudioList();
break;
case AudioServer.GIVE_AUDIO_LIST:
case Codes.GIVE_AUDIO_LIST:
int micCount = data.getInt();
mics = new HashMap<Integer, String>(micCount);
for(int i = 0; i<micCount;i++) {
int thisMicId = data.getInt();
mics.put(thisMicId,BytesUtils.readString(data));
}
isMicListUpToDate=true;
System.out.println("Audio list given: "+mics);
micListUpdateListener.forEach(c -> c.accept(mics));
break;
case Codes.DISCONNECTING:
byte deviceType2 = data.get();
int deviceId2 = data.getInt();
if(deviceType2==Codes.Device.MASTER_DEVICE) {
System.out.println("Le master s'est déconnécté, on fait de même !");
masterAddress=null;
this.dispose();
for(Runnable toRun : disconnectListener)
toRun.run();
}else if(deviceType2==Codes.Device.MIC_DEVICE){
if(listeningTo==deviceId2) {
System.out.println("Le micro que l'on écoutait s'est déconnécté.");
listeningTo=-1;
for(Runnable toRun : brokenMicListener)
toRun.run();
}
}else {
System.out.println("Un appareil de type "+deviceType2+" s'est déconécté, mais je m'en fout ^^");
}
break;
case Codes.STOP_STREAMING:
int deviceId3 = data.getInt();
if(listeningTo==deviceId3) {
System.out.println("Le micro que l'on écoutait arêtte d'émmetre.");
listeningTo=-1;
for(Runnable toRun : brokenMicListener)
toRun.run();
}
default:
System.out.println("Je ne devait pas recevoir cette commade !");
break;
}
}
public void dispose() {
speakerLine.close();
serveur.dispose();
if(masterAddress!=null) {
ByteBuffer decoPacket = ByteBuffer.allocate(Codes.packetMaxSize);
decoPacket.put(Codes.DISCONNECTING);
decoPacket.put(Codes.Device.SPEAKER_DEVICE);
decoPacket.putInt(speakerId);
try {
serveur.sendData(decoPacket,masterAddress);
} catch (IOException e) {
e.printStackTrace();
}
}
if(mics!=null)mics.clear();
if(speakerLine!=null) {
speakerLine.stop();
speakerLine.close();
}
serveur.close();
}
public void setServerAnswered(Runnable serverAnswered) {
@ -194,7 +243,26 @@ public class SpeakerServer {
}
public void addDisconnectListener(Runnable listener) {
disconnectListener.add(listener);
}
public void removeDisconnectListener(Runnable listener) {
disconnectListener.remove(listener);
}
public void addBrokenMicListener(Runnable listener) {
brokenMicListener.add(listener);
}
public void removeBrokenMicListener(Runnable listener) {
brokenMicListener.remove(listener);
}
public void addMicListUpdateListener(Consumer<Map<Integer, String>> listener) {
micListUpdateListener.add(listener);
}
public void removeMicListUpdateListener(Consumer<Map<Integer, String>> listener) {
micListUpdateListener.remove(listener);
}
}

View File

@ -1,15 +1,9 @@
package com.bernard.murder.game;
import static com.bernard.murder.ParseUtils.mappingKeys;
import static com.bernard.murder.ParseUtils.parseTimeLength;
import static com.bernard.murder.ParseUtils.sequenceStream;
import static com.bernard.murder.ParseUtils.watch;
import static com.bernard.murder.YamlUtils.getMapping;
import static com.bernard.murder.YamlUtils.isMapping;
import static com.bernard.murder.YamlUtils.getSequence;
import static com.bernard.murder.YamlUtils.isSequence;
import static com.bernard.util.ParseUtils.mappingStringKeys;
import static com.bernard.util.ParseUtils.parseTimeLength;
import static com.bernard.util.ParseUtils.sequenceStream;
import static com.bernard.util.ParseUtils.watch;
import java.io.File;
import java.io.FileWriter;
@ -32,13 +26,13 @@ import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.YamlPrinter;
import com.amihaiemil.eoyaml.YamlSequence;
import com.bernard.murder.ParseUtils;
import com.bernard.murder.model.Action;
import com.bernard.murder.model.Objet;
import com.bernard.murder.model.Partie;
import com.bernard.murder.model.Personnage;
import com.bernard.murder.model.Piece;
import com.bernard.murder.model.Status;
import com.bernard.util.ParseUtils;
public class GameCreator {
@ -48,13 +42,13 @@ public class GameCreator {
YamlInput input = Yaml.createYamlInput(toread);
YamlMapping globalMap = input.readYamlMapping();
YamlMapping yjoueurs = getMapping(globalMap.value("joueurs"));
YamlMapping yjoueurs = globalMap.yamlMapping("joueurs");
// Pour pouvoir créer les objets et les espaces personnels
Set<String> playerNames = mappingKeys(yjoueurs).stream().map(n -> n.toString()).collect(Collectors.toSet());
Set<String> playerNames = mappingStringKeys(yjoueurs).stream().map(n -> n.toString()).collect(Collectors.toSet());
YamlMapping yactions = getMapping(globalMap.value("actions"));
YamlMapping yactions = globalMap.yamlMapping("actions");
Set<Action> actions = yactions.keys()
.stream()
.map(n -> new Action(n.asScalar().value(), parseTimeLength(yactions.string(n))))
@ -62,35 +56,40 @@ public class GameCreator {
Map<String,Set<Action>> persActions = playerNames.stream()
.collect(Collectors.toMap(Function.identity(), s -> actions.stream().map(Action::clone).collect(Collectors.toSet())));
YamlSequence yinventory = getSequence(globalMap.value("inventaire"));
YamlSequence yinventory = globalMap.yamlSequence("inventaire");
Set<String> objets = StreamSupport.stream(Spliterators.spliteratorUnknownSize(yinventory.iterator(),Spliterator.ORDERED),false)
.map(n ->n.asScalar().value())
.collect(Collectors.toSet());
Map<String,Set<Objet>> persObjets = playerNames.stream()
.collect(Collectors.toMap(Function.identity(),p -> objets.stream().map(o ->new Objet(String.format(o,p))).collect(Collectors.toSet())));
YamlSequence ystatus = getSequence(globalMap.value("status"));
Map<String,Set<Objet>> persObjets =
playerNames.stream()
.collect(Collectors.toMap(
Function.identity(),
p -> objets.stream()
.map(o ->new Objet(String.format(o,p)))
.collect(Collectors.toSet())
));
YamlSequence ystatus = globalMap.yamlSequence("status");
Set<Status> status = sequenceStream(ystatus).map(n -> new Status(n.asScalar().value())).collect(Collectors.toSet());
YamlMapping yespaces = getMapping(globalMap.value("espaces"));
YamlMapping yespaces = globalMap.yamlMapping("espaces");
Map<String, Map<Objet, Integer>> objetsDansEspaces = yespaces.keys().stream().collect(Collectors.toMap(
n -> n.asScalar().value(),
n-> parseHiddenObjects(getSequence(yespaces.value(n)))
n-> parseHiddenObjects(yespaces.yamlSequence(n))
));
Set<Piece> espaceObjets = yespaces.keys().stream()
.map(n -> new Piece(n.asScalar().value(), objetsDansEspaces.get(n.asScalar().value())))
.collect(Collectors.toSet());
YamlMapping yespacesPersos = getMapping(globalMap.value("espacesPersos"));
YamlMapping yespacesPersos = globalMap.yamlMapping("espacesPersos");
Map<String,Set<Piece>> persespacesPersos = playerNames.stream().collect(Collectors.toMap(
Function.identity(),
p -> yespacesPersos.keys().stream()
.map(e -> new Piece(
String.format(e.asScalar().value(), p),
parseHiddenObjects(getSequence(yespacesPersos.value(e)),p)
parseHiddenObjects(yespacesPersos.yamlSequence(e),p)
))
.collect(Collectors.toSet())
));
@ -99,32 +98,33 @@ public class GameCreator {
for(YamlNode pn : yjoueurs.keys()) {
String pname = pn.asScalar().value();
persActions.get(pname).addAll(
getMapping(getMapping(yjoueurs.value(pn)).value("actions")).keys()
yjoueurs.yamlMapping(pn).yamlMapping("actions").keys()
.stream()
.map(n -> new Action(n.asScalar().value(), parseTimeLength(getMapping(getMapping(yjoueurs.value(pn)).value("actions")).string(n))))
.map(n -> new Action(n.asScalar().value(), parseTimeLength(yjoueurs.yamlMapping(pn).yamlMapping("actions").string(n))))
.collect(Collectors.toSet())
);
persObjets.get(pname).addAll(
StreamSupport.stream(Spliterators.spliteratorUnknownSize(getSequence(getMapping(yjoueurs.value(pn)).value("inventaire")).iterator(),Spliterator.ORDERED),false)
.map(n ->n.asScalar().value())
.map(o ->new Objet(o))
StreamSupport.stream(Spliterators.spliteratorUnknownSize(yjoueurs.yamlMapping(pn).yamlSequence("inventaire").iterator(),Spliterator.ORDERED),false)
.map(n -> n.asScalar().value())
.map(o -> new Objet(o))
.collect(Collectors.toSet())
);
if(isMapping(getMapping(yjoueurs.value(pn)).value("espacePerso")))
if(yjoueurs.yamlMapping(pn).value("espacePerso").type()==Node.MAPPING)
// Plusieurs espaces
getMapping(getMapping(yjoueurs.value(pn)).value("espacePerso")).keys().forEach(n ->
yjoueurs.yamlMapping(pn).yamlMapping("espacePerso").keys().forEach(n ->
persespacesPersos.get(pname)
.stream()
.filter(p -> p.getNom().contentEquals(n.asScalar().value()))
.findAny()
.orElseGet(() -> new Piece(n.asScalar().value()))
.insertObjects(parseHiddenObjects(getSequence(getMapping(getMapping(yjoueurs.value(pn)).value("espacePerso")).value(n))))
.insertObjects(parseHiddenObjects(yjoueurs.yamlMapping(pn).yamlMapping("espacePerso").yamlSequence(n)))
);
else
((persespacesPersos.get(pname).isEmpty())?
Stream.of(new Piece(String.format("Espace de %s",pname))):persespacesPersos.get(pname).stream())
.forEach(p -> p.insertObjects(parseHiddenObjects(getSequence(getMapping(yjoueurs.value(pn)).value("espacePerso")))));
.forEach(p -> p.insertObjects(parseHiddenObjects(yjoueurs.yamlMapping(pn).yamlSequence("espacePerso"))));
}
@ -137,8 +137,8 @@ public class GameCreator {
persActions.get(p),
status.stream().filter(st -> sequenceStream(ystatus)
.filter(n -> n instanceof YamlMapping)
.filter(n -> getMapping(n).string(st.getName())!=null)
.filter(n -> getMapping(getMapping(n).value(st.getName())).string("onStart").contentEquals("true"))
.filter(n -> n.asMapping().string(st.getName())!=null)
.filter(n -> (n.asMapping().yamlMapping(st.getName())).string("onStart").contentEquals("true"))
.findAny().isPresent())
.collect(Collectors.toSet()),
persespacesPersos.get(p)
@ -203,50 +203,50 @@ public class GameCreator {
YamlInput input = Yaml.createYamlInput(f);
YamlMapping globalMap = input.readYamlMapping();
YamlSequence ystatus = getSequence(globalMap.value("status"));
YamlSequence ystatus = globalMap.yamlSequence("status");
Set<Status> status = sequenceStream(ystatus).map(n -> new Status(n.asScalar().value())).collect(Collectors.toSet());
YamlMapping yespaces = getMapping(globalMap.value("pieces"));
YamlMapping yespaces = globalMap.yamlMapping("pieces");
Map<String, Map<Objet, Integer>> objetsDansEspaces = yespaces.keys().stream().collect(Collectors.toMap(
n -> watch(watch(n).asScalar().value()),
n-> parseHiddenObjects(getSequence(yespaces.value(n)))
n-> parseHiddenObjects(yespaces.yamlSequence(n))
));
Set<Piece> espaceObjets = yespaces.keys().stream()
.map(n -> new Piece(n.asScalar().value(), objetsDansEspaces.get(n.asScalar().value())))
.collect(Collectors.toSet());
YamlMapping yjoueurs = getMapping(globalMap.value("personnages"));
YamlMapping yjoueurs = globalMap.yamlMapping("personnages");
Set<Personnage> personnages = new HashSet<Personnage>();
for(YamlNode pn : yjoueurs.keys()) {
String pname = pn.asScalar().value();
Set<Action> actions = getMapping(getMapping(yjoueurs.value(pn)).value("actions")).keys()
Set<Action> actions = yjoueurs.yamlMapping(pn).yamlMapping("actions").keys()
.stream()
.map(n -> new Action(n.asScalar().value(), Long.parseLong(getMapping(getMapping(getMapping(yjoueurs.value(pn)).value("actions")).value(n)).string("basetime")),
Long.parseLong(getMapping(getMapping(getMapping(yjoueurs.value(pn)).value("actions")).value(n)).string("triggerTime"))))
.map(n -> new Action(n.asScalar().value(), Long.parseLong(yjoueurs.yamlMapping(pn).yamlMapping("actions").yamlMapping(n).string("basetime")),
Long.parseLong(yjoueurs.yamlMapping(pn).yamlMapping("actions").yamlMapping(n).string("triggerTime"))))
.collect(Collectors.toSet());
Set<Objet> inventaire =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(getSequence(getMapping(yjoueurs.value(pn)).value("inventaire")).iterator(),Spliterator.ORDERED),false)
StreamSupport.stream(Spliterators.spliteratorUnknownSize(yjoueurs.yamlMapping(pn).yamlSequence("inventaire").iterator(),Spliterator.ORDERED),false)
.map(n ->n.asScalar().value())
.map(o ->new Objet(o))
.collect(Collectors.toSet());
Set<Piece> espacesPerso = getMapping(getMapping(yjoueurs.value(pn)).value("espacePerso")).keys().stream().map(n ->
new Piece(n.asScalar().value(), parseHiddenObjects(getSequence(getMapping(getMapping(yjoueurs.value(pn)).value("espacePerso")).value(n))))).collect(Collectors.toSet());
Set<Piece> espacesPerso = yjoueurs.yamlMapping(pn).yamlMapping("espacePerso").keys().stream().map(n ->
new Piece(n.asScalar().value(), parseHiddenObjects(yjoueurs.yamlMapping(pn).yamlMapping("espacePerso").yamlSequence(n)))).collect(Collectors.toSet());
Set<Status> persoStatus = status.stream().filter(
s -> !isSequence(getMapping(yjoueurs.value(pn)).value("status"))?false:getSequence(getMapping(yjoueurs.value(pn)).value("status")).values().stream().anyMatch(n -> n.asScalar().value().equals(s.getName()))
s -> (yjoueurs.yamlMapping(pn).value("status").type()!=Node.SEQUENCE)?false:yjoueurs.yamlMapping(pn).yamlSequence("status").values().stream().anyMatch(n -> n.asScalar().value().equals(s.getName()))
).collect(Collectors.toSet());
personnages.add(new Personnage(pname, inventaire, actions, persoStatus, espacesPerso));
}
YamlMapping minelsMap = getMapping(globalMap.value("minels"));
YamlMapping minelsMap = globalMap.yamlMapping("minels");
return new QuicksavedPartie(new Partie(personnages, status, espaceObjets),minelsMap);

View File

@ -17,6 +17,7 @@ import javax.swing.Timer;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.bernard.murder.model.Action;
import com.bernard.murder.model.Inventaire;
import com.bernard.murder.model.Objet;
import com.bernard.murder.model.Partie;
@ -26,6 +27,7 @@ public class GameManager {
Partie partie;
Map<Inventaire,Set<Runnable>> inventoryUpdateListeners;
Map<Personnage,Set<Runnable>> actionsUpdateListeners;
long startTime;
@ -36,6 +38,7 @@ public class GameManager {
public GameManager(Partie partie) {
this.partie = partie;
this.inventoryUpdateListeners = new HashMap<Inventaire, Set<Runnable>>();
this.actionsUpdateListeners = new HashMap<Personnage, Set<Runnable>>();
this.minelsQuicksaver = () -> Yaml.createYamlMappingBuilder().build();
startTime = System.currentTimeMillis();
@ -57,6 +60,14 @@ public class GameManager {
for(Runnable r : inventoryUpdateListeners.get(inv))
r.run();
}
public void actionsUpdate(Action act) {
actionsUpdate(partie.personnagesStream().filter(p->p.getActions().contains(act)).findAny().get());
}
public void actionsUpdate(Personnage inv) {
if(!actionsUpdateListeners.containsKey(inv))return;
for(Runnable r : actionsUpdateListeners.get(inv))
r.run();
}
public void dumpCurrentState() {
System.out.println(partie);
@ -67,19 +78,24 @@ public class GameManager {
inventoryUpdateListeners.put(inv, new HashSet<Runnable>());
inventoryUpdateListeners.get(inv).add(runnable);
}
public void addActionsUpdateListener(Personnage perso, Runnable runnable) {
if(!actionsUpdateListeners.containsKey(perso))
actionsUpdateListeners.put(perso, new HashSet<Runnable>());
actionsUpdateListeners.get(perso).add(runnable);
}
public void quickSave() {
System.out.println("Quicksaving");
File toSave = new File(quickSaveFilename());
File tempOldSave = new File(quickSaveFilename()+".tmp");
if(toSave.exists())toSave.renameTo(tempOldSave);
try {
GameCreator.quickSave(toSave, partie,minelsQuicksaver.get());
System.out.println("Quicksaved");
if(tempOldSave.exists())tempOldSave.delete();
} catch (IOException e) {
e.printStackTrace();
System.err.println("La sauvegarde rapide n'a pas fonctionné. Elle est donc désactivée.");
quickSaver.stop();
}
}
@ -87,6 +103,12 @@ public class GameManager {
public String quickSaveFilename() {
return "murder-"+DateTimeFormatter.ofPattern("uu-MM-dd_HH'h'mm").withZone(ZoneId.systemDefault()).withLocale(Locale.getDefault()).format(Instant.ofEpochMilli(startTime))+".bernard.quickmurder";
}
public void removeQuickSaves() {
for(File f : (new File(".")).listFiles((f,n) -> n.startsWith("murder-") && n.endsWith(".bernard.quickmurder"))) {
f.delete();
}
}
public Set<Inventaire> getEveryInventaire() {
Set<Inventaire> inventaires = new HashSet<Inventaire>();
@ -108,6 +130,10 @@ public class GameManager {
return partie.personnagesStream().filter(p -> key.equalsIgnoreCase(p.getNom())).findAny().orElse(null);
}
public Personnage getPersoHavingAction(Action act) {
return partie.personnagesStream().filter(p -> p.getActions().contains(act)).findAny().orElse(null);
}
public void bindMinelQuicksaver(Supplier<YamlMapping> minelsQuicksaver) {
this.minelsQuicksaver = minelsQuicksaver;
}
@ -118,9 +144,43 @@ public class GameManager {
inventaires.addAll(partie.personnages());
partie.personnagesStream().forEach(p -> inventaires.addAll(p.espacePersos()));
return inventaires.stream().filter(i -> name.equalsIgnoreCase(i.getInventoryName())).findAny().orElseGet(()->{
System.out.println("JE n'ai pas trouvé l'inventaire "+name+" dans la liste "+inventaires.stream().map(Inventaire::getInventoryName).collect(Collectors.joining(",")));
System.err.println("JE n'ai pas trouvé l'inventaire "+name+" dans la liste "+inventaires.stream().map(Inventaire::getInventoryName).collect(Collectors.joining(",")));
return null;
});
}
public void createObjet(String newName, Inventaire inv) {
Objet o = new Objet(newName);
System.out.println("Création de l'objet "+o+" dans "+inv);
inv.addObjet(o);
inventoryUpdate(inv);
}
public void deleteObjet(Objet o, Inventaire inv) {
System.out.println("Destruction de l'objet "+o+" dans "+inv);
inv.removeObjet(o);
inventoryUpdate(inv);
}
public void renameObjet(Objet o, String newName, Inventaire inv) {
System.out.println("Renommage de l'objet "+o+" en "+newName+" dans "+inv);
Objet newObjet = new Objet(newName);
inv.removeObjet(o);
inv.addObjet(newObjet);
inventoryUpdate(inv);
}
public void launchAction(Action a) {
a.setTriggertime(System.currentTimeMillis());
actionsUpdate(a);
}
public void resetAction(Action a) {
a.setTriggertime(0);
actionsUpdate(a);
}
public Partie partie() {
return partie;
}
}

View File

@ -0,0 +1,356 @@
package com.bernard.murder.game;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import com.bernard.murder.audio.AudioServer;
import com.bernard.murder.audio.Codes;
import com.bernard.murder.model.Action;
import com.bernard.murder.model.Inventaire;
import com.bernard.murder.model.Objet;
import com.bernard.murder.model.Personnage;
import com.bernard.murder.model.messages.Message;
import com.bernard.murder.model.messages.Thread;
import com.bernard.util.BytesUtils;
public class GameNetworkInterface {
public GameManager theManager;
AudioServer theServer;
Map<String, String> joueursTokens; // <token, joueur name>
Map<String, String> opTokens; // <token, op name>
Map<String, String> passwords; // <Nom du joueur/op, Mdp>
Map<String,List<Thread>> messages; // <Nom du joueur, tous les threads>
Map<String, Set<SocketAddress>> messageListeners; // <Nom du joueur/op, ensemble des adresses à notifier>
Set<String> opNames;
long globalThreadUid = 0L;
public GameNetworkInterface(GameManager theManager, AudioServer theServer) {
this.theManager = theManager;
this.theServer = theServer;
joueursTokens = new HashMap<>();
opTokens = new HashMap<>();
passwords = new HashMap<>();
}
public synchronized void receiveCommand(ByteBuffer cmd,SocketAddress senderAddress) {
byte commandePartie = cmd.get();
System.out.println("Sous-commande: "+commandePartie);
String token;
switch(commandePartie) {
case Codes.Partie.ASK_OP_TOKEN:
case Codes.Partie.ASK_JOUEUR_TOKEN:
String name = BytesUtils.readString(cmd);
String mdp = BytesUtils.readString(cmd);
if(!passwords.get(name).equals(mdp)) {
// Pas autorisé !!!
authError(senderAddress, null);
return;
}
byte[] tokenBytes = new byte[72];
ThreadLocalRandom.current().nextBytes(tokenBytes);
String newToken = Base64.getUrlEncoder().encodeToString(tokenBytes);
if(commandePartie == Codes.Partie.ASK_OP_TOKEN) {
opTokens.put(newToken, name);
opNames.add(name);
}
if(commandePartie == Codes.Partie.ASK_JOUEUR_TOKEN)
joueursTokens.put(newToken, name);
System.out.println("Ajout d'un joli token !"+opTokens+";"+joueursTokens);
//XXX: On enregistre aussi la jolie adresse pour recevoir les messages, c'est cencé bouger
messageListeners.putIfAbsent(name, new HashSet<>());
messageListeners.get(name).add(senderAddress);
ByteBuffer outToken = ByteBuffer.allocate(Codes.packetMaxSize);
outToken.put(Codes.ACCES_PARTIE);
outToken.put(Codes.Partie.GIVING_TOKEN);
BytesUtils.writeString(outToken, newToken);
try {
theServer.serveur.sendData(outToken, senderAddress);
} catch (IOException e) {
e.printStackTrace();
}
break;
case Codes.Partie.ASK_PLAYER_LIST:
//Pas besoin du token pour cette requête
ByteBuffer outList = ByteBuffer.allocate(Codes.packetMaxSize);
outList.put(Codes.ACCES_PARTIE);
outList.put(Codes.Partie.GIVE_PLAYER_LIST);
Set<Personnage> persos = theManager.partie.personnages();
outList.putInt(persos.size());
for(Personnage perso:persos)
BytesUtils.writeString(outList, perso.getNom());
try {
theServer.serveur.sendData(outList, senderAddress);
} catch (IOException e) {
e.printStackTrace();
}
break;
case Codes.Partie.ASK_INVENTORY:
case Codes.Partie.ASK_INVENTORY_WATCH:
token = BytesUtils.readString(cmd);
String invName = BytesUtils.readString(cmd);
if(!opTokens.containsKey(token)) {
// Alors on teste le joueur.
String toktoktoken = token;
Personnage perso = theManager.partie.personnagesStream()
.filter(p -> p.getNom().equals(joueursTokens.get(toktoktoken)))
.findAny().orElse(null);
if(!(perso!=null && perso.getInventoryName().equals(invName))) {
authError(senderAddress, token);
return;
}
}
// Autorisééééé
sendInventoryContent(senderAddress,invName);
if(commandePartie==Codes.Partie.ASK_INVENTORY_WATCH) {
theManager.addInventoryUpdateListener(theManager.getInventoryByName(invName), () -> sendInventoryContent(senderAddress,invName));
}
break;
case Codes.Partie.ASK_ACTIONS:
case Codes.Partie.ASK_ACTIONS_WATCH:
token = BytesUtils.readString(cmd);
String persoName = BytesUtils.readString(cmd);
if(!opTokens.containsKey(token)) {
// Alors on teste le joueur.
String toktoken = token;
Personnage perso = theManager.partie.personnagesStream()
.filter(p -> p.getNom().equals(joueursTokens.get(toktoken)))
.findAny().orElse(null);
if(!(perso!=null && perso.getNom().equals(persoName))) {
authError(senderAddress, token);
return;
}
}
// Autorisééééé
sendActionsStatus(senderAddress,persoName);
if(commandePartie==Codes.Partie.ASK_INVENTORY_WATCH) {
theManager.addActionsUpdateListener(theManager.getPersoByName(persoName), () -> sendActionsStatus(senderAddress,persoName));
}
break;
case Codes.Partie.ASK_NEW_THREAD:
token = BytesUtils.readString(cmd);
UUID requestId = new UUID(cmd.getLong(), cmd.getLong());
long startTimestamp = cmd.getLong();
if(!joueursTokens.containsKey(token)) {
// t ki en fét ?
authError(senderAddress, token);
return;
}else
persoName = joueursTokens.get(token);
long newUid = globalThreadUid++;
Thread t = new Thread(persoName, startTimestamp, newUid);
messages.putIfAbsent(persoName, new ArrayList<>());
messages.get(persoName).add(t);
ByteBuffer outThr = ByteBuffer.allocate(Codes.packetMaxSize);
outThr.put(Codes.ACCES_PARTIE);
outThr.put(Codes.Partie.CREATED_NEW_THREAD);
outThr.putLong(requestId.getMostSignificantBits());
outThr.putLong(requestId.getLeastSignificantBits());
BytesUtils.writeString(outThr,persoName);
outThr.putLong(newUid);
outThr.putLong(startTimestamp);
Set<SocketAddress> dests = new HashSet<>();
dests.addAll(messageListeners.getOrDefault(persoName,Set.of())); // Tous les comptes du joueur
dests.add(senderAddress); // Celui qui a envoyé
dests.addAll(opAddresses()); // Et tous les opés
for(SocketAddress dest : dests){
try {
theServer.serveur.sendData(outThr, dest);
} catch (IOException e) {
e.printStackTrace();
}
}
break;
case Codes.Partie.CLOSE_NEW_THREAD:
token = BytesUtils.readString(cmd);
long uidToClose = cmd.getLong();
long closeTimestamp = cmd.getLong();
if(!joueursTokens.containsKey(token) && !opTokens.containsKey(token)) {
// t ki en fét ?
authError(senderAddress, token);
return;
}
Thread ttc = threadFromUid(uidToClose);
persoName = ttc.joueur;
ttc.closeTime = closeTimestamp;
ByteBuffer outThrC = ByteBuffer.allocate(Codes.packetMaxSize);
outThrC.put(Codes.ACCES_PARTIE);
outThrC.put(Codes.Partie.CLOSED_THREAD);
outThrC.putLong(uidToClose);
outThrC.putLong(closeTimestamp);
Set<SocketAddress> destsC = new HashSet<>();
destsC.addAll(messageListeners.getOrDefault(persoName,Set.of())); // Tous les comptes du joueur
destsC.add(senderAddress); // Celui qui a envoyé
destsC.addAll(opAddresses()); // Et tous les opés
for(SocketAddress dest : destsC){
try {
theServer.serveur.sendData(outThrC, dest);
} catch (IOException e) {
e.printStackTrace();
}
}
break;
case Codes.Partie.SEND_MESSAGE:
token = BytesUtils.readString(cmd);
long sendTimestamp = cmd.getLong();
long threadUid = cmd.getLong();
String emmeteur = BytesUtils.readString(cmd);
String message = BytesUtils.readString(cmd);
if(!(joueursTokens.containsKey(token) && joueursTokens.get(token).equals(emmeteur)) &&
!(opTokens.containsKey(token) && opTokens.get(token).equals(emmeteur))) {
// t ki en fét ?
authError(senderAddress, token);
return;
}
if(threadUid<0) {
System.err.println("Je ne sais pas encore traiter les messages hors thread, revenez plus tard");
return;
}
Thread tt = threadFromUid(threadUid);
Message mess = new Message(emmeteur, message, sendTimestamp);
int newPos = tt.messages.size();
tt.messages.add(mess);
ByteBuffer outMess = ByteBuffer.allocate(Codes.packetMaxSize);
outMess.put(Codes.ACCES_PARTIE);
outMess.put(Codes.Partie.NEW_MESSAGE);
outMess.putLong(sendTimestamp);
outMess.putLong(threadUid);
outMess.putInt(newPos);
BytesUtils.writeString(outMess,emmeteur);
BytesUtils.writeString(outMess,message);
Set<SocketAddress> destsM = new HashSet<>();
destsM.addAll(messageListeners.getOrDefault(tt.joueur,Set.of())); // Tous les comptes du joueur
destsM.add(senderAddress); // Celui qui a envoyé
destsM.addAll(opAddresses()); // Et tous les opés
for(SocketAddress dest : destsM){
try {
theServer.serveur.sendData(outMess, dest);
} catch (IOException e) {
e.printStackTrace();
}
}
break;
}
}
public void sendInventoryContent(SocketAddress destinataire, String inventoryName) {
Inventaire inv = theManager.getInventoryByName(inventoryName);
ByteBuffer outInv = ByteBuffer.allocate(Codes.packetMaxSize);
outInv.put(Codes.ACCES_PARTIE);
outInv.put(Codes.Partie.INVENTORY_CONTENT);
Set<Objet> objs = inv.getObjects();
outInv.putInt(objs.size());
for(Objet obj:objs)
BytesUtils.writeString(outInv, obj.getNom());
try {
theServer.serveur.sendData(outInv, destinataire);
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendActionsStatus(SocketAddress destinataire, String persoName) {
System.out.println("Sending actions of "+persoName+ " to "+destinataire);
Personnage perso = theManager.getPersoByName(persoName);
ByteBuffer outPerso = ByteBuffer.allocate(Codes.packetMaxSize);
outPerso.put(Codes.ACCES_PARTIE);
outPerso.put(Codes.Partie.ACTIONS_STATUS);
BytesUtils.writeString(outPerso, persoName);
Set<Action> actions = perso.getActions();
outPerso.putInt(actions.size());
for(Action act:actions) {
BytesUtils.writeString(outPerso, act.getName());
outPerso.putLong(act.getBasetime());
outPerso.putLong(act.getTriggertime());
}
try {
theServer.serveur.sendData(outPerso, destinataire);
} catch (IOException e) {
e.printStackTrace();
}
}
public void authError(SocketAddress responce, String token) {
System.out.println("Refus du token "+token);
ByteBuffer out = ByteBuffer.allocate(Codes.packetMaxSize);
out.put(Codes.ACCES_PARTIE);
out.put(Codes.Partie.AUTH_ERROR);
if(token!=null)
BytesUtils.writeString(out, token);
else
BytesUtils.writeString(out, "");
try {
theServer.serveur.sendData(out, responce);
} catch (IOException e) {
e.printStackTrace();
}
}
public Set<SocketAddress> opAddresses(){
return opNames.stream()
.map(messageListeners::get)
.reduce(new HashSet<>(), (a,b) -> {a.addAll(b);return a;});
}
public void addPass(String nom, String pass) {
passwords.put(nom, pass);
}
public Thread threadFromUid(long theUid) {
return messages.values().stream()
.map(l -> l.stream().filter(t -> t.uid==theUid).findAny())
.filter(Optional::isPresent)
.findAny()
.get()
.get()// Vérifié
;
}
}

View File

@ -39,10 +39,6 @@ public class Action implements Cloneable{
public boolean canBeLaunched() {
return System.currentTimeMillis()-triggertime-basetime>0;
}
public void launch() {
triggertime=System.currentTimeMillis();
}
public boolean hasFinished() {
return triggertime + basetime - System.currentTimeMillis()<0;
@ -64,6 +60,10 @@ public class Action implements Cloneable{
return triggertime;
}
public void setTriggertime(long triggertime) {
this.triggertime = triggertime;
}
}

View File

@ -3,7 +3,7 @@ package com.bernard.murder.model;
import java.util.Set;
import java.util.stream.Stream;
public class Personnage implements Inventaire{
public class Personnage implements Inventaire {
String nom;
Set<Objet> inventaire;
@ -61,9 +61,7 @@ public class Personnage implements Inventaire{
@Override
public void removeObjet(Objet o) {
System.out.println("Avant :"+inventaire);
this.inventaire.remove(o);
System.out.println("Après :"+inventaire);
}
@ -84,7 +82,11 @@ public class Personnage implements Inventaire{
@Override
public String getInventoryName() {
return "Inventaire de "+getNom();
return getInventoryNameFromPersoName(getNom());
}
public static final String getInventoryNameFromPersoName(String persoName) {
return "Inventaire de "+persoName;
}

View File

@ -0,0 +1,17 @@
package com.bernard.murder.model.messages;
public class Message {
public String emmeteur;
public String texte;
public long sendTimestamp;
public Message(String emmeteur, String texte, long sendTimestamp) {
this.emmeteur = emmeteur;
this.texte = texte;
this.sendTimestamp = sendTimestamp;
}
}

View File

@ -0,0 +1,24 @@
package com.bernard.murder.model.messages;
import java.util.ArrayList;
import java.util.List;
public class Thread {
public String joueur;
public long openTimestamp;
public String opAssigne;
public List<Message> messages;
public long closeTime = -1;
public long uid;
public Thread(String joueur, long openTimestamp, long uid) {
this.joueur = joueur;
this.openTimestamp = openTimestamp;
this.messages = new ArrayList<>();
this.uid = uid;
}
}

View File

@ -0,0 +1,16 @@
package com.bernard.murder.server;
public class Codes {
// NETWORKING
// [commande 1, UUID requestId]
public static final byte PING = 0x00;
public static final byte PONG = 0x01;
// [commande 1]
public static final byte ASK_PUBKEY = 0x02;
// [commande 1, byteCount int, byte[] key]
public static final byte GIVE_PUBKEY = 0x03;
}

View File

@ -0,0 +1,12 @@
package com.bernard.murder.server;
import com.bernard.murder.audio.Serveur;
public class CommandedServer {
Serveur serveur;
}

View File

@ -0,0 +1,48 @@
package com.bernard.murder.server;
import java.net.SocketAddress;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import com.bernard.murder.Parametres;
public class Device {
DeviceType type;
String nom;
PublicKey cle;
SocketAddress addresse;
public Device(DeviceType type, String nom, PublicKey cle, SocketAddress addresse) {
super();
this.type = type;
this.nom = nom;
this.cle = cle;
this.addresse = addresse;
}
public byte[] decryptReceivedMessage(byte[] message) {
try {
Cipher decodeur;
decodeur = Cipher.getInstance(Parametres.instance.encryptionAlg);
decodeur.init(Cipher.DECRYPT_MODE, this.cle);
return decodeur.doFinal(message);
} catch (NoSuchAlgorithmException |
NoSuchPaddingException |
InvalidKeyException |
IllegalBlockSizeException |
BadPaddingException e) {
throw new IllegalStateException("Impossible d'initialiser le décodeur pour l'appareil à l'adresse "+addresse.toString(),e);
}
}
}

View File

@ -0,0 +1,31 @@
package com.bernard.murder.server;
import java.util.Arrays;
public enum DeviceType {
TerminalMaitre(0x00),
TerminalEsclave(0x01),
Microphone(0x10),
Enceinte(0x11),
JoueurPhone(0x20),
OpPhone(0x21);
private byte code;
private DeviceType(byte code) {
this.code = code;
}
private DeviceType(int code) {
this((byte)code);
}
public byte getCode() {
return code;
}
public static DeviceType of(byte code) {
return Arrays.stream(DeviceType.values()).filter(d -> d.getCode()==code).findAny().orElse(null);
}
}

View File

@ -0,0 +1,96 @@
package com.bernard.murder.server;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import com.bernard.murder.Parametres;
import com.bernard.murder.audio.Serveur;
public class ServeurMaitre {
private Serveur serveur;
private PrivateKey masterPrivateKey;
private Cipher encodeur;
private Map<SocketAddress,Device> devices;
public ServeurMaitre(String name, int port) throws UnknownHostException, SocketException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
serveur = new Serveur((BiConsumer<Object,SocketAddress>)(this::globalCommandReceiver), new InetSocketAddress(port));
SecureRandom srand = new SecureRandom();
KeyPairGenerator kpg = KeyPairGenerator.getInstance(Parametres.instance.encryptionAlg);
kpg.initialize(Parametres.instance.encryptionKeySize, srand);
KeyPair kpair = kpg.generateKeyPair();
this.masterPrivateKey = kpair.getPrivate();
// Renvoie une erreur si le cipher n'existe pas.
this.encodeur = Cipher.getInstance(Parametres.instance.encryptionAlg);
this.encodeur.init(Cipher.ENCRYPT_MODE, masterPrivateKey);
Device thisDevice = new Device(DeviceType.TerminalMaitre, name, kpair.getPublic(), serveur.getAddress());
devices = new HashMap<>();
devices.put(serveur.getAddress(), thisDevice);
}
/*
*
* Toutes les commandes sont encryptées avec la clé de l'envoyeur, sauf
* les commandes de ping et récupération de la clé publique du maître
*
*/
public void globalCommandReceiver(Object untypedData, SocketAddress sender) {
byte[] data = (byte[]) untypedData;
if(devices.containsKey(sender)) {
// Alors on déchiffre avant de faire avaler.
Device sendingDevice = devices.get(sender);
byte[] realMessage = sendingDevice.decryptReceivedMessage(data);
uncryptedCommandReceiver(ByteBuffer.wrap(realMessage), sendingDevice);
}else {
// La commande ne dois pas être encryptée
ByteBuffer donnees = ByteBuffer.wrap(data);
byte commande = donnees.get();
switch(commande) {
case Codes.PING:
case Codes.PONG:
case Codes.ASK_PUBKEY:
//case Codes.GIVE_PUBKEY: Inutile pour le master, les clés sont données à l'enregistrement.
default:
System.err.println("J'ai reçu une commande qui devrait être encryptée !!!");
}
}
}
private void uncryptedCommandReceiver(ByteBuffer data, Device sender) {
}
public void audioCommandReceiver(ByteBuffer data, Device device) {
}
}

View File

@ -36,7 +36,6 @@ public class MouseReactiveTabbedPane extends JTabbedPane {
@Override
public void dragOver(DropTargetDragEvent dtde) {
int tab = getTab(dtde);
System.out.println(">"+tab+"/"+hoverIndex+"-"+enteredTime);
if(tab==-1) {
enteredTime=-1;
hoverIndex=-1;

View File

@ -0,0 +1,315 @@
package com.bernard.murder.util.view;
import java.awt.*;
import javax.swing.*;
//Honteusement volé de https://tips4java.wordpress.com/2009/12/20/scrollable-panel/
/**
* A panel that implements the Scrollable interface. This class allows you to
* customize the scrollable features by using newly provided setter methods so
* you don't have to extend this class every time.
*
* Scrollable amounts can be specifed as a percentage of the viewport size or as
* an actual pixel value. The amount can be changed for both unit and block
* scrolling for both horizontal and vertical scrollbars.
*
* The Scrollable interface only provides a boolean value for determining
* whether or not the viewport size (width or height) should be used by the
* scrollpane when determining if scrollbars should be made visible. This class
* supports the concept of dynamically changing this value based on the size of
* the viewport. In this case the viewport size will only be used when it is
* larger than the panels size. This has the effect of ensuring the viewport is
* always full as components added to the panel will be size to fill the area
* available, based on the rules of the applicable layout manager of course.
*/
public class ScrollablePanel extends JPanel implements Scrollable, SwingConstants {
private static final long serialVersionUID = -6250434641501235247L;
public enum ScrollableSizeHint {
NONE, FIT, STRETCH;
}
public enum IncrementType {
PERCENT, PIXELS;
}
private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE;
private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE;
private IncrementInfo horizontalBlock;
private IncrementInfo horizontalUnit;
private IncrementInfo verticalBlock;
private IncrementInfo verticalUnit;
/**
* Default constructor that uses a FlowLayout
*/
public ScrollablePanel() {
this(new FlowLayout());
}
/**
* Constuctor for specifying the LayoutManager of the panel.
*
* @param layout the LayoutManger for the panel
*/
public ScrollablePanel(LayoutManager layout) {
super(layout);
IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100);
IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10);
setScrollableBlockIncrement(HORIZONTAL, block);
setScrollableBlockIncrement(VERTICAL, block);
setScrollableUnitIncrement(HORIZONTAL, unit);
setScrollableUnitIncrement(VERTICAL, unit);
}
/**
* Get the height ScrollableSizeHint enum
*
* @return the ScrollableSizeHint enum for the height
*/
public ScrollableSizeHint getScrollableHeight() {
return scrollableHeight;
}
/**
* Set the ScrollableSizeHint enum for the height. The enum is used to determine
* the boolean value that is returned by the getScrollableTracksViewportHeight()
* method. The valid values are:
*
* ScrollableSizeHint.NONE - return "false", which causes the height of the
* panel to be used when laying out the children ScrollableSizeHint.FIT - return
* "true", which causes the height of the viewport to be used when laying out
* the children ScrollableSizeHint.STRETCH - return "true" when the viewport
* height is greater than the height of the panel, "false" otherwise.
*
* @param scrollableHeight as represented by the ScrollableSizeHint enum.
*/
public void setScrollableHeight(ScrollableSizeHint scrollableHeight) {
this.scrollableHeight = scrollableHeight;
revalidate();
}
/**
* Get the width ScrollableSizeHint enum
*
* @return the ScrollableSizeHint enum for the width
*/
public ScrollableSizeHint getScrollableWidth() {
return scrollableWidth;
}
/**
* Set the ScrollableSizeHint enum for the width. The enum is used to determine
* the boolean value that is returned by the getScrollableTracksViewportWidth()
* method. The valid values are:
*
* ScrollableSizeHint.NONE - return "false", which causes the width of the panel
* to be used when laying out the children ScrollableSizeHint.FIT - return
* "true", which causes the width of the viewport to be used when laying out the
* children ScrollableSizeHint.STRETCH - return "true" when the viewport width
* is greater than the width of the panel, "false" otherwise.
*
* @param scrollableWidth as represented by the ScrollableSizeHint enum.
*/
public void setScrollableWidth(ScrollableSizeHint scrollableWidth) {
this.scrollableWidth = scrollableWidth;
revalidate();
}
/**
* Get the block IncrementInfo for the specified orientation
*
* @return the block IncrementInfo for the specified orientation
*/
public IncrementInfo getScrollableBlockIncrement(int orientation) {
return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock;
}
/**
* Specify the information needed to do block scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @paran type specify how the amount parameter in the calculation of the
* scrollable amount. Valid values are: IncrementType.PERCENT - treat the
* amount as a % of the viewport size IncrementType.PIXEL - treat the
* amount as the scrollable amount
* @param amount a value used with the IncrementType to determine the scrollable
* amount
*/
public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount) {
IncrementInfo info = new IncrementInfo(type, amount);
setScrollableBlockIncrement(orientation, info);
}
/**
* Specify the information needed to do block scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @param info An IncrementInfo object containing information of how to
* calculate the scrollable amount.
*/
public void setScrollableBlockIncrement(int orientation, IncrementInfo info) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
horizontalBlock = info;
break;
case SwingConstants.VERTICAL:
verticalBlock = info;
break;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
/**
* Get the unit IncrementInfo for the specified orientation
*
* @return the unit IncrementInfo for the specified orientation
*/
public IncrementInfo getScrollableUnitIncrement(int orientation) {
return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit;
}
/**
* Specify the information needed to do unit scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @paran type specify how the amount parameter in the calculation of the
* scrollable amount. Valid values are: IncrementType.PERCENT - treat the
* amount as a % of the viewport size IncrementType.PIXEL - treat the
* amount as the scrollable amount
* @param amount a value used with the IncrementType to determine the scrollable
* amount
*/
public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount) {
IncrementInfo info = new IncrementInfo(type, amount);
setScrollableUnitIncrement(orientation, info);
}
/**
* Specify the information needed to do unit scrolling.
*
* @param orientation specify the scrolling orientation. Must be either:
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
* @param info An IncrementInfo object containing information of how to
* calculate the scrollable amount.
*/
public void setScrollableUnitIncrement(int orientation, IncrementInfo info) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
horizontalUnit = info;
break;
case SwingConstants.VERTICAL:
verticalUnit = info;
break;
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
// Implement Scrollable interface
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(Rectangle visible, int orientation, int direction) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
return getScrollableIncrement(horizontalUnit, visible.width);
case SwingConstants.VERTICAL:
return getScrollableIncrement(verticalUnit, visible.height);
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
@Override
public int getScrollableBlockIncrement(Rectangle visible, int orientation, int direction) {
switch (orientation) {
case SwingConstants.HORIZONTAL:
return getScrollableIncrement(horizontalBlock, visible.width);
case SwingConstants.VERTICAL:
return getScrollableIncrement(verticalBlock, visible.height);
default:
throw new IllegalArgumentException("Invalid orientation: " + orientation);
}
}
protected int getScrollableIncrement(IncrementInfo info, int distance) {
if (info.getIncrement() == IncrementType.PIXELS)
return info.getAmount();
else
return distance * info.getAmount() / 100;
}
@Override
public boolean getScrollableTracksViewportWidth() {
if (scrollableWidth == ScrollableSizeHint.NONE)
return false;
if (scrollableWidth == ScrollableSizeHint.FIT)
return true;
// STRETCH sizing, use the greater of the panel or viewport width
if (getParent() instanceof JViewport) {
return (((JViewport) getParent()).getWidth() > getPreferredSize().width);
}
return false;
}
@Override
public boolean getScrollableTracksViewportHeight() {
if (scrollableHeight == ScrollableSizeHint.NONE)
return false;
if (scrollableHeight == ScrollableSizeHint.FIT)
return true;
// STRETCH sizing, use the greater of the panel or viewport height
if (getParent() instanceof JViewport) {
return (((JViewport) getParent()).getHeight() > getPreferredSize().height);
}
return false;
}
/**
* Helper class to hold the information required to calculate the scroll amount.
*/
static class IncrementInfo {
private IncrementType type;
private int amount;
public IncrementInfo(IncrementType type, int amount) {
this.type = type;
this.amount = amount;
}
public IncrementType getIncrement() {
return type;
}
public int getAmount() {
return amount;
}
@Override
public String toString() {
return "ScrollablePanel[" + type + ", " + amount + "]";
}
}
}

View File

@ -1,7 +1,9 @@
package com.bernard.murder.view;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
@ -13,13 +15,20 @@ import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Mixer.Info;
import javax.sound.sampled.SourceDataLine;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.bernard.murder.audio.AudioServer;
import com.bernard.murder.audio.Codes;
import com.bernard.murder.audio.SpeakerServer;
public class EnceinteServeurFrame extends JFrame{
@ -28,35 +37,34 @@ public class EnceinteServeurFrame extends JFrame{
SpeakerServer serveur;
String deviceName;
NamedMicrophone[] micarray;
public EnceinteServeurFrame(String deviceName) {
this.setSize(300, 500);
this.setMinimumSize(new Dimension(100, 200));
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setTitle("Serveur audio");
this.setTitle("Serveur audio: Enceinte");
this.deviceName = deviceName;
this.setContentPane(genContentPan());
this.setVisible(true);
}
public JPanel genContentPan() {
public Container genContentPan() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(new EmptyBorder(3, 3, 3, 3));
InformedSourceDataline[] marray = getEnceinteList();
JList<InformedSourceDataline> enceinteListe = new JList<InformedSourceDataline>(marray);
enceinteListe.setOpaque(false);
JPanel masterPanel = new JPanel(new BorderLayout());
JTextField masterIP = new JTextField("192.168.1.1");
JTextField masterIP = new JTextField("192.168.1.1",15);
JButton serverControl = new JButton("Lancer");
masterPanel.add(serverControl,BorderLayout.EAST);
masterPanel.add(masterIP,BorderLayout.CENTER);
JList<NamedMicrophone> mics = new JList<>();
mics.setOpaque(false);
JButton silenceButton = new JButton("Silence");
serverControl.addActionListener(e->{
if(enceinteListe.getSelectedValue()==null)return;
@ -64,24 +72,77 @@ public class EnceinteServeurFrame extends JFrame{
if(serveur!=null) {
serveur.dispose();
serveur = null;
masterIP.setEnabled(true);
mics.setModel(new DefaultListModel<>());
serverControl.setText("Lancer");
}else {
serveur = new SpeakerServer(new InetSocketAddress(masterIP.getText(), AudioServer.communicationPort), deviceName,enceinteListe.getSelectedValue().tdl);
serveur = new SpeakerServer(new InetSocketAddress(masterIP.getText(), Codes.communicationPort), deviceName,enceinteListe.getSelectedValue().tdl);
masterIP.setEnabled(false);
serveur.setServerAnswered(()->{
serverControl.setText("Arrêter");
List<NamedMicrophone> list = serveur.getAudioList().entrySet().stream().map(et -> new NamedMicrophone(et.getKey(), et.getValue())).collect(Collectors.toList());
NamedMicrophone[] micarray = new NamedMicrophone[list.size()];
});
serveur.addMicListUpdateListener(m -> {
List<NamedMicrophone> list = m.entrySet().stream().map(et -> new NamedMicrophone(et.getKey(), et.getValue())).collect(Collectors.toList());
micarray = new NamedMicrophone[list.size()];
list.toArray(micarray);
mics.setListData(micarray);
System.out.println("Micros chargés: "+list);
});
serveur.addDisconnectListener(()->{
// Le dispose a été fait
serveur = null;
masterIP.setEnabled(true);
serverControl.setText("Lancer");
});
serveur.addBrokenMicListener(()->{
//XXX Test si ca fonctionne, si ca déclenche bien le listener.
mics.clearSelection();
serveur.askStopStreaming();
});
serverControl.setText("Lancement");
}
serverControl.setEnabled(true);
});
panel.add(masterPanel,BorderLayout.NORTH);
panel.add(mics,BorderLayout.SOUTH);
panel.add(enceinteListe,BorderLayout.CENTER);
mics.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mics.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
int i = e.getFirstIndex();
boolean wantToListen=mics.isSelectedIndex(i);
if(wantToListen && serveur != null)
serveur.askForStream(micarray[i].micId);
}
});
silenceButton.addActionListener(e -> {
mics.clearSelection();
if(serveur!=null)
serveur.askStopStreaming();
});
JScrollPane jE = new JScrollPane(enceinteListe);
jE.setBorder(BorderFactory.createTitledBorder("Enceintes disponibles"));
JScrollPane jM = new JScrollPane(mics);
jM.setBorder(BorderFactory.createTitledBorder("Microphones à écouter"));
JPanel headP = new JPanel(new BorderLayout());
headP.setBorder(new EmptyBorder(3, 3, 5, 3));
JPanel centerP = new JPanel(new GridLayout(2, 1));
headP.add(serverControl,BorderLayout.EAST);
headP.add(masterIP,BorderLayout.CENTER);
centerP.add(jE);
centerP.add(jM);
panel.add(headP,BorderLayout.NORTH);
panel.add(centerP,BorderLayout.CENTER);
panel.add(silenceButton,BorderLayout.SOUTH);
return panel;
}

View File

@ -65,7 +65,6 @@ public class HoverSelect implements DropTargetListener,Runnable{
@Override
public void dragEnter(DropTargetDragEvent e) {
enteredTime = System.nanoTime();
System.out.println("entré");
plannedThread = new Thread(()-> {
try {
Thread.sleep(waitTime/1_000_000, (int)(waitTime%1_000_000));
@ -91,7 +90,6 @@ public class HoverSelect implements DropTargetListener,Runnable{
@Override
public void run() {
enteredTime = System.nanoTime();
System.out.println("entré");
plannedThread = new Thread(()-> {
try {
Thread.sleep(waitTime/1_000_000, (int)(waitTime%1_000_000));

View File

@ -2,6 +2,8 @@ package com.bernard.murder.view;
import java.awt.GridLayout;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.List;
@ -15,25 +17,80 @@ import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.YamlPrinter;
import com.bernard.configurator.Configurator;
import com.bernard.murder.Parametres;
import com.bernard.murder.game.GameCreator;
import com.bernard.murder.game.GameCreator.QuicksavedPartie;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Partie;
import com.bernard.murder.model.Personnage;
import com.bernard.murder.view.minel.Minel;
import com.formdev.flatlaf.FlatDarculaLaf;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatIntelliJLaf;
import com.formdev.flatlaf.FlatLightLaf;
public class LauncherFrame extends JFrame{
private static final long serialVersionUID = 5831232688024137883L;
public static final String paramPath = "./murderator-config.yml";
public static void main2(String[] args) throws IOException {
File test = new File("/tmp/test.yml");
YamlPrinter yp = Yaml.createYamlPrinter(new FileWriter(test));
YamlNode theNode = Configurator.objectToNode(new Parametres());
System.out.println(theNode);
yp.print(theNode);
System.out.println("--------------------------------------------");
System.out.println("On tente de relire ...");
Parametres params = Configurator.readYaml(theNode, Parametres.class);
System.out.println(params);
System.out.println("Terminé !");
}
public static void main(String[] args) {
try {
File paramFile = new File(LauncherFrame.paramPath);
if(!paramFile.exists()) {
try {
YamlPrinter yp = Yaml.createYamlPrinter(new FileWriter(paramFile));
yp.print(Configurator.objectToNode(new Parametres()));
} catch (IOException e) {
e.printStackTrace();
}
}
YamlNode theConf = Yaml.createYamlInput(paramFile).readYamlMapping();
Parametres.instance = (Parametres)Configurator.readYaml(theConf, Parametres.class);
} catch (Exception e) {
System.err.println("Impossible de lire les paramètres, on tombe sur les défauts.");
e.printStackTrace();
Parametres.instance = new Parametres();
}
new LauncherFrame();
}
public LauncherFrame() {
try {
//TODO implement flatlaf look&feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
if(Parametres.instance.lookAndFeel==null)
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
else
switch(Parametres.instance.lookAndFeel) {
case "flatlaf-light":
FlatLightLaf.setup();break;
case "flatlaf-dark":
FlatDarkLaf.setup();break;
case "flatlaf-intelliJ":
FlatIntelliJLaf.setup();break;
case "flatlaf-darcula":
FlatDarculaLaf.setup();break;
default:
UIManager.setLookAndFeel(Parametres.instance.lookAndFeel);
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e1) {
e1.printStackTrace();
}

View File

@ -12,13 +12,16 @@ import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Mixer.Info;
import javax.sound.sampled.TargetDataLine;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import com.bernard.murder.audio.AudioServer;
import com.bernard.murder.audio.Codes;
import com.bernard.murder.audio.MicServer;
public class MicServeurFrame extends JFrame{
@ -29,11 +32,11 @@ public class MicServeurFrame extends JFrame{
String deviceName;
public MicServeurFrame(String deviceName) {
this.setSize(300, 500);
this.setSize(800, 300);
this.setMinimumSize(new Dimension(100, 200));
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setTitle("Serveur audio");
this.setTitle("Serveur audio: Microphone");
this.deviceName = deviceName;
this.setContentPane(genContentPan());
@ -41,13 +44,15 @@ public class MicServeurFrame extends JFrame{
}
public JPanel genContentPan() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(new EmptyBorder(3, 3, 3, 3));
InformedTargetDataline[] marray = getEnceinteList();
JList<InformedTargetDataline> micListe = new JList<InformedTargetDataline>(marray);
micListe.setOpaque(false);
JPanel masterPanel = new JPanel(new BorderLayout());
JTextField masterIP = new JTextField("192.168.1.1");
@ -61,19 +66,38 @@ public class MicServeurFrame extends JFrame{
if(serveur!=null) {
serveur.dispose();
serveur = null;
masterIP.setEnabled(true);
serverControl.setText("Lancer");
}else {
serveur = new MicServer(new InetSocketAddress(masterIP.getText(), AudioServer.communicationPort), deviceName,micListe.getSelectedValue().tdl);
masterIP.setEnabled(false);
serveur = new MicServer(new InetSocketAddress(masterIP.getText(), Codes.communicationPort), deviceName,micListe.getSelectedValue().tdl);
serveur.setServerAnswered(()->{
serverControl.setText("Arrêter");
});
serveur.addDisconnectListener(()->{
// Le dispose a été fait
serveur = null;
masterIP.setEnabled(true);
serverControl.setText("Lancer");
});
serverControl.setText("Lancement");
}
serverControl.setEnabled(true);
});
panel.add(masterPanel,BorderLayout.NORTH);
panel.add(micListe,BorderLayout.CENTER);
JScrollPane jE = new JScrollPane(micListe);
jE.setBorder(BorderFactory.createTitledBorder("Microphones disponibles"));
JPanel headP = new JPanel(new BorderLayout());
headP.setBorder(new EmptyBorder(3, 3, 5, 3));
headP.add(serverControl,BorderLayout.EAST);
headP.add(masterIP,BorderLayout.CENTER);
panel.add(headP,BorderLayout.NORTH);
panel.add(jE,BorderLayout.CENTER);
return panel;
}

View File

@ -14,7 +14,6 @@ import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.amihaiemil.eoyaml.YamlNode;
import com.amihaiemil.eoyaml.YamlSequence;
import com.bernard.murder.YamlUtils;
import com.bernard.murder.game.GameCreator.QuicksavedPartie;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Partie;
@ -25,6 +24,7 @@ import com.bernard.murder.view.minel.Minel;
import com.bernard.murder.view.minel.ObjetSearchMinel;
import com.bernard.murder.view.minel.ServeurMinel;
import com.bernard.murder.view.minel.TextPanMinel;
import com.bernard.util.YamlUtils;
public class MinelsCreator {
@ -35,7 +35,6 @@ public class MinelsCreator {
List<Minel> piecesMinels = new ArrayList<>();
qpartie.piecesStream().map(p -> new InventaireMinel(manager, p)).forEach(m -> piecesMinels.add(m));
generalMinels.add(new TextPanMinel(manager));
generalMinels.add(new ObjetSearchMinel(manager, manager.getEveryInventaire()));
generalMinels.add(new ServeurMinel(manager));
@ -142,4 +141,9 @@ public class MinelsCreator {
.build();
}
public static void closeMiniels(Map<Personnage, List<Minel>> persoMinels, Map<String, List<Minel>> supMinels) {
persoMinels.values().stream().forEach(l -> l.forEach(Minel::onFrameClose));
supMinels .values().stream().forEach(l -> l.forEach(Minel::onFrameClose));
}
}

View File

@ -3,22 +3,27 @@ package com.bernard.murder.view;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import com.bernard.murder.ParseUtils;
import com.bernard.murder.Parametres;
import com.bernard.murder.game.GameCreator.QuicksavedPartie;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Partie;
import com.bernard.murder.model.Personnage;
import com.bernard.murder.util.view.MouseReactiveTabbedPane;
import com.bernard.murder.util.view.ScrollablePanel;
import com.bernard.murder.view.minel.Minel;
import com.bernard.util.ParseUtils;
public class MurderatorGameFrame extends JFrame{
@ -28,17 +33,21 @@ public class MurderatorGameFrame extends JFrame{
Map<String,List<Minel>> minelsSup;
Map<Personnage,List<Minel>> minels;
GameManager manager;
public MurderatorGameFrame(String frameName, Partie partie, GameManager manager,Map<String,List<Minel>> minelsSup,Map<Personnage,List<Minel>> minels) {
this.setSize(700, 500);
this.setMinimumSize(new Dimension(200, 100));
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
this.setTitle(frameName);
this.minelsSup = minelsSup;
this.minels = minels;
this.manager = manager;
manager.bindMinelQuicksaver(() -> MinelsCreator.createMinelQuicksave(minels, minelsSup));
this.setContentPane(genGamePane(partie,manager,minelsSup,minels));
this.pack();
this.addWindowListener(new WListener());
//this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
@ -52,71 +61,88 @@ public class MurderatorGameFrame extends JFrame{
}
public JPanel genGamePane(Partie partie,GameManager manager,Map<String,List<Minel>> minelsSup,Map<Personnage,List<Minel>> minels) {
JPanel globalPan = new JPanel(new BorderLayout());
//Center Panel
MouseReactiveTabbedPane centerPan = new MouseReactiveTabbedPane(JTabbedPane.TOP);
int j = 0;
for(String s : minelsSup.keySet()) {
JPanel centralLocalBpanPan = new JPanel(new GridLayout(2,(minelsSup.get(s).size()+1)/2,-1,-1));
minelsSup.get(s).stream().map(m -> m.genContentPane()).forEach(mpan -> {centralLocalBpanPan.add(mpan);mpan.setBorder(BorderFactory.createLineBorder(ParseUtils.randDarkBlueColor(),2));});
JScrollPane centralLocalPan = new JScrollPane(centralLocalBpanPan);
centerPan.insertTab(s,null,centralLocalPan,null,j++);
System.out.println(j);
//Calcul des dimensions max
int minelCount = minelsSup.get(s).size();
int lincount = (int) Math.ceil(((double)minelCount)/((double)Parametres.instance.minielParLigne));
ScrollablePanel centralLocalBpanPan = new ScrollablePanel(new GridLayout(lincount,Parametres.instance.minielParLigne,-1,-1));
centralLocalBpanPan.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT );
minelsSup.get(s).stream()
.map(m -> m.genContentPane())
.forEach(mpan -> {
centralLocalBpanPan.add(mpan);
mpan.setBorder(BorderFactory.createLineBorder(ParseUtils.randDarkBlueColor(),3));
mpan.setMinimumSize(Parametres.instance.minielMinSize);
}
);
if(lincount > Parametres.instance.lignesDeMinielAvantScroll) {
JScrollPane jsp = new JScrollPane(centralLocalBpanPan,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
centerPan.insertTab(s,null,jsp,null,j++);
} else
centerPan.insertTab(s,null,centralLocalBpanPan,null,j++);
}
for(Personnage p : minels.keySet()) {
JPanel centralLocalBpanPan = new JPanel(new GridLayout(2, (minels.get(p).size()+1)/2,-1,-1));
minels.get(p).stream().map(m -> m.genContentPane()).forEach(mpan -> {centralLocalBpanPan.add(mpan);mpan.setBorder(BorderFactory.createLineBorder(ParseUtils.randDarkBlueColor(),2));});
JScrollPane centralLocalPan = new JScrollPane(centralLocalBpanPan);
centerPan.insertTab(p.getNom(),null,centralLocalPan,null,j++);
System.out.println(j);
//Calcul des dimensions max
int minelCount = minels.get(p).size();
int lincount = (int) Math.ceil(((double)minelCount)/((double)Parametres.instance.minielParLigne));
ScrollablePanel centralLocalBpanPan = new ScrollablePanel(new GridLayout(lincount,Parametres.instance.minielParLigne,-1,-1));
centralLocalBpanPan.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT );
minels.get(p).stream()
.map(m -> m.genContentPane())
.forEach(mpan -> {
centralLocalBpanPan.add(mpan);
mpan.setBorder(BorderFactory.createLineBorder(ParseUtils.randDarkBlueColor(),3));
mpan.setMinimumSize(Parametres.instance.minielMinSize);
}
);
if(lincount > Parametres.instance.lignesDeMinielAvantScroll) {
JScrollPane jsp = new JScrollPane(centralLocalBpanPan,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
centerPan.insertTab(p.getNom(),null,jsp,null,j++);
} else
centerPan.insertTab(p.getNom(),null,centralLocalBpanPan,null,j++);
}
for (int i = 0; i < centerPan.getTabCount(); i++) {
System.out.println(i);
}
/*
//Left Panel
JPanel leftPan = new JPanel(new GridLayout(minels.size() + minelsSup.size(), 1));
for(String s : minelsSup.keySet()) {
JButton localButton = new JButton(s);
localButton.addActionListener(e -> centerLayout.show(centerPan, s));
try {
localButton.setDropTarget(new NotADropTarget());
localButton.getDropTarget().addDropTargetListener(new HoverSelect(centerLayout, centerPan, s));
} catch (TooManyListenersException e1) {
e1.printStackTrace();
}
leftPan.add(localButton);
}
for(Personnage p : minels.keySet()) {
JButton localButton = new JButton(p.getNom());
localButton.addActionListener(e -> centerLayout.show(centerPan, personnageIdentifier(p)));
try {
localButton.setDropTarget(new ObjetDropTarget(manager, p, ()->manager.inventoryUpdate(p)));
localButton.getDropTarget().addDropTargetListener(new HoverSelect(centerLayout, centerPan, personnageIdentifier(p),DnDConstants.ACTION_MOVE));
} catch (TooManyListenersException e1) {
e1.printStackTrace();
}
leftPan.add(localButton);
}
globalPan.add(leftPan, BorderLayout.WEST);
*/
globalPan.add(centerPan, BorderLayout.CENTER);
return globalPan;
}
public String personnageIdentifier(Personnage personnage) {
public static final String personnageIdentifier(Personnage personnage) {
return String.format("%08X",System.identityHashCode(personnage))+personnage.getNom();
}
public class WListener extends WindowAdapter{
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
int res = JOptionPane.showConfirmDialog(MurderatorGameFrame.this,
"En fermant cette fenêtre, vous allez mettre fin à la murder et supprimer les sauvegardes automatiques ?",
"Fermeture",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if(res==JOptionPane.OK_OPTION) {
manager.removeQuickSaves();
MinelsCreator.closeMiniels(minels, minelsSup);
MurderatorGameFrame.this.setVisible(false);
MurderatorGameFrame.this.dispose();
System.exit(0);
}
}
}
}

View File

@ -1,16 +1,23 @@
package com.bernard.murder.view.minel;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.Timer;
@ -18,10 +25,11 @@ import javax.swing.Timer;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.bernard.murder.ParseUtils;
import com.bernard.murder.Parametres;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Action;
import com.bernard.murder.model.Personnage;
import com.bernard.util.ParseUtils;
public class ActionsMinel extends Minel {
@ -33,6 +41,7 @@ public class ActionsMinel extends Minel {
Collection<Action> updatingActions;
Map<Action,JLabel> actionStatusTexts = new HashMap<>();
Map<Action,JButton> actionButtons = new HashMap<>();
Map<Action,JPopupMenu> jpmMenus = new HashMap<>();
public ActionsMinel(GameManager manager, Personnage perso) {
super(manager);
@ -53,17 +62,33 @@ public class ActionsMinel extends Minel {
actionsListPan.setLayout(new BoxLayout(actionsListPan, BoxLayout.PAGE_AXIS));
for(Action a : personnage.getActions()) {
JPanel actionControlPanel = new JPanel(new BorderLayout());
JPanel leftPan = new JPanel(new BorderLayout());
JLabel actionName = new JLabel(a.getName());
JPanel outerButton = new JPanel(new GridBagLayout());
JButton actionButton = new JButton("GO");
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.NONE;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.CENTER;
outerButton.add(actionButton,gbc);
JLabel actionStatusText = new JLabel(availableText);
actionButton.addActionListener(e->launchAction(a));
actionButtons.put(a, actionButton);
actionStatusTexts.put(a, actionStatusText);
actionControlPanel.add(actionButton, BorderLayout.EAST);
actionControlPanel.add(actionName, BorderLayout.NORTH);
actionControlPanel.add(actionStatusText, BorderLayout.SOUTH);
leftPan.add(actionName, BorderLayout.NORTH);
leftPan.add(actionStatusText, BorderLayout.SOUTH);
actionControlPanel.add(outerButton, BorderLayout.EAST);
actionControlPanel.add(leftPan,BorderLayout.CENTER);
ActionPopupListener apl = new ActionPopupListener(a);
jpmMenus.put(a, apl.makePopup());
actionControlPanel.addMouseListener(apl);
actionsListPan.add(actionControlPanel);
actionsListPan.add(new JSeparator());
@ -71,9 +96,14 @@ public class ActionsMinel extends Minel {
Timer timer = new Timer(100, e->updateTexts());
timer.start();
}
JScrollPane globalScroll = new JScrollPane(actionsListPan);
JLabel titleLabel = new JLabel("Actions de "+personnage.getNom());
globalPan.add(titleLabel,BorderLayout.NORTH);
JLabel titre = new JLabel("Actions de "+personnage.getNom(),JLabel.CENTER);
titre.setFont(Parametres.instance.minielTitleFont);
titre.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
globalPan.add(titre,BorderLayout.NORTH);
globalPan.add(globalScroll,BorderLayout.CENTER);
updateTexts();
@ -83,12 +113,19 @@ public class ActionsMinel extends Minel {
private void launchAction(Action a) {
if(!a.canBeLaunched())return;
a.launch();
manager.launchAction(a);
actionButtons.get(a).setEnabled(false);
updateText(a);
updatingActions.add(a);
}
private void resetAction(Action a) {
manager.resetAction(a);
actionButtons.get(a).setEnabled(true);
updateTexts();
updatingActions.remove(a);
}
@Override
public YamlMappingBuilder saveToYaml() {
return Yaml.createYamlMappingBuilder().add("personnage", personnage.getNom());
@ -108,6 +145,37 @@ public class ActionsMinel extends Minel {
actionStatusTexts.get(a).setText(String.format(waitingTimeText, ParseUtils.dumpTimeLength(a.timeToWaitLeft()), ParseUtils.dumpHourDate(a.dateReset())));
}
public class ActionPopupListener extends MouseAdapter {
Action theAction;
public ActionPopupListener(Action theAction) {
this.theAction = theAction;
}
public final JPopupMenu makePopup() {
JPopupMenu popup = new JPopupMenu();
JMenuItem jmi = new JMenuItem("Réinitialiser");
jmi.addActionListener(e -> resetAction(theAction));
popup.add(jmi);
return popup;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
jpmMenus.get(theAction).show(e.getComponent(), e.getX(), e.getY());
}
}
}
}

View File

@ -3,16 +3,25 @@ package com.bernard.murder.view.minel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.bernard.murder.Parametres;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Inventaire;
import com.bernard.murder.model.Objet;
@ -32,20 +41,26 @@ public class InventaireMinel extends Minel {
JList<Objet> objets;
Inventaire inv;
JPopupMenu invPopup;
@Override
public JPanel genContentPane() {
JPanel globalpan = new JPanel(new BorderLayout());
JLabel titre;
if(inv.getInventoryName()!=null) {
JLabel invName = new JLabel(inv.getInventoryName());
globalpan.add(invName, BorderLayout.NORTH);
}
if(inv.getInventoryName()!=null)
titre = new JLabel(inv.getInventoryName(),JLabel.CENTER);
else
titre = new JLabel("Inventaire",JLabel.CENTER);
titre.setFont(Parametres.instance.minielTitleFont);
titre.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
globalpan.add(titre, BorderLayout.NORTH);
JPanel inventaire = new JPanel();
JButton voler = new JButton("RandomItem");
JButton voler = new JButton("Objet au pif");
voler.addActionListener(e -> {
objets.setSelectedIndex((int) (Math.random() * objets.getModel().getSize()));
});
@ -54,6 +69,8 @@ public class InventaireMinel extends Minel {
objets.setCellRenderer(new ObjetListCellRenderer());
objets.setDragEnabled(true);
objets.setTransferHandler(new ObjetTransferHandler());
objets.setOpaque(false);
objets.addMouseListener(new InvPopupListener());
updateObjets();
final ObjetDropTarget odt = new ObjetDropTarget(manager, inv, this::updateObjets);
objets.setDropTarget(odt);
@ -63,15 +80,15 @@ public class InventaireMinel extends Minel {
inventaire.add(objets);
JScrollPane textsp = new JScrollPane(inventaire);
globalpan.add(voler, BorderLayout.SOUTH);
globalpan.add(inventaire, BorderLayout.CENTER);
globalpan.add(textsp, BorderLayout.CENTER);
return globalpan;
}
private void updateObjets() {
System.out.print("Updating "+inv+" with");
manager.dumpCurrentState();
Objet[] objz = new Objet[inv.getObjects().size()];
objz = inv.getObjects().toArray(objz);
objets.setListData(objz);
@ -86,18 +103,21 @@ public class InventaireMinel extends Minel {
private static final long serialVersionUID = -7176962839330435585L;
Font origFont = null;
@Override
public Component getListCellRendererComponent(JList<? extends Objet> list, Objet objet, int index,
boolean isSelected, boolean cellHasFocus) {
setText(objet.getNom());
if(origFont==null)origFont = getFont();
if (isSelected) {
setFont(getFont().deriveFont(Font.BOLD));
setBackground(list.getSelectionBackground());
double ratio = 0.9;
setFont(origFont.deriveFont(AffineTransform.getScaleInstance(ratio, 1.0)));
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setFont(getFont().deriveFont(Font.PLAIN));
setFont(origFont.deriveFont(Font.PLAIN));
setBackground(list.getBackground());
setForeground(list.getForeground());
}
@ -106,4 +126,70 @@ public class InventaireMinel extends Minel {
}
}
public class InvPopupListener extends MouseAdapter{
JPopupMenu jpm;
JMenuItem renameOpt;
JMenuItem deleteOpt;
public InvPopupListener() {
jpm = new JPopupMenu();
JMenuItem createOpt = new JMenuItem("Créer");
createOpt.addActionListener(e -> {
String newName = JOptionPane.showInputDialog(
objets,
"Nom du nouvel objet",
"Création d'objet",
JOptionPane.QUESTION_MESSAGE);
if(newName != null)
manager.createObjet(newName,inv);
});
jpm.add(createOpt);
renameOpt = new JMenuItem("Renommer");
renameOpt.addActionListener(e -> {
String newName = JOptionPane.showInputDialog(
objets,
"Nouveau nom",
"Renommage d'objet",
JOptionPane.QUESTION_MESSAGE);
if(newName != null)
manager.renameObjet(objets.getSelectedValue(),newName,inv);
});
jpm.add(renameOpt);
deleteOpt = new JMenuItem("Supprimer");
deleteOpt.addActionListener(e -> {
int res = JOptionPane.showConfirmDialog(
objets,
"Voulez-vous vraiment détruire cet objet ?",
"Destruction d'objet",
JOptionPane.WARNING_MESSAGE,
JOptionPane.YES_NO_OPTION);
if(res == JOptionPane.OK_OPTION)
manager.deleteObjet(objets.getSelectedValue(),inv);
});
jpm.add(deleteOpt);
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
objets.setSelectedIndex(objets.locationToIndex(e.getPoint()));
renameOpt.setEnabled(!objets.isSelectionEmpty());
deleteOpt.setEnabled(!objets.isSelectionEmpty());
jpm.show(e.getComponent(), e.getX(), e.getY());
}
}
}
}

View File

@ -24,4 +24,6 @@ public abstract class Minel{
public abstract YamlMappingBuilder saveToYaml();
public void onFrameClose() {};
}

View File

@ -8,22 +8,27 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import com.amihaiemil.eoyaml.Scalar;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.amihaiemil.eoyaml.YamlNode;
import com.bernard.murder.ParseUtils;
import com.bernard.murder.YamlUtils;
import com.bernard.murder.Parametres;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Inventaire;
import com.bernard.murder.model.Objet;
import com.bernard.murder.util.view.SimpleDocumentListener;
import com.bernard.util.ParseUtils;
import com.bernard.util.YamlUtils;
public class ObjetSearchMinel extends Minel {
@ -59,29 +64,32 @@ public class ObjetSearchMinel extends Minel {
JPanel globalPan = new JPanel(new BorderLayout());
JLabel titre = new JLabel("Recherche d'objets",JLabel.CENTER);
titre.setFont(Parametres.instance.minielTitleFont);
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
JTextField searchField = new JTextField();
searchField.setToolTipText("Objet à chercher");
JList<InventorizedObject> searchResults = new JList<>();
searchResults.setOpaque(false);
searchResults.setBorder(BorderFactory.createEmptyBorder(5,2,2,2));
searchField.getDocument().addDocumentListener(new SimpleDocumentListener() {
DocumentListener dl = new SimpleDocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
System.out.println("Updated to "+e.getDocument().toString());
String searchText = searchField.getText();
if(searchText.isBlank()) {
/*if(searchText.isBlank()) {
searchResults.setListData(new InventorizedObject[0]);
return;
}
}*/
Set<InventorizedObject> startMatch = new HashSet<>();
Set<InventorizedObject> anyMatch = new HashSet<>();
Set<InventorizedObject> subwordMatch = new HashSet<>();
for(Objet o : objets.keySet()) {
System.out.println(o+"->"+searchText);
if(o.getNom().startsWith(searchText))
startMatch.add(new InventorizedObject(o,objets.get(o)));
else if(o.getNom().contains(searchText))
@ -99,11 +107,18 @@ public class ObjetSearchMinel extends Minel {
searchResults.setListData(results);
}
});
};
searchField.getDocument().addDocumentListener(dl);
globalPan.add(searchField, BorderLayout.NORTH);
globalPan.add(searchResults,BorderLayout.CENTER);
//Remplis la liste pour la première fois
dl.changedUpdate(null);
panel.add(searchField, BorderLayout.NORTH);
panel.add(new JScrollPane(searchResults),BorderLayout.CENTER);
globalPan.add(titre,BorderLayout.NORTH);
globalPan.add(panel,BorderLayout.CENTER);
return globalPan;
}

View File

@ -1,11 +1,27 @@
package com.bernard.murder.view.minel;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.bernard.murder.Parametres;
import com.bernard.murder.audio.AudioServer;
import com.bernard.murder.game.GameManager;
@ -13,27 +29,159 @@ public class ServeurMinel extends Minel {
AudioServer serveur;
JList<Integer> micListe;
JList<Integer> spkListe;
JPopupMenu micPopup;
JPopupMenu spkPopup;
public ServeurMinel(GameManager manager) {
super(manager);
serveur = new AudioServer();
try {
serveur = new AudioServer(manager);
serveur.genPasswords();
}catch(SocketException | UnknownHostException ex) {
JOptionPane.showMessageDialog(null, "Lancement du serveur audio impossible !\n"+ex.getMessage(), "Impossible de lancer le serveur audio", JOptionPane.ERROR_MESSAGE, null);
}
}
public ServeurMinel(GameManager manager,YamlMapping ym) {
super(manager);
serveur = new AudioServer();
this(manager);
// On se fiche de la sauvegarde automatique.
}
@Override
public JPanel genContentPane() {
JPanel pan = new JPanel();
JLabel label = new JLabel("Rien pour l'instant");
pan.add(label);
return pan;
JPanel panel = new JPanel(new BorderLayout());
JLabel titre = new JLabel("Status du serveur",JLabel.CENTER);
titre.setFont(Parametres.instance.minielTitleFont);
titre.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
micListe = new JList<>();
micListe.setOpaque(false);
spkListe = new JList<>();
spkListe.setOpaque(false);
EnceintePopupListener epl = new EnceintePopupListener();
spkPopup = epl.makePopup();
spkListe.addMouseListener(epl);
MicsPopupListener mpl = new MicsPopupListener();
micPopup = mpl.makePopup();
micListe.addMouseListener(mpl);
serveur.addChangeListener(() -> {
// Updating lists content
micListe.setListData(new Vector<Integer>(serveur.getMics().keySet()));
spkListe.setListData(new Vector<Integer>(serveur.getSpeakers().keySet()));
});
JScrollPane jE = new JScrollPane(spkListe);
jE.setBorder(BorderFactory.createTitledBorder("Enceintes connéctées"));
JScrollPane jM = new JScrollPane(micListe);
jM.setBorder(BorderFactory.createTitledBorder("Microphones connéctés"));
JPanel centerP = new JPanel(new GridLayout(2, 1));
centerP.add(jE);
centerP.add(jM);
panel.add(titre,BorderLayout.NORTH);
panel.add(centerP,BorderLayout.CENTER);
return panel;
}
public class EnceintePopupListener extends MouseAdapter{
public final JPopupMenu makePopup() {
JPopupMenu popup = new JPopupMenu();
JLabel idText = new JLabel();
idText.setFont(idText.getFont().deriveFont(Font.ITALIC));
idText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
spkListe.getSelectionModel().addListSelectionListener(e ->
idText.setText("Id: "+ (spkListe.getSelectedValue()!=null?spkListe.getSelectedValue():-1))
);
popup.add(idText);
JMenuItem jmi = new JMenuItem("Silence");
jmi.addActionListener(e -> {
serveur.forceSilence(spkListe.getSelectedValue());
});
popup.add(jmi);
jmi = new JMenuItem("Déconnecter");
jmi.addActionListener(e -> {
serveur.disconnectEnceinte(spkListe.getSelectedValue());
});
popup.add(jmi);
return popup;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
spkListe.setSelectedIndex(spkListe.locationToIndex(e.getPoint()));
if(!spkListe.isSelectionEmpty())
spkPopup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
public class MicsPopupListener extends MouseAdapter{
public final JPopupMenu makePopup() {
JPopupMenu popup = new JPopupMenu();
JLabel idText = new JLabel();
idText.setFont(idText.getFont().deriveFont(Font.ITALIC));
idText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
micListe.getSelectionModel().addListSelectionListener(e -> //XXX: Tester si l'affichage se met bien à jour ici
idText.setText("Id: "+ (micListe.getSelectedValue()!=null?micListe.getSelectedValue():-1))
);
popup.add(idText);
JMenuItem jmi = new JMenuItem("Déconnecter");
jmi.addActionListener(e -> {
serveur.disconnectMic(micListe.getSelectedValue());
});
popup.add(jmi);
return popup;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
micListe.setSelectedIndex(micListe.locationToIndex(e.getPoint()));
if(!micListe.isSelectionEmpty())
micPopup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
@Override
public YamlMappingBuilder saveToYaml() {
// Ce miniel représente le serveur audio, indépendant du relancement
return Yaml.createYamlMappingBuilder();
}
@Override
public void onFrameClose() {
serveur.dispose();
}
}

View File

@ -1,29 +0,0 @@
package com.bernard.murder.view.minel;
import javax.swing.JPanel;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.bernard.murder.game.GameManager;
import com.bernard.murder.model.Personnage;
public class StatusMinel extends Minel {
public StatusMinel(GameManager manager, Personnage personnage) {
super(manager);
// TODO Auto-generated constructor stub
}
@Override
public JPanel genContentPane() {
// TODO Auto-generated method stub
return null;
}
@Override
public YamlMappingBuilder saveToYaml() {
//TODO auto-generated thingy
return Yaml.createYamlMappingBuilder();
}
}

View File

@ -2,6 +2,8 @@ package com.bernard.murder.view.minel;
import java.awt.BorderLayout;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
@ -10,11 +12,11 @@ import javax.swing.border.EmptyBorder;
import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.bernard.murder.Parametres;
import com.bernard.murder.game.GameManager;
public class TextPanMinel extends Minel {
String initTexte = "";
JTextArea textArea;
public TextPanMinel(GameManager manager) {
@ -22,29 +24,35 @@ public class TextPanMinel extends Minel {
}
public TextPanMinel(GameManager gm, YamlMapping ym) {
super(gm);
initTexte = ym.string("texte");
this(gm);
textArea.setText(ym.string("texte"));
}
@Override
public JPanel genContentPane() {
JPanel globalPan = new JPanel(new BorderLayout());
//globalPan.setBackground(ParseUtils.randColor());
JLabel titre = new JLabel("Notes",JLabel.CENTER);
titre.setFont(Parametres.instance.minielTitleFont);
titre.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
textArea = new JTextArea();
textArea.setBorder(new EmptyBorder(23,23,23,23));
textArea.setText(initTexte);
textArea.setText("");
textArea.setLineWrap(true);
//Color col = ParseUtils.randColor();
//textArea.setBackground(col);
//textArea.setForeground(ParseUtils.getContrastColor(col));
textArea.setBackground(Parametres.instance.textPanMinielBackgroundColor);
textArea.setForeground(Parametres.instance.textPanMinielTextColor);
globalPan.add(titre,BorderLayout.NORTH);
globalPan.add(new JScrollPane(textArea),BorderLayout.CENTER);
return globalPan;
}
@Override
public YamlMappingBuilder saveToYaml() {
return Yaml.createYamlMappingBuilder().add("texte", textArea!=null?textArea.getText():initTexte);
return Yaml.createYamlMappingBuilder().add("texte", textArea!=null?textArea.getText():"");
}
}

View File

@ -36,7 +36,6 @@ public class ObjetDropTarget extends DropTarget {
}
for(Runnable runnable : toUpdate)
runnable.run();
manager.dumpCurrentState();
} catch (Exception ex) {
ex.printStackTrace();
}

View File

@ -5,8 +5,7 @@ actions:
status:
- Mort
- Paralysie
- NoKill:
onStart: true
- NoKill
inventaire:
# Tout le monde en a un au départ, sera nommé Portefeuille_Bernard, Portefeuille_Jach
@ -43,4 +42,4 @@ joueurs:
- "PB ENS"
espacePerso:
- "Guide Richard"
# Par défaut, se place dans le seul espace personnel (Si il y en a deux, pas bon)
# Par défaut, se place dans le seul espace personnel (Si il y en a deux, pas bon)