Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global cutscene #179

Merged
merged 6 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/interactor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
"react-progressive-graceful-image": "^0.7.0",
"react-redux": "^9.0.4",
"react-rnd": "^10.4.10",
"react-slick": "^0.30.3",
"react-swipeable": "^7.0.1",
"react-toastify": "^9.1.1",
"reactflow": "^11.10.1",
"sass": "^1.59.3",
"selection-popover": "^0.3.0",
"slick-carousel": "^1.8.1",
"uuid": "^9.0.0"
},
"devDependencies": {
Expand All @@ -54,6 +56,7 @@
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@types/react-modal": "^3.16.3",
"@types/react-slick": "^0.23.13",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
justify-content: space-between;
align-items: center;
width: 100%;
background: linear-gradient(to top, #000000b3, #0000 100%);
z-index: 0;

&-right,
&-left {
Expand Down Expand Up @@ -203,6 +205,12 @@
}
}
}

&-left--hidden {
opacity: 0;
pointer-events: none;
cursor: default;
}
}

&__music-player {
Expand Down
30 changes: 20 additions & 10 deletions apps/interactor/src/components/interactor/CutsceneDisplayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TextFormatterStatic } from '../common/TextFormatter';
import { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io';
import { useI18n } from '../../libs/i18n';
import { setCutsceneTextIndex, setCutscenePartIndex } from '../../state/slices/narrationSlice';
import { selectShouldPlayGlobalStartCutscene } from '../../state/selectors';

const PartRenderer = ({
part,
Expand Down Expand Up @@ -109,17 +110,25 @@ export const CutsceneDisplayer = ({ onEndDisplay }: { onEndDisplay: () => void }
const { i18n } = useI18n();
const dispatch = useAppDispatch();
const scene = useAppSelector(selectCurrentScene);
const currentCutscene = useAppSelector((state) => state.novel.cutscenes?.find((c) => c.id === scene?.cutScene?.id));
const shouldPlayGlobalCutscene = useAppSelector(selectShouldPlayGlobalStartCutscene);
const globalCutscene = useAppSelector(
(state) =>
state.novel.globalStartCutsceneId &&
state.novel.cutscenes?.find((c) => c.id === state.novel.globalStartCutsceneId),
);
const currentCutscene = useAppSelector((state) =>
shouldPlayGlobalCutscene ? globalCutscene : state.novel.cutscenes?.find((c) => c.id === scene?.cutScene?.id),
);

const currentPartIndex = useAppSelector((state) => state.narration.input.cutscenePartIndex || 0);
const currentTextIndex = useAppSelector((state) => state.narration.input.cutsceneTextIndex || 0);

const isMobileDisplay = isMobileApp || window.innerWidth < 600;
const TEXTS_PER_GROUP = 3;

const handleContinueClick = () => {
const currentPart = parts[currentPartIndex];

if (currentTextIndex < currentPart.text.length - 1) {
dispatch(setCutsceneTextIndex(currentTextIndex + 1));
} else if (currentPartIndex < parts.length - 1) {
Expand All @@ -140,10 +149,10 @@ export const CutsceneDisplayer = ({ onEndDisplay }: { onEndDisplay: () => void }

const isAtEnd = () => {
if (!parts[currentPartIndex]) return false;

const currentPart = parts[currentPartIndex];
const isLastPart = currentPartIndex === parts.length - 1;

return isLastPart && currentTextIndex === currentPart.text.length - 1;
};

Expand All @@ -152,7 +161,7 @@ export const CutsceneDisplayer = ({ onEndDisplay }: { onEndDisplay: () => void }
if (!isMobileDisplay) {
return currentPart.text.slice(0, currentTextIndex + 1);
}

const startIndex = Math.floor(currentTextIndex / TEXTS_PER_GROUP) * TEXTS_PER_GROUP;
return currentPart.text.slice(startIndex, currentTextIndex + 1);
};
Expand Down Expand Up @@ -202,7 +211,10 @@ export const CutsceneDisplayer = ({ onEndDisplay }: { onEndDisplay: () => void }
</div>
<div className="CutsceneDisplayer__buttons">
<button
className="CutsceneDisplayer__buttons-left"
className={classNames({
'CutsceneDisplayer__buttons-left': true,
'CutsceneDisplayer__buttons-left--hidden': currentTextIndex === 0 && currentPartIndex === 0,
})}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
handlePreviousClick();
Expand All @@ -221,9 +233,7 @@ export const CutsceneDisplayer = ({ onEndDisplay }: { onEndDisplay: () => void }
}
}}
>
{isAtEnd() ? (
<p className="CutsceneDisplayer__buttons-right__text">{i18n('go_to_scene')}</p>
) : null}
{isAtEnd() ? <p className="CutsceneDisplayer__buttons-right__text">{i18n('go_to_scene')}</p> : null}
<IoIosArrowForward />
</button>
</div>
Expand Down
12 changes: 10 additions & 2 deletions apps/interactor/src/components/interactor/Interactor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
selectLastLoadedCharacters,
selectLastSelectedCharacter,
selectDisplayingCutScene,
selectShouldPlayGlobalStartCutscene,
} from '../../state/selectors';
import { useAppDispatch, useAppSelector } from '../../state/store';
import ChatBox from '../chat-box/ChatBox';
Expand All @@ -19,8 +20,9 @@ import SceneSuggestion from './SceneSuggestion';
import EmotionRenderer from '../emotion-render/EmotionRenderer';
import { AssetDisplayPrefix } from '@mikugg/bot-utils';
import { CutsceneDisplayer } from './CutsceneDisplayer';
import { markCurrentCutsceneAsSeen } from '../../state/slices/narrationSlice';
import { markCurrentCutsceneAsSeen, setHasPlayedGlobalStartCutscene } from '../../state/slices/narrationSlice';
import IndicatorsDisplay from '../indicators-display/IndicatorsDisplay';
import StartSelector from '../start-selector/StartSelector';

const Interactor = () => {
const { assetLinkLoader, isMobileApp } = useAppContext();
Expand All @@ -30,6 +32,7 @@ const Interactor = () => {
const displayCharacter = useAppSelector(selectLastSelectedCharacter);
const backgrounds = useAppSelector((state) => state.novel.backgrounds);
const displayingCutscene = useAppSelector(selectDisplayingCutScene);
const shouldPlayGlobalCutscene = useAppSelector(selectShouldPlayGlobalStartCutscene);

if (!scene) {
return null;
Expand All @@ -43,11 +46,16 @@ const Interactor = () => {
<div className="Interactor__cutscene">
<CutsceneDisplayer
onEndDisplay={() => {
dispatch(markCurrentCutsceneAsSeen());
if (shouldPlayGlobalCutscene) {
dispatch(setHasPlayedGlobalStartCutscene(true));
} else {
dispatch(markCurrentCutsceneAsSeen());
}
}}
/>
</div>
) : null}
{!displayingCutscene && <StartSelector />}
<div className="Interactor__content">
<InteractorHeader />
<IndicatorsDisplay />
Expand Down
133 changes: 133 additions & 0 deletions apps/interactor/src/components/start-selector/StartSelector.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
@import '../../variables';

.StartSelector {
&__overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(6px);
z-index: 1000;
}

&__container {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}

&__cards {
// display: flex;
// gap: 16px;
// padding: 20px;
// overflow-x: auto;
// width: 100%;
}

&__slider {
width: 90%;

.slick-slide {
padding: 0 10px;
}

.slick-center {
.StartSelector__card {
opacity: 1;
border: 1px solid $secondary-color;
box-shadow: 0 0px 15px -3px rgba(124, 58, 237, 0.5), 0 4px 6px -2px rgba(124, 58, 237, 0.25);
}
}
}

&__card {
// flex: 0 0 300px;
height: 140px;
position: relative;
border: 1px solid #6c7293;
padding: 16px;
border-radius: 16px;
z-index: 1;
transition: all 0.2s ease-out;
cursor: pointer;
opacity: 0.8;
width: 100%;

&:hover {
box-shadow: 0 0px 15px -3px rgba(124, 58, 237, 0.5), 0 4px 6px -2px rgba(124, 58, 237, 0.25);
border: 1px solid $secondary-color;
opacity: 1;
}

&:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to bottom, #000000b3, #0000 50%);
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
}

.slick-center &__card {
// transform: scale(1.1);
opacity: 1;
transition: transform 0.3s ease-out;
}

&__card-title {
font-size: 1rem;
font-weight: bold;
text-align: left;
}

&__card-description {
font-size: 0.8rem;
text-align: left;
word-break: break-all;
}

&__card-text {
position: absolute;
top: 10px;
left: 10px;
}

&__card-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
transition: all 0.2s ease-out;
overflow: hidden;
opacity: 0.7;
z-index: -1;
overflow: hidden;
border-radius: 16px;
}

&__card-characters {
position: absolute;
bottom: 0;
right: 0;
height: 70%;
width: auto;
display: flex;
}

&__card-character {
height: 200%;
width: auto;
}
}
Loading
Loading