Skip to content

Commit

Permalink
Merge pull request #635 from mafia-rust/bug/442-507
Browse files Browse the repository at this point in the history
Make player keyword data optional and swappable
  • Loading branch information
Jack-Papel authored Apr 26, 2024
2 parents 3320cc0 + a19e0c2 commit 0311a25
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 118 deletions.
67 changes: 39 additions & 28 deletions client/src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReactElement } from "react";
import translate, { translateChecked } from "../game/lang";
import React from "react";
import GAME_MANAGER, { find, replaceMentions } from "..";
import StyledText from "./StyledText";
import StyledText, { KeywordDataMap, PLAYER_SENDER_KEYWORD_DATA } from "./StyledText";
import "./chatMessage.css"
import { ChatGroup, PhaseState, PlayerIndex, Verdict } from "../game/gameState.d";
import { Role } from "../game/roleState.d";
Expand All @@ -16,7 +16,9 @@ import { OjoAction } from "../menu/game/gameScreenContent/RoleSpecificMenus/Smal
export default function ChatElement(
props: {
message: ChatMessage,
playerNames?: string[]
playerNames?: string[],
playerKeywordData?: KeywordDataMap,
playerSenderKeywordData?: KeywordDataMap
},
): ReactElement {
const message = props.message;
Expand Down Expand Up @@ -50,6 +52,17 @@ export default function ChatElement(
style += " player"
}

if (message.variant.messageSender.type === "livingToDead") {
icon += translate("messageSender.livingToDead.icon")
}

let messageSender = "";
if (message.variant.messageSender.type === "player") {
messageSender = "sender-"+playerNames[message.variant.messageSender.player];
} else {
messageSender = translate("role."+message.variant.messageSender.type+".name");
}

if (
GAME_MANAGER.state.stateType === "game" && GAME_MANAGER.state.clientState.type === "player" && GAME_MANAGER.state.clientState.myIndex !== null &&
(find(GAME_MANAGER.state.players[GAME_MANAGER.state.clientState.myIndex].name ?? "").test(sanitizePlayerMessage(replaceMentions(
Expand All @@ -68,20 +81,32 @@ export default function ChatElement(
) {
style += " mention";
}
break;

return <span className="chat-message">
<StyledText className={style}
playerKeywordData={props.playerSenderKeywordData ?? PLAYER_SENDER_KEYWORD_DATA}
>{icon ?? ""} {messageSender}: </StyledText>
<StyledText className={style}
playerKeywordData={props.playerKeywordData}
>{translateChatMessage(message.variant, playerNames)}</StyledText>
</span>;
case "targetsMessage":
return <span className="chat-message result">
<StyledText className={"chat-message " + style}>{(icon??"")} {translateChatMessage(message.variant, playerNames)}</StyledText>
<ChatElement message={
<StyledText className={"chat-message " + style}
playerKeywordData={props.playerKeywordData}
>{(icon??"")} {translateChatMessage(message.variant, playerNames)}</StyledText>
<ChatElement {...props} message={
{
variant: message.variant.message,
chatGroup: message.chatGroup,
}
} playerNames={playerNames}/>
}/>
</span>
case "playerDied":
return <>
<StyledText className={"chat-message " + style}>{(icon??"")} {translate("chatMessage.playerDied",
<StyledText className={"chat-message " + style}
playerKeywordData={props.playerKeywordData}
>{(icon??"")} {translate("chatMessage.playerDied",
playerNames[message.variant.grave.playerIndex],
)}</StyledText>
<div className="grave-message">
Expand All @@ -90,7 +115,9 @@ export default function ChatElement(
</>;
}

return <StyledText className={"chat-message " + style}>{(icon??"")} {translateChatMessage(message.variant, playerNames)}</StyledText>;
return <StyledText className={"chat-message " + style}
playerKeywordData={props.playerKeywordData}
>{(icon??"")} {translateChatMessage(message.variant, playerNames)}</StyledText>;
}

function playerListToString(playerList: PlayerIndex[], playerNames: string[]): string {
Expand All @@ -114,28 +141,12 @@ export function translateChatMessage(message: ChatMessageVariant, playerNames?:

switch (message.type) {
case "normal":

if(message.messageSender.type === "player"){
return translate("chatMessage.normal",
"sender-"+playerNames[message.messageSender.player],
sanitizePlayerMessage(replaceMentions(message.text, playerNames))
);
} else if (message.messageSender.type === "livingToDead") {
return (translate("messageSender.livingToDead.icon"))+translate("chatMessage.normal",
"sender-"+playerNames[message.messageSender.player],
sanitizePlayerMessage(replaceMentions(message.text, playerNames))
);
} else {
return translate("chatMessage.normal",
translate("role."+message.messageSender.type+".name"),
sanitizePlayerMessage(replaceMentions(message.text, playerNames))
);
}
return sanitizePlayerMessage(replaceMentions(message.text, playerNames));
case "whisper":
return translate("chatMessage.whisper",
playerNames[message.fromPlayerIndex],
playerNames[message.toPlayerIndex],
message.text
sanitizePlayerMessage(replaceMentions(message.text, playerNames))
);
case "broadcastWhisper":
return translate("chatMessage.broadcastWhisper",
Expand Down Expand Up @@ -246,7 +257,7 @@ export function translateChatMessage(message: ChatMessageVariant, playerNames?:
);
case "journalistJournal":
return translate("chatMessage.journalistJournal",
sanitizePlayerMessage(replaceMentions(message.journal))
sanitizePlayerMessage(replaceMentions(message.journal, playerNames))
);
case "youAreInterviewingPlayer":
return translate("chatMessage.youAreInterviewingPlayer",
Expand Down Expand Up @@ -339,7 +350,7 @@ export function translateChatMessage(message: ChatMessageVariant, playerNames?:
case "playerRoleAndWill":
return translate("chatMessage.playersRoleAndWill",
translate("role."+message.role+".name"),
sanitizePlayerMessage(message.will)
sanitizePlayerMessage(replaceMentions(message.will, playerNames))
);
case "consigliereResult":
const visitedNobody = message.visited.length === 0;
Expand Down
123 changes: 67 additions & 56 deletions client/src/components/StyledText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import "./styledText.css";
import DUMMY_NAMES from "../resources/dummyNames.json";
import { ARTICLES, WikiArticleLink, getArticleLangKey } from "./WikiArticleLink";
import GameScreen, { ContentMenu } from "../menu/game/GameScreen";
import { Player } from "../game/gameState.d";

export type TokenData = {
style?: string,
link?: WikiArticleLink,
replacement?: string
};
type KeywordData = TokenData[];
type KeywordDataMap = { [key: string]: KeywordData };
export type KeywordDataMap = { [key: string]: KeywordData };

const MARKDOWN_OPTIONS = {
breaks: true,
Expand All @@ -43,19 +44,24 @@ type Token = {
}
};

export type StyledTextProps = {
children: string[] | string,
className?: string,
noLinks?: boolean,
markdown?: boolean,
playerKeywordData?: KeywordDataMap
};

/**
* Styled Text
*
* ***MAKE SURE TO SANITIZE TEXT INPUT INTO THIS ELEMENT*** (If it's from the user)
*
* @param props.playerKeywordData If omitted, defaults to {@link PLAYER_KEYWORD_DATA}
* @see sanitizePlayerMessage in ChatMessage.tsx
*/
export default function StyledText(props: {
children: string[] | string,
className?: string,
noLinks?: boolean,
markdown?: boolean
}): ReactElement {
export default function StyledText(props: StyledTextProps): ReactElement {
const playerKeywordData = props.playerKeywordData ?? PLAYER_KEYWORD_DATA;

let tokens: Token[] = [{
type: "raw",
Expand All @@ -70,7 +76,7 @@ export default function StyledText(props: {
tokens[0].string = tokens[0].string.replace(/\n/g, '<br>');
}

tokens = styleKeywords(tokens);
tokens = styleKeywords(tokens, playerKeywordData);

const jsxString = tokens.map(token => {
if (token.type === "raw") {
Expand Down Expand Up @@ -100,43 +106,25 @@ export default function StyledText(props: {
</span>
}

const KEYWORD_DATA_MAP: KeywordDataMap = {};
const KEYWORD_DATA: KeywordDataMap = {};
computeKeywordData();

function clearKeywordData() {
for (const key in KEYWORD_DATA_MAP) {
delete KEYWORD_DATA_MAP[key];
function computeKeywordData() {
for (const key in KEYWORD_DATA) {
delete KEYWORD_DATA[key];
}
}

function computeBasicKeywordData() {
console.log("recomputed keyword data");

function addTranslatableKeywordData(langKey: string, data: KeywordData) {
KEYWORD_DATA_MAP[translate(langKey)] = data;
KEYWORD_DATA[translate(langKey)] = data;
for (let i = 0, variant; (variant = translateChecked(`${langKey}:var.${i}`)) !== null; i++) {
const variantData = data.map(datum => ({
...datum,
replacement: datum.replacement === translate(langKey) ? translate(`${langKey}:var.${i}`) : datum.replacement
}));
KEYWORD_DATA_MAP[variant] = variantData;
KEYWORD_DATA[variant] = variantData;
}
}

//add dummy names keywords
for(let i = 0; i < DUMMY_NAMES.length; i++) {
const name = DUMMY_NAMES[i];
KEYWORD_DATA_MAP["sender-"+name] = [
{ style: "keyword-player-number", replacement: (i + 1).toString() },
{ replacement: " " },
{ style: "keyword-player-sender", replacement: name }
];
KEYWORD_DATA_MAP[name] = [
{ style: "keyword-player-number", replacement: (i + 1).toString() },
{ replacement: " " },
{ style: "keyword-player", replacement: name }
];
}

//add article keywords
const SortedArticles = [...ARTICLES];
for (const article of SortedArticles) {
Expand Down Expand Up @@ -164,7 +152,6 @@ function computeBasicKeywordData() {
replacement: translate(`role.${role}.name`) // Capitalize roles
}]);
}


//add from keywords.json
for (const [keyword, data] of Object.entries(KEYWORD_DATA_JSON)) {
Expand All @@ -177,35 +164,59 @@ function computeBasicKeywordData() {
}
}

export function computeKeywordDataWithPlayers() {
clearKeywordData();
export const PLAYER_SENDER_KEYWORD_DATA: KeywordDataMap = {};
export const PLAYER_KEYWORD_DATA: KeywordDataMap = {};

if(GAME_MANAGER.state.stateType === "game"){

for(const player of GAME_MANAGER.state.players) {
KEYWORD_DATA_MAP["sender-"+player.toString()] = [
{ style: "keyword-player-number", replacement: (player.index + 1).toString() },
{ replacement: " " },
{ style: "keyword-player-sender", replacement: player.name }
];

KEYWORD_DATA_MAP[player.toString()] = [
{ style: "keyword-player-number", replacement: (player.index + 1).toString() },
{ replacement: " " },
{ style: "keyword-player", replacement: player.name }
];

}
export function computePlayerKeywordData(players: Player[]) {
for (const key in PLAYER_KEYWORD_DATA) {
delete PLAYER_KEYWORD_DATA[key];
}
for (const key in PLAYER_SENDER_KEYWORD_DATA) {
delete PLAYER_SENDER_KEYWORD_DATA[key];
}

for(const player of players) {
PLAYER_SENDER_KEYWORD_DATA["sender-"+player.toString()] = [
{ style: "keyword-player-number", replacement: (player.index + 1).toString() },
{ replacement: " " },
{ style: "keyword-player-sender", replacement: player.name }
];

PLAYER_KEYWORD_DATA[player.toString()] = [
{ style: "keyword-player-number", replacement: (player.index + 1).toString() },
{ replacement: " " },
{ style: "keyword-player", replacement: player.name }
];

}
}

export const DUMMY_NAMES_KEYWORD_DATA: KeywordDataMap = {};
computeDummyNamesKeywordData();

computeBasicKeywordData();
function computeDummyNamesKeywordData() {
for (const key in DUMMY_NAMES_KEYWORD_DATA) {
delete DUMMY_NAMES_KEYWORD_DATA[key];
}
for(let i = 0; i < DUMMY_NAMES.length; i++) {
const name = DUMMY_NAMES[i];
DUMMY_NAMES_KEYWORD_DATA["sender-"+name] = [
{ style: "keyword-player-number", replacement: (i + 1).toString() },
{ replacement: " " },
{ style: "keyword-player-sender", replacement: name }
];
DUMMY_NAMES_KEYWORD_DATA[name] = [
{ style: "keyword-player-number", replacement: (i + 1).toString() },
{ replacement: " " },
{ style: "keyword-player", replacement: name }
];
}
}

computeBasicKeywordData();
function styleKeywords(tokens: Token[], extraData?: KeywordDataMap): Token[] {
const keywordDataMap = { ...KEYWORD_DATA, ...extraData };

function styleKeywords(tokens: Token[]): Token[] {
for(const [keyword, data] of Object.entries(KEYWORD_DATA_MAP).sort((a, b) => b[0].length - a[0].length)){
for(const [keyword, data] of Object.entries(keywordDataMap).sort((a, b) => b[0].length - a[0].length)){
for(let index = 0; index < tokens.length; index++) {
const token = tokens[index];
if (token.type !== "raw") continue;
Expand Down
Loading

0 comments on commit 0311a25

Please sign in to comment.