Skip to content

Commit

Permalink
Add interpretation of chat messages
Browse files Browse the repository at this point in the history
  • Loading branch information
Garanas committed May 9, 2024
1 parent d0e9566 commit dbb816b
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import com.faforever.commons.replay.body.ReplayBodyParser;
import com.faforever.commons.replay.body.ReplayBodyToken;
import com.faforever.commons.replay.body.ReplayBodyTokenizer;
import com.faforever.commons.replay.header.ReplayHeader;
import com.faforever.commons.replay.header.ReplayHeaderParser;
import com.faforever.commons.replay.header.ReplayHeaderToken;
import com.faforever.commons.replay.header.ReplayHeaderTokenizer;
import com.faforever.commons.replay.header.*;
import com.faforever.commons.replay.semantics.Semantics;
import com.faforever.commons.replay.semantics.records.TrackedEvent;
import com.fasterxml.jackson.databind.DeserializationFeature;
Expand Down Expand Up @@ -41,17 +38,17 @@ private static ReplayHeader loadSCFAReplayHeader(LittleEndianDataInputStream str
}

@Contract(pure = true)
private static @NotNull List<TrackedEvent> loadSCFAReplayBody(LittleEndianDataInputStream stream) throws IOException{
private static @NotNull List<TrackedEvent> loadSCFAReplayBody(List<Source> sources, LittleEndianDataInputStream stream) throws IOException{
List<ReplayBodyToken> bodyTokens = ReplayBodyTokenizer.tokenize(stream);
List<Event> bodyEvents = ReplayBodyParser.parseTokens(bodyTokens);
return Semantics.registerEvents(bodyEvents);
return Semantics.registerEvents(sources, bodyEvents);
}

@Contract(pure = true)
private static ReplayContainer loadSCFAReplayFromMemory(ReplayMetadata metadata, byte[] scfaReplayBytes) throws IOException {
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream((new ByteArrayInputStream(scfaReplayBytes)))) {
ReplayHeader replayHeader = loadSCFAReplayHeader(stream);
List<TrackedEvent> replayBody = loadSCFAReplayBody(stream);
List<TrackedEvent> replayBody = loadSCFAReplayBody(replayHeader.sources(), stream);

if(stream.available() > 0) {
throw new EOFException();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,103 @@
package com.faforever.commons.replay.semantics;

import com.faforever.commons.replay.body.Event;
import com.faforever.commons.replay.header.Source;
import com.faforever.commons.replay.semantics.records.ChatMessage;
import com.faforever.commons.replay.semantics.records.TrackedEvent;
import com.faforever.commons.replay.shared.LuaTable;

import java.time.Duration;
import java.util.List;
import java.util.Objects;

public class Semantics {

public static List<TrackedEvent> registerEvents(List<Event> events) {
public static Duration tickToDuration(int tick) {
return Duration.ofSeconds(tick / 10);
}

public static List<TrackedEvent> registerEvents(List<Source> sources, List<Event> events) {
final int[] tick = {-1};
final int[] clientId = {-1};

return events.stream().map((event) -> switch(event) {
return events.stream().map((event) -> switch (event) {
case Event.Advance e -> {
tick[0] = tick[0] + e.ticksToAdvance();
yield null;
tick[0] = tick[0] + e.ticksToAdvance();
yield null;
}

case Event.SetCommandSource e -> {
clientId[0] = e.playerIndex();
yield null;
}

default -> new TrackedEvent(tick[0], clientId[0], event);
default -> new TrackedEvent(tick[0], sources.get(clientId[0]), event);
}).filter(Objects::nonNull).toList();
}

;

/**
* Retrieves all events that are chat messages
*
* @param events A list of events
* @return A list of events that are chat messages
*/
public static List<ChatMessage> findChatMessages(List<Source> sources, List<TrackedEvent> events) {
return events.stream().map((trackedEvent) -> switch (trackedEvent.event()) {

// TODO: the fact that we piggy-back on the 'GiveResourcesToPlayer' callback to embed chat messages is all wrong! We should instead introduce an alternative callback with the sole purpose to send messages.
// Requires refactoring in the game!

case Event.LuaSimCallback(
String func, LuaTable parametersLua, Event.CommandUnits commandUnits
) when func.equals("GiveResourcesToPlayer") -> {
yield switch (parametersLua) {
case LuaTable.Table callbackTable -> {

// TODO: this field has no meaning and can be manipulated, instead use the authorised command source.
// Requires refactoring in the game!
if (!(callbackTable.value().get("From") instanceof LuaTable.Number from)) {
yield null;
}

if (from.value() - 1 <= -2) {
yield null;
}

// TODO: this field has no meaning and can be manipulated, instead use the authorised command source.
// Requires refactoring in the game!
if (!(callbackTable.value().get("Sender") instanceof LuaTable.String sender)) {
yield null;
}

// TODO: apparently all players create a sim callback that contains the chat message. This hack is how we skip it,
// Requires refactoring in the game!
if (!Objects.equals(sender.value(), trackedEvent.source().name())) {
yield null;
}

if (!(callbackTable.value().get("Msg") instanceof LuaTable.Table msgTable)) {
yield null;
}


// TODO: this is 1 out of the 2 legitimate fields
if (!(msgTable.value().get("to") instanceof LuaTable.String msgTo)) {
yield null;
}

// TODO: this is 2 out of the 2 legitimate fields
if (!(msgTable.value().get("text") instanceof LuaTable.String msgText)) {
yield null;
}

yield new ChatMessage(tickToDuration(trackedEvent.tick()), trackedEvent.source().name(), msgTo.value(), msgText.value());
}
default -> null;
};
}
default -> null;
}).filter(Objects::nonNull).toList();
};
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.faforever.commons.replay.semantics.records;

import com.faforever.commons.replay.body.Event;
import com.faforever.commons.replay.header.Source;

public record TrackedEvent(int tick, int authorisedByClient, Event event) {
public record TrackedEvent(int tick, Source source, Event event) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.faforever.commons.replay;

import com.faforever.commons.replay.body.Event;
import com.faforever.commons.replay.semantics.Semantics;
import com.faforever.commons.replay.semantics.records.ChatMessage;
import com.faforever.commons.replay.semantics.records.TrackedEvent;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.compress.compressors.CompressorException;
Expand All @@ -10,6 +13,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;

import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -40,6 +44,9 @@ public void parseBinary01() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(106, chatMessages.size());
}
);
}
Expand All @@ -54,6 +61,9 @@ public void parseBinary02() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(2, chatMessages.size());
}
);
}
Expand All @@ -68,6 +78,9 @@ public void parseBinary03() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(1, chatMessages.size());
}
);
}
Expand All @@ -82,6 +95,9 @@ public void parseBinary04() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(7, chatMessages.size());
}
);
}
Expand All @@ -96,6 +112,9 @@ public void parseBinary05() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(0, chatMessages.size());
}
);
}
Expand All @@ -110,6 +129,9 @@ public void parseBinary06() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(0, chatMessages.size());
}
);
}
Expand All @@ -124,6 +146,9 @@ public void parseBinary07() throws CompressorException, IOException {

assertNoUnprocessedTokens(fafReplayContainer);
assertNoErrorTokens(fafReplayContainer);

List<ChatMessage> chatMessages = Semantics.findChatMessages(fafReplayContainer.header().sources(), fafReplayContainer.trackedEvents());
assertEquals(3, chatMessages.size());
}
);
}
Expand Down

0 comments on commit dbb816b

Please sign in to comment.