Merge branch 'metadataScriptingApi' of github.com:thecodingmachine/workadventure into metadataScriptAPIV2
40
front/dist/index.tmpl.html
vendored
|
@ -29,7 +29,6 @@
|
|||
|
||||
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||
|
||||
<title>WorkAdventure</title>
|
||||
|
@ -47,32 +46,6 @@
|
|||
</aside>
|
||||
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
||||
</div>
|
||||
<div id="activeCam" class="activeCam">
|
||||
<div id="div-myCamVideo" class="video-container">
|
||||
<video id="myCamVideo" autoplay muted></video>
|
||||
<div id="mySoundMeter" class="sound-progress">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-cam-action">
|
||||
<div id="btn-monitor" class="btn-monitor">
|
||||
<img id="monitor" src="resources/logos/monitor.svg">
|
||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
||||
</div>
|
||||
<div id="btn-video" class="btn-video">
|
||||
<img id="cinema" src="resources/logos/cinema.svg">
|
||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||
</div>
|
||||
<div id="btn-micro" class="btn-micro">
|
||||
<img id="microphone" src="resources/logos/microphone.svg">
|
||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cowebsite" class="cowebsite hidden">
|
||||
|
@ -114,23 +87,10 @@
|
|||
<div id="audioplayer" style="visibility: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-playing">
|
||||
<img src="/resources/logos/megaphone.svg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||
</div>
|
||||
<div id="webRtcSetup" class="webrtcsetup">
|
||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</div>
|
||||
<audio id="audio-webrtc-in">
|
||||
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
<audio id="audio-webrtc-out">
|
||||
<source src="/resources/objects/webrtc-out.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
<audio id="report-message">
|
||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
|
|
5
front/dist/resources/fonts/fonts.css
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/*This file is a workaround to allow phaser to load directly this font */
|
||||
@font-face {
|
||||
font-family: "Press Start 2P";
|
||||
src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2');
|
||||
}
|
160
front/dist/resources/html/CustomCharacterScene.html
vendored
|
@ -1,160 +0,0 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#customizeScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
width: 42vw;
|
||||
height: 48vh;
|
||||
/*max-width: 300px;
|
||||
max-height: 48vh;*/
|
||||
overflow: hidden;
|
||||
}
|
||||
#customizeScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#customizeScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#customizeScene section.action {
|
||||
text-align: center;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
top: 100%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 55%;
|
||||
}
|
||||
#customizeScene button {
|
||||
margin: 2px 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#customizeScene button#customizeSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#customizeScene section h6,
|
||||
#customizeScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#customizeScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#customizeScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#customizeScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#customizeScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section input#customizeSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton{
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: -8vh;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonLeft{
|
||||
left: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonRight{
|
||||
right: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonUp{
|
||||
left: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: -20px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonDown{
|
||||
right: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
#customizeScene section.action img{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
#customizeScene section.action a#customizeSceneFormBack img{
|
||||
margin-top: -2px;
|
||||
}
|
||||
#customizeScene section.action button#customizeSceneFormSubmit img{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
#customizeScene {
|
||||
max-width: 160px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 400px) {
|
||||
#customizeScene section.action {
|
||||
top: 92%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="customizeScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Custom your WOKA</h3>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonLeft"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonRight"> > </button>
|
||||
<!--<button class="customizeSceneButton" id="customizeSceneButtonUp"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonDown"> > </button>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a type="submit" id="customizeSceneFormBack">Back <img src="resources/objects/arrow_up.png"/></a>
|
||||
<button type="submit" id="customizeSceneFormSubmit">Next <img src="resources/objects/arrow_up.png"/></button>
|
||||
</section>
|
||||
</form>
|
129
front/dist/resources/html/EnableCameraScene.html
vendored
|
@ -1,129 +0,0 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#enableCameraScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
width: 42vw;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#enableCameraScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#enableCameraScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section.title {
|
||||
position: absolute;
|
||||
top: 1vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 40vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#enableCameraScene button#enableCameraSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#enableCameraScene section h6,
|
||||
#enableCameraScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#enableCameraScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#enableCameraScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#enableCameraScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#enableCameraScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section input#enableCameraSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#enableCameraScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
/*@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#enableCameraScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}*/
|
||||
</style>
|
||||
|
||||
<form id="enableCameraScene" hidden>
|
||||
<!-- FIX me -->
|
||||
<section class="title text-center">
|
||||
<h5>Turn on your camera and microphone</h5>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your camera</h3>
|
||||
<select id="camera">
|
||||
</select>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your michrophone</h3>
|
||||
<select id="michrophone">
|
||||
</select>
|
||||
</section>-->
|
||||
<section class="action">
|
||||
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
|
||||
</section>
|
||||
</form>
|
134
front/dist/resources/html/SelectCompanionScene.html
vendored
|
@ -1,134 +0,0 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCompanionScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCompanionScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCompanionScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCompanionScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 20vh;
|
||||
}
|
||||
#selectCompanionScene button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCompanionScene button#selectCompanionSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCompanionScene section h6,
|
||||
#selectCompanionScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCompanionScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCompanionScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCompanionScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCompanionScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section input#selectCompanionSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCompanionScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button#selectCompanionSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCompanionScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCompanionScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<a herf="#" id="selectCompanionSceneFormBack">No companion</a>
|
||||
<button type="submit" id="selectCompanionSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
5
front/dist/resources/html/gameMenu.html
vendored
|
@ -1,7 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameMenu main{
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
@ -67,7 +64,7 @@
|
|||
<button id="adminConsoleButton">Admin console</button>
|
||||
</section>
|
||||
<section id="socialLinks" hidden>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventurebytcm" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventure.WA" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
|
||||
</section>
|
||||
</main>
|
||||
|
|
3
front/dist/resources/html/gameMenuIcon.html
vendored
|
@ -1,7 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#menuIcon button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameQuality {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
|
3
front/dist/resources/html/gameReport.html
vendored
|
@ -1,7 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameReport {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
|
3
front/dist/resources/html/gameShare.html
vendored
|
@ -1,7 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameShare {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
|
109
front/dist/resources/html/helpCameraSettings.html
vendored
|
@ -1,109 +0,0 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#helpCameraSettings {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 25px auto 0;
|
||||
width: 400px;
|
||||
max-height: calc(48vh - 50px);
|
||||
max-width: 48vw;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#helpCameraSettings h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#helpCameraSettings section {
|
||||
margin: 10px;
|
||||
}
|
||||
#helpCameraSettings section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#helpCameraSettings button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#helpCameraSettings section a{
|
||||
font-size: 12px;
|
||||
text-decoration: underline;
|
||||
color: black;
|
||||
}
|
||||
#helpCameraSettings section h6,
|
||||
#helpCameraSettings section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#helpCameraSettings section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#helpCameraSettings section p{
|
||||
font-size: 8px;
|
||||
margin: 0px 20px;
|
||||
}
|
||||
#helpCameraSettings section p a{
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section p.err{
|
||||
color: #ff0000;
|
||||
}
|
||||
#helpCameraSettings section ul{
|
||||
margin: 6px;
|
||||
}
|
||||
#helpCameraSettings section li{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section img {
|
||||
width: 200px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#helpCameraSettings{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="helpCameraSettings" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Camera/Microphone access needed</h5>
|
||||
<p class="err" id="permissionError">Permission denied</p>
|
||||
<p class="info">You must allow camera and microphone access in your browser.</p>
|
||||
<ul>
|
||||
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
||||
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
||||
</ul>
|
||||
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||
<p id='browserHelpSetting'></p>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<p>If your problem persist, please contact us: <a id="mailto" href="mailto:workadventure@thecodingmachine.com?subject=Support camera and microphone settings" target="_blank"> workadventure@thecodingmachine.com</a>.</p>
|
||||
</section>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a href="#" id="helpCameraSettingsFormRefresh">Refresh</a>
|
||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||
</section>
|
||||
</form>
|
120
front/dist/resources/html/loginScene.html
vendored
|
@ -1,120 +0,0 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#loginScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
max-width: 200px;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
#loginScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#loginScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#loginScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#loginScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#loginScene button#loginSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#loginScene section h6,
|
||||
#loginScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#loginScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#loginScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#loginScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#loginScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section input#loginSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#loginScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#loginScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="loginScene" hidden>
|
||||
<section class="text-center">
|
||||
<img src="resources/logos/logo.png">
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Enter your name</h5>
|
||||
<p class="info">9 chars maximum</p>
|
||||
<p class="err" id="errorLoginScene"></p>
|
||||
</section>
|
||||
<section>
|
||||
<input type="text" name="email" id="loginSceneName">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
142
front/dist/resources/html/selectCharacterScene.html
vendored
|
@ -1,142 +0,0 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCharacterScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCharacterScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCharacterScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCharacterScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 28vh;
|
||||
}
|
||||
#selectCharacterScene button {
|
||||
margin: 10px 0px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCharacterScene button#selectCharacterSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCharacterScene section h6,
|
||||
#selectCharacterScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCharacterScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCharacterScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCharacterScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCharacterScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section input#selectCharacterSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCharacterScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: none;
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: none;
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button#selectCharacterSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCharacterScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: block;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCharacterScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="selectCharacterSceneFormSubmit">Continue</button>
|
||||
<button type="submit" id="selectCharacterSceneFormCustomYourOwnSubmit">Custom your WOKA</button>
|
||||
</section>
|
||||
</form>
|
|
@ -1,7 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
|
|
BIN
front/dist/resources/objects/arrow_down.png
vendored
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
front/dist/resources/objects/arrow_up.png
vendored
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 4.9 KiB |
BIN
front/dist/resources/objects/arrow_up_black.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
|
@ -20,9 +20,11 @@
|
|||
"jasmine": "^3.5.0",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.1.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-check": "^2.1.0",
|
||||
"svelte-loader": "^3.1.1",
|
||||
"svelte-preprocess": "^4.7.3",
|
||||
"ts-loader": "^9.1.2",
|
||||
|
@ -34,24 +36,32 @@
|
|||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/press-start-2p": "^4.3.0",
|
||||
"@types/simple-peer": "^9.6.0",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"axios": "^0.21.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"generic-type-guard": "^3.2.0",
|
||||
"google-protobuf": "^3.13.0",
|
||||
"phaser": "^3.54.0",
|
||||
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
|
||||
"phaser3-rex-plugins": "^1.1.42",
|
||||
"queue-typescript": "^1.0.1",
|
||||
"quill": "1.3.6",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.6.2",
|
||||
"socket.io-client": "^2.3.0"
|
||||
"socket.io-client": "^2.3.0",
|
||||
"standardized-audio-context": "^25.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"build": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||
"start": "run-p templater serve svelte-check-watch",
|
||||
"templater": "cross-env ./templater.sh",
|
||||
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch",
|
||||
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,397 +0,0 @@
|
|||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
||||
export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
|
||||
export const INPUT_TYPE_CONSOLE = 'input-type';
|
||||
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
|
||||
|
||||
export const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||
export const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||
|
||||
interface EventTargetFiles extends EventTarget {
|
||||
files: Array<File>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class ConsoleGlobalMessageManager {
|
||||
|
||||
private readonly divMainConsole: HTMLDivElement;
|
||||
private readonly divMessageConsole: HTMLDivElement;
|
||||
//private readonly divSettingConsole: HTMLDivElement;
|
||||
private readonly buttonMainConsole: HTMLDivElement;
|
||||
private readonly buttonSendMainConsole: HTMLImageElement;
|
||||
//private readonly buttonAdminMainConsole: HTMLImageElement;
|
||||
//private readonly buttonSettingsMainConsole: HTMLImageElement;
|
||||
private activeConsole: boolean = false;
|
||||
private activeMessage: boolean = false;
|
||||
private activeSetting: boolean = false;
|
||||
private userInputManager!: UserInputManager;
|
||||
private static cssLoaded: boolean = false;
|
||||
|
||||
constructor(private Connection: RoomConnection, userInputManager : UserInputManager, private isAdmin: Boolean) {
|
||||
this.buttonMainConsole = document.createElement('div');
|
||||
this.buttonMainConsole.classList.add('console');
|
||||
this.buttonMainConsole.hidden = true;
|
||||
this.divMainConsole = document.createElement('div');
|
||||
this.divMainConsole.className = CLASS_CONSOLE_MESSAGE;
|
||||
this.divMessageConsole = document.createElement('div');
|
||||
this.divMessageConsole.className = 'message';
|
||||
//this.divSettingConsole = document.createElement('div');
|
||||
//this.divSettingConsole.className = 'setting';
|
||||
this.buttonSendMainConsole = document.createElement('img');
|
||||
this.buttonSendMainConsole.id = 'btn-send-message';
|
||||
//this.buttonSettingsMainConsole = document.createElement('img');
|
||||
//this.buttonAdminMainConsole = document.createElement('img');
|
||||
this.userInputManager = userInputManager;
|
||||
this.initialise();
|
||||
|
||||
}
|
||||
|
||||
initialise() {
|
||||
for (const elem of document.getElementsByClassName(CLASS_CONSOLE_MESSAGE)) {
|
||||
elem.remove();
|
||||
}
|
||||
|
||||
const typeConsole = document.createElement('input');
|
||||
typeConsole.id = INPUT_TYPE_CONSOLE;
|
||||
typeConsole.value = MESSAGE_TYPE;
|
||||
typeConsole.type = 'hidden';
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.classList.add('menu')
|
||||
const textMessage = document.createElement('span');
|
||||
textMessage.innerText = "Message";
|
||||
textMessage.classList.add('active');
|
||||
textMessage.addEventListener('click', () => {
|
||||
textMessage.classList.add('active');
|
||||
const messageSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(INPUT_CONSOLE_MESSAGE));
|
||||
messageSection.classList.add('active');
|
||||
|
||||
textAudio.classList.remove('active');
|
||||
const audioSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(UPLOAD_CONSOLE_MESSAGE));
|
||||
audioSection.classList.remove('active');
|
||||
|
||||
typeConsole.value = MESSAGE_TYPE;
|
||||
});
|
||||
menu.appendChild(textMessage);
|
||||
const textAudio = document.createElement('span');
|
||||
textAudio.innerText = "Audio";
|
||||
textAudio.addEventListener('click', () => {
|
||||
textAudio.classList.add('active');
|
||||
const audioSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(UPLOAD_CONSOLE_MESSAGE));
|
||||
audioSection.classList.add('active');
|
||||
|
||||
textMessage.classList.remove('active');
|
||||
const messageSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(INPUT_CONSOLE_MESSAGE));
|
||||
messageSection.classList.remove('active');
|
||||
|
||||
typeConsole.value = AUDIO_TYPE;
|
||||
});
|
||||
menu.appendChild(textMessage);
|
||||
menu.appendChild(textAudio);
|
||||
this.divMessageConsole.appendChild(menu);
|
||||
|
||||
this.buttonSendMainConsole.src = 'resources/logos/send-yellow.svg';
|
||||
this.buttonSendMainConsole.addEventListener('click', () => {
|
||||
if(this.activeMessage){
|
||||
this.disabledMessageConsole();
|
||||
}else{
|
||||
this.activeMessageConsole();
|
||||
}
|
||||
});
|
||||
|
||||
/*this.buttonAdminMainConsole.src = 'resources/logos/setting-yellow.svg';
|
||||
this.buttonAdminMainConsole.addEventListener('click', () => {
|
||||
window.open(ADMIN_URL, '_blank');
|
||||
});*/
|
||||
|
||||
/*this.buttonSettingsMainConsole.src = 'resources/logos/monitor-yellow.svg';
|
||||
this.buttonSettingsMainConsole.addEventListener('click', () => {
|
||||
if(this.activeSetting){
|
||||
this.disabledSettingConsole();
|
||||
}else{
|
||||
this.activeSettingConsole();
|
||||
}
|
||||
});*/
|
||||
|
||||
this.divMessageConsole.appendChild(typeConsole);
|
||||
|
||||
/*if(this.isAdmin) {
|
||||
this.buttonMainConsole.appendChild(this.buttonSendMainConsole);
|
||||
//this.buttonMainConsole.appendChild(this.buttonAdminMainConsole);
|
||||
}*/
|
||||
this.createTextMessagePart();
|
||||
this.createUploadAudioPart();
|
||||
//this.buttonMainConsole.appendChild(this.buttonSettingsMainConsole);
|
||||
|
||||
this.divMainConsole.appendChild(this.buttonMainConsole);
|
||||
this.divMainConsole.appendChild(this.divMessageConsole);
|
||||
//this.divMainConsole.appendChild(this.divSettingConsole);
|
||||
|
||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
mainSectionDiv.appendChild(this.divMainConsole);
|
||||
}
|
||||
|
||||
createTextMessagePart(){
|
||||
const div = document.createElement('div');
|
||||
div.id = INPUT_CONSOLE_MESSAGE
|
||||
const buttonSend = document.createElement('button');
|
||||
buttonSend.innerText = 'Send';
|
||||
buttonSend.classList.add('btn');
|
||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||
this.sendMessage();
|
||||
this.disabledMessageConsole();
|
||||
});
|
||||
const buttonDiv = document.createElement('div');
|
||||
buttonDiv.classList.add('btn-action');
|
||||
buttonDiv.appendChild(buttonSend)
|
||||
|
||||
const section = document.createElement('section');
|
||||
section.id = this.getSectionId(INPUT_CONSOLE_MESSAGE);
|
||||
section.classList.add('active');
|
||||
section.appendChild(div);
|
||||
section.appendChild(buttonDiv);
|
||||
this.divMessageConsole.appendChild(section);
|
||||
|
||||
(async () => {
|
||||
try{
|
||||
// Start loading CSS
|
||||
const cssPromise = ConsoleGlobalMessageManager.loadCss();
|
||||
// Import quill
|
||||
const {default: Quill}:any = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
// Wait for CSS to be loaded
|
||||
await cssPromise;
|
||||
|
||||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{'header': 1}, {'header': 2}], // custom button values
|
||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
|
||||
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
|
||||
[{'direction': 'rtl'}], // text direction
|
||||
|
||||
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
|
||||
[{'header': [1, 2, 3, 4, 5, 6, false]}],
|
||||
|
||||
[{'color': []}, {'background': []}], // dropdown with defaults from theme
|
||||
[{'font': []}],
|
||||
[{'align': []}],
|
||||
|
||||
['clean'],
|
||||
|
||||
['link', 'image', 'video']
|
||||
// remove formatting button
|
||||
];
|
||||
|
||||
new Quill(`#${INPUT_CONSOLE_MESSAGE}`, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions
|
||||
},
|
||||
});
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
createUploadAudioPart(){
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('upload');
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.setAttribute('for', UPLOAD_CONSOLE_MESSAGE);
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.setAttribute('for', UPLOAD_CONSOLE_MESSAGE);
|
||||
img.src = 'resources/logos/music-file.svg';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.id = UPLOAD_CONSOLE_MESSAGE
|
||||
input.addEventListener('input', (e: Event) => {
|
||||
if(!e.target){
|
||||
return;
|
||||
}
|
||||
const eventTarget : EventTargetFiles = (e.target as EventTargetFiles);
|
||||
if(!eventTarget || !eventTarget.files || eventTarget.files.length === 0){
|
||||
return;
|
||||
}
|
||||
const file : File = eventTarget.files[0];
|
||||
|
||||
if(!file){
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
HtmlUtils.removeElementByIdOrFail('audi-message-filename');
|
||||
}catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
const p = document.createElement('p');
|
||||
p.id = 'audi-message-filename';
|
||||
p.innerText = `${file.name} : ${this.getFileSize(file.size)}`;
|
||||
label.appendChild(p);
|
||||
});
|
||||
|
||||
label.appendChild(img);
|
||||
div.appendChild(label);
|
||||
div.appendChild(input);
|
||||
|
||||
const buttonSend = document.createElement('button');
|
||||
buttonSend.innerText = 'Send';
|
||||
buttonSend.classList.add('btn');
|
||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||
this.sendMessage();
|
||||
this.disabledMessageConsole();
|
||||
});
|
||||
const buttonDiv = document.createElement('div');
|
||||
buttonDiv.classList.add('btn-action');
|
||||
buttonDiv.appendChild(buttonSend)
|
||||
|
||||
const section = document.createElement('section');
|
||||
section.id = this.getSectionId(UPLOAD_CONSOLE_MESSAGE);
|
||||
section.appendChild(div);
|
||||
section.appendChild(buttonDiv);
|
||||
this.divMessageConsole.appendChild(section);
|
||||
}
|
||||
|
||||
private static loadCss(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (ConsoleGlobalMessageManager.cssLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const fileref = document.createElement("link")
|
||||
fileref.setAttribute("rel", "stylesheet")
|
||||
fileref.setAttribute("type", "text/css")
|
||||
fileref.setAttribute("href", "https://cdn.quilljs.com/1.3.7/quill.snow.css");
|
||||
document.getElementsByTagName("head")[0].appendChild(fileref);
|
||||
ConsoleGlobalMessageManager.cssLoaded = true;
|
||||
fileref.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
fileref.onerror = () => {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage(){
|
||||
const inputType = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(INPUT_TYPE_CONSOLE);
|
||||
if(AUDIO_TYPE !== inputType.value && MESSAGE_TYPE !== inputType.value){
|
||||
throw "Error event type";
|
||||
}
|
||||
if(AUDIO_TYPE === inputType.value){
|
||||
return this.sendAudioMessage();
|
||||
}
|
||||
return this.sendTextMessage();
|
||||
}
|
||||
|
||||
private sendTextMessage(){
|
||||
const elements = document.getElementsByClassName('ql-editor');
|
||||
const quillEditor = elements.item(0);
|
||||
if(!quillEditor){
|
||||
throw "Error get quill node";
|
||||
}
|
||||
const GlobalMessage : PlayGlobalMessageInterface = {
|
||||
id: "1", // FIXME: use another ID?
|
||||
message: quillEditor.innerHTML,
|
||||
type: MESSAGE_TYPE
|
||||
};
|
||||
quillEditor.innerHTML = '';
|
||||
this.Connection.emitGlobalMessage(GlobalMessage);
|
||||
}
|
||||
|
||||
private async sendAudioMessage(){
|
||||
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(UPLOAD_CONSOLE_MESSAGE);
|
||||
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
||||
if(!selectedFile){
|
||||
throw 'no file selected';
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('file', selectedFile);
|
||||
const res = await this.Connection.uploadAudio(fd);
|
||||
|
||||
const GlobalMessage : PlayGlobalMessageInterface = {
|
||||
id: (res as {id: string}).id,
|
||||
message: (res as {path: string}).path,
|
||||
type: AUDIO_TYPE
|
||||
};
|
||||
inputAudio.value = '';
|
||||
try {
|
||||
HtmlUtils.removeElementByIdOrFail('audi-message-filename');
|
||||
}catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
this.Connection.emitGlobalMessage(GlobalMessage);
|
||||
}
|
||||
|
||||
active(){
|
||||
this.userInputManager.disableControls();
|
||||
this.divMainConsole.style.top = '0';
|
||||
this.activeConsole = true;
|
||||
}
|
||||
|
||||
disabled(){
|
||||
this.userInputManager.initKeyBoardEvent();
|
||||
this.activeConsole = false;
|
||||
this.divMainConsole.style.top = '-80%';
|
||||
}
|
||||
|
||||
activeMessageConsole(){
|
||||
if(!this.isAdmin){
|
||||
throw "User is not admin";
|
||||
}
|
||||
if(this.activeMessage){
|
||||
this.disabledMessageConsole();
|
||||
return;
|
||||
}
|
||||
this.activeMessage = true;
|
||||
this.active();
|
||||
this.divMessageConsole.classList.add('active');
|
||||
this.buttonMainConsole.hidden = false;
|
||||
this.buttonSendMainConsole.classList.add('active');
|
||||
//if button not
|
||||
try{
|
||||
HtmlUtils.getElementByIdOrFail('btn-send-message');
|
||||
}catch (e) {
|
||||
this.buttonMainConsole.appendChild(this.buttonSendMainConsole);
|
||||
}
|
||||
}
|
||||
|
||||
disabledMessageConsole(){
|
||||
this.activeMessage = false;
|
||||
this.disabled();
|
||||
this.buttonMainConsole.hidden = true;
|
||||
this.divMessageConsole.classList.remove('active');
|
||||
this.buttonSendMainConsole.classList.remove('active');
|
||||
}
|
||||
|
||||
private getSectionId(id: string) : string {
|
||||
return `section-${id}`;
|
||||
}
|
||||
|
||||
private getFileSize(number: number) :string {
|
||||
if (number < 1024) {
|
||||
return number + 'bytes';
|
||||
} else if (number >= 1024 && number < 1048576) {
|
||||
return (number / 1024).toFixed(1) + 'KB';
|
||||
} else if (number >= 1048576) {
|
||||
return (number / 1048576).toFixed(1) + 'MB';
|
||||
}else{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||
import {soundManager} from "../Phaser/Game/SoundManager";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export class GlobalMessageManager {
|
||||
|
||||
|
@ -34,54 +36,17 @@ export class GlobalMessageManager {
|
|||
previousMessage.remove();
|
||||
}
|
||||
|
||||
if(AUDIO_TYPE === message.type){
|
||||
if(AdminMessageEventTypes.audio === message.type){
|
||||
this.playAudioMessage(message.id, message.message);
|
||||
}
|
||||
|
||||
if(MESSAGE_TYPE === message.type){
|
||||
if(AdminMessageEventTypes.admin === message.type){
|
||||
this.playTextMessage(message.id, message.message);
|
||||
}
|
||||
}
|
||||
|
||||
private playAudioMessage(messageId : string, urlMessage: string){
|
||||
//delete previous elements
|
||||
const previousDivAudio = document.getElementsByClassName('audio-playing');
|
||||
for(let i = 0; i < previousDivAudio.length; i++){
|
||||
previousDivAudio[i].remove();
|
||||
}
|
||||
|
||||
//create new element
|
||||
const divAudio : HTMLDivElement = document.createElement('div');
|
||||
divAudio.id = `audio-playing-${messageId}`;
|
||||
divAudio.classList.add('audio-playing');
|
||||
const imgAudio : HTMLImageElement = document.createElement('img');
|
||||
imgAudio.src = '/resources/logos/megaphone.svg';
|
||||
const pAudio : HTMLParagraphElement = document.createElement('p');
|
||||
pAudio.textContent = 'Message audio'
|
||||
divAudio.appendChild(imgAudio);
|
||||
divAudio.appendChild(pAudio);
|
||||
|
||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
mainSectionDiv.appendChild(divAudio);
|
||||
|
||||
const messageAudio : HTMLAudioElement = document.createElement('audio');
|
||||
messageAudio.id = this.getHtmlMessageId(messageId);
|
||||
messageAudio.autoplay = true;
|
||||
messageAudio.style.display = 'none';
|
||||
messageAudio.onended = () => {
|
||||
divAudio.classList.remove('active');
|
||||
messageAudio.remove();
|
||||
setTimeout(() => {
|
||||
divAudio.remove();
|
||||
}, 1000);
|
||||
}
|
||||
messageAudio.onplay = () => {
|
||||
divAudio.classList.add('active');
|
||||
}
|
||||
const messageAudioSource : HTMLSourceElement = document.createElement('source');
|
||||
messageAudioSource.src = `${UPLOADER_URL}${urlMessage}`;
|
||||
messageAudio.appendChild(messageAudioSource);
|
||||
mainSectionDiv.appendChild(messageAudio);
|
||||
private playAudioMessage(messageId : string, urlMessage: string) {
|
||||
soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
|
||||
}
|
||||
|
||||
private playTextMessage(messageId : string, htmlMessage: string){
|
||||
|
|
|
@ -44,7 +44,13 @@ export class TypeMessageExt implements TypeMessageInterface{
|
|||
mainSectionDiv.appendChild(div);
|
||||
|
||||
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
|
||||
reportMessageAudio.play();
|
||||
// FIXME: this will fail on iOS
|
||||
// We should move the sound playing into the GameScene and listen to the event of a report using a store
|
||||
try {
|
||||
reportMessageAudio.play();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
this.nbSecond = this.maxNbSecond;
|
||||
setTimeout((c) => {
|
||||
|
|
|
@ -5,9 +5,7 @@ import type { ChatEvent } from './ChatEvent';
|
|||
import type { ClosePopupEvent } from './ClosePopupEvent';
|
||||
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||
import type { GoToPageEvent } from './GoToPageEvent';
|
||||
import type { MenuItemClickedEvent } from './MenuItemClickedEvent';
|
||||
import type { MenuItemRegisterEvent } from './MenuItemRegisterEvent';
|
||||
import type { HasPlayerMovedEvent } from './HasPlayerMovedEvent';
|
||||
import type { LoadPageEvent } from './LoadPageEvent';
|
||||
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
||||
import type { OpenTabEvent } from './OpenTabEvent';
|
||||
|
@ -17,16 +15,18 @@ import type { LayerEvent } from './LayerEvent';
|
|||
import type { SetPropertyEvent } from "./setPropertyEvent";
|
||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
||||
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||
import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent';
|
||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||
import type { ChangeTileEvent } from "./ChangeTileEvent";
|
||||
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T
|
||||
}
|
||||
|
||||
export type IframeEventMap = {
|
||||
getState: GameStateEvent,
|
||||
registerMenuCommand: MenuItemRegisterEvent
|
||||
//getState: GameStateEvent,
|
||||
loadPage: LoadPageEvent
|
||||
chat: ChatEvent,
|
||||
openPopup: OpenPopupEvent
|
||||
closePopup: ClosePopupEvent
|
||||
|
@ -43,11 +43,12 @@ export type IframeEventMap = {
|
|||
hideLayer: LayerEvent
|
||||
setProperty: SetPropertyEvent
|
||||
getDataLayer: undefined
|
||||
//tilsetEvent: TilesetEvent
|
||||
loadSound: LoadSoundEvent
|
||||
playSound: PlaySoundEvent
|
||||
stopSound: null
|
||||
changeTile: ChangeTileEvent
|
||||
getState: undefined,
|
||||
registerMenuCommand: MenuItemRegisterEvent
|
||||
}
|
||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||
type: T;
|
||||
|
|
13
front/src/Api/Events/LoadPageEvent.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isLoadPageEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>;
|
|
@ -1,10 +0,0 @@
|
|||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isMenuItemRegisterEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
menutItem: tg.isString
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a new menu item.
|
||||
*/
|
||||
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
|
@ -8,3 +8,5 @@ export const isMenuItemClickedEvent =
|
|||
* A message sent from the game to the iFrame when a menu item is clicked.
|
||||
*/
|
||||
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>;
|
||||
|
||||
|
25
front/src/Api/Events/ui/MenuItemRegisterEvent.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import * as tg from "generic-type-guard";
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export const isMenuItemRegisterEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
menutItem: tg.isString
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a new menu item.
|
||||
*/
|
||||
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
||||
|
||||
export const isMenuItemRegisterIframeEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
type: tg.isSingletonString("registerMenuCommand"),
|
||||
data: isMenuItemRegisterEvent
|
||||
}).get();
|
||||
|
||||
|
||||
const _registerMenuCommandStream: Subject<string> = new Subject();
|
||||
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
||||
|
||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
||||
_registerMenuCommandStream.next(event.menutItem)
|
||||
}
|
|
@ -1,30 +1,36 @@
|
|||
import {Subject} from "rxjs";
|
||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import type {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
||||
import type {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
||||
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
||||
import {scriptUtils} from "./ScriptUtils";
|
||||
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
||||
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
||||
import {
|
||||
IframeEvent,
|
||||
IframeEventMap,
|
||||
IframeResponseEvent,
|
||||
IframeResponseEventMap,
|
||||
isIframeEventWrapper,
|
||||
TypedMessageEvent
|
||||
} from "./Events/IframeEvent";
|
||||
import type {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
||||
import {isPlaySoundEvent, PlaySoundEvent} from "./Events/PlaySoundEvent";
|
||||
import {isStopSoundEvent, StopSoundEvent} from "./Events/StopSoundEvent";
|
||||
import {isLoadSoundEvent, LoadSoundEvent} from "./Events/LoadSoundEvent";
|
||||
import {isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
|
||||
import {isLayerEvent, LayerEvent} from "./Events/LayerEvent";
|
||||
import {isMenuItemRegisterEvent,} from "./Events/ui/MenuItemRegisterEvent";
|
||||
import type {DataLayerEvent} from "./Events/DataLayerEvent";
|
||||
import type {GameStateEvent} from "./Events/GameStateEvent";
|
||||
import type {HasPlayerMovedEvent} from "./Events/HasPlayerMovedEvent";
|
||||
import {isLoadPageEvent} from "./Events/LoadPageEvent";
|
||||
import {handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent} from "./Events/ui/MenuItemRegisterEvent";
|
||||
import {ChangeTileEvent, isChangeTileEvent} from "./Events/ChangeTileEvent";
|
||||
|
||||
import { Subject } from "rxjs";
|
||||
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||
import { scriptUtils } from "./ScriptUtils";
|
||||
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||
import { isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
|
||||
import type { GameStateEvent } from './Events/GameStateEvent';
|
||||
import type { HasPlayerMovedEvent } from './Events/HasPlayerMovedEvent';
|
||||
import { Math } from 'phaser';
|
||||
import type { DataLayerEvent } from "./Events/DataLayerEvent";
|
||||
import { isMenuItemRegisterEvent } from './Events/MenuItemRegisterEvent';
|
||||
import type { MenuItemClickedEvent } from './Events/MenuItemClickedEvent';
|
||||
//import { isTilesetEvent, TilesetEvent } from "./Events/TilesetEvent";
|
||||
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
|
||||
import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
||||
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
||||
import { isChangeTileEvent, ChangeTileEvent } from './Events/ChangeTileEvent';
|
||||
/**
|
||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||
* Also allows to send messages to those iframes.
|
||||
|
@ -43,6 +49,9 @@ class IframeListener {
|
|||
private readonly _goToPageStream: Subject<GoToPageEvent> = new Subject();
|
||||
public readonly goToPageStream = this._goToPageStream.asObservable();
|
||||
|
||||
private readonly _loadPageStream: Subject<string> = new Subject();
|
||||
public readonly loadPageStream = this._loadPageStream.asObservable();
|
||||
|
||||
private readonly _openCoWebSiteStream: Subject<OpenCoWebSiteEvent> = new Subject();
|
||||
public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
|
||||
|
||||
|
@ -82,8 +91,8 @@ class IframeListener {
|
|||
private readonly _registerMenuCommandStream: Subject<string> = new Subject();
|
||||
public readonly registerMenuCommandStream = this._registerMenuCommandStream.asObservable();
|
||||
|
||||
/* private readonly _tilesetLoaderStream: Subject<TilesetEvent> = new Subject();
|
||||
public readonly tilesetLoaderStream = this._tilesetLoaderStream.asObservable();*/
|
||||
private readonly _unregisterMenuCommandStream: Subject<string> = new Subject();
|
||||
public readonly unregisterMenuCommandStream = this._unregisterMenuCommandStream.asObservable();
|
||||
|
||||
private readonly _playSoundStream: Subject<PlaySoundEvent> = new Subject();
|
||||
public readonly playSoundStream = this._playSoundStream.asObservable();
|
||||
|
@ -98,6 +107,7 @@ class IframeListener {
|
|||
public readonly changeTileStream = this._changeTileStream.asObservable();
|
||||
|
||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||
private sendPlayerMove: boolean = false;
|
||||
|
||||
|
@ -106,14 +116,17 @@ class IframeListener {
|
|||
// Do we trust the sender of this message?
|
||||
// Let's only accept messages from the iframe that are allowed.
|
||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||
let foundSrc: string | null = null;
|
||||
for (const iframe of this.iframes) {
|
||||
let foundSrc: string | undefined;
|
||||
|
||||
let iframe: HTMLIFrameElement;
|
||||
for (iframe of this.iframes) {
|
||||
if (iframe.contentWindow === message.source) {
|
||||
foundSrc = iframe.src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundSrc) {
|
||||
|
||||
if (foundSrc === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -138,6 +151,9 @@ class IframeListener {
|
|||
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||
scriptUtils.goToPage(payload.data.url);
|
||||
}
|
||||
else if (payload.type === 'loadPage' && isLoadPageEvent(payload.data)) {
|
||||
this._loadPageStream.next(payload.data.url);
|
||||
}
|
||||
else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
||||
this._playSoundStream.next(payload.data);
|
||||
}
|
||||
|
@ -148,11 +164,7 @@ class IframeListener {
|
|||
this._loadSoundStream.next(payload.data);
|
||||
}
|
||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||
const scriptUrl = [...this.scripts.keys()].find(key => {
|
||||
return this.scripts.get(key)?.contentWindow == message.source
|
||||
})
|
||||
|
||||
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||
scriptUtils.openCoWebsite(payload.data.url, foundSrc);
|
||||
}
|
||||
|
||||
else if (payload.type === 'closeCoWebSite') {
|
||||
|
@ -174,6 +186,13 @@ class IframeListener {
|
|||
this.sendPlayerMove = true
|
||||
} else if (payload.type == "getDataLayer") {
|
||||
this._dataLayerChangeStream.next();
|
||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||
const data = payload.data.menutItem;
|
||||
// @ts-ignore
|
||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||
this._unregisterMenuCommandStream.next(data);
|
||||
})
|
||||
handleMenuItemRegistrationEvent(payload.data)
|
||||
} else if (payload.type == "registerMenuCommand" && isMenuItemRegisterEvent(payload.data)) {
|
||||
this._registerMenuCommandStream.next(payload.data.menutItem)
|
||||
} else if (payload.type == "changeTile" && isChangeTileEvent(payload.data)) {
|
||||
|
@ -192,7 +211,7 @@ class IframeListener {
|
|||
}
|
||||
|
||||
|
||||
sendFrozenGameStateEvent(gameStateEvent: GameStateEvent) {
|
||||
sendGameStateEvent(gameStateEvent: GameStateEvent) {
|
||||
this.postMessage({
|
||||
'type': 'gameState',
|
||||
'data': gameStateEvent
|
||||
|
@ -204,9 +223,13 @@ class IframeListener {
|
|||
*/
|
||||
registerIframe(iframe: HTMLIFrameElement): void {
|
||||
this.iframes.add(iframe);
|
||||
this.iframeCloseCallbacks.set(iframe, []);
|
||||
}
|
||||
|
||||
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||
this.iframeCloseCallbacks.get(iframe)?.forEach(callback => {
|
||||
callback();
|
||||
});
|
||||
this.iframes.delete(iframe);
|
||||
}
|
||||
|
||||
|
@ -216,7 +239,7 @@ class IframeListener {
|
|||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||
// Using external iframe mode (
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl);
|
||||
|
||||
|
@ -231,25 +254,24 @@ class IframeListener {
|
|||
} else {
|
||||
// production code
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add('allow-scripts');
|
||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||
|
||||
const html = '<!doctype html>\n' +
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc = '<!doctype html>\n' +
|
||||
'\n' +
|
||||
'<html lang="en">\n' +
|
||||
'<head>\n' +
|
||||
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
||||
'<script src="' + scriptUrl + '" ></script>\n' +
|
||||
'<title></title>\n' +
|
||||
'</head>\n' +
|
||||
'</html>\n';
|
||||
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc = html;
|
||||
|
||||
document.body.prepend(iframe);
|
||||
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
|
@ -259,12 +281,12 @@ class IframeListener {
|
|||
|
||||
}
|
||||
|
||||
private getIFrameId(scriptUrl: string): string {
|
||||
private static getIFrameId(scriptUrl: string): string {
|
||||
return 'script' + btoa(scriptUrl);
|
||||
}
|
||||
|
||||
unregisterScript(scriptUrl: string): void {
|
||||
const iFrameId = this.getIFrameId(scriptUrl);
|
||||
const iFrameId = IframeListener.getIFrameId(scriptUrl);
|
||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||
if (!iframe) {
|
||||
throw new Error('Unknown iframe for script "' + scriptUrl + '"');
|
||||
|
@ -275,15 +297,6 @@ class IframeListener {
|
|||
this.scripts.delete(scriptUrl);
|
||||
}
|
||||
|
||||
sendMenuClickedEvent(menuItem: string) {
|
||||
this.postMessage({
|
||||
'type': 'menuItemClicked',
|
||||
'data': {
|
||||
menuItem: menuItem,
|
||||
} as MenuItemClickedEvent
|
||||
});
|
||||
}
|
||||
|
||||
sendUserInputChat(message: string) {
|
||||
this.postMessage({
|
||||
'type': 'userInputChat',
|
||||
|
@ -333,7 +346,7 @@ class IframeListener {
|
|||
/**
|
||||
* Sends the message... to all allowed iframes.
|
||||
*/
|
||||
private postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.contentWindow?.postMessage(message, '*');
|
||||
}
|
||||
|
|
31
front/src/Api/iframe/IframeApiContribution.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import type * as tg from "generic-type-guard";
|
||||
import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent';
|
||||
|
||||
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
||||
window.parent.postMessage(content, "*")
|
||||
}
|
||||
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
||||
|
||||
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
||||
|
||||
typeChecker: Guard,
|
||||
callback: (payloadData: T) => void
|
||||
}
|
||||
|
||||
export interface IframeCallbackContribution<Key extends keyof IframeResponseEventMap> extends IframeCallback<Key> {
|
||||
|
||||
type: Key
|
||||
}
|
||||
|
||||
/**
|
||||
* !! be aware that the implemented attributes (addMethodsAtRoot and subObjectIdentifier) must be readonly
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
export abstract class IframeApiContribution<T extends {
|
||||
callbacks: Array<IframeCallbackContribution<keyof IframeResponseEventMap>>,
|
||||
}> {
|
||||
|
||||
abstract callbacks: T["callbacks"]
|
||||
}
|
39
front/src/Api/iframe/Sound/Sound.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import {sendToWorkadventure} from "../IframeApiContribution";
|
||||
import type {LoadSoundEvent} from "../../Events/LoadSoundEvent";
|
||||
import type {PlaySoundEvent} from "../../Events/PlaySoundEvent";
|
||||
import type {StopSoundEvent} from "../../Events/StopSoundEvent";
|
||||
import SoundConfig = Phaser.Types.Sound.SoundConfig;
|
||||
|
||||
export class Sound {
|
||||
constructor(private url: string) {
|
||||
sendToWorkadventure({
|
||||
"type": 'loadSound',
|
||||
"data": {
|
||||
url: this.url,
|
||||
} as LoadSoundEvent
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public play(config: SoundConfig) {
|
||||
sendToWorkadventure({
|
||||
"type": 'playSound',
|
||||
"data": {
|
||||
url: this.url,
|
||||
config
|
||||
} as PlaySoundEvent
|
||||
|
||||
});
|
||||
return this.url;
|
||||
}
|
||||
public stop() {
|
||||
sendToWorkadventure({
|
||||
"type": 'stopSound',
|
||||
"data": {
|
||||
url: this.url,
|
||||
} as StopSoundEvent
|
||||
|
||||
});
|
||||
return this.url;
|
||||
}
|
||||
}
|
18
front/src/Api/iframe/Ui/ButtonDescriptor.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import type {Popup} from "./Popup";
|
||||
|
||||
export type ButtonClickedCallback = (popup: Popup) => void;
|
||||
|
||||
export interface ButtonDescriptor {
|
||||
/**
|
||||
* The label of the button
|
||||
*/
|
||||
label: string,
|
||||
/**
|
||||
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
|
||||
*/
|
||||
className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled",
|
||||
/**
|
||||
* Callback called if the button is pressed
|
||||
*/
|
||||
callback: ButtonClickedCallback,
|
||||
}
|
11
front/src/Api/iframe/Ui/MenuItem.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { MenuItemClickedEvent } from '../../Events/ui/MenuItemClickedEvent';
|
||||
import { iframeListener } from '../../IframeListener';
|
||||
|
||||
export function sendMenuClickedEvent(menuItem: string) {
|
||||
iframeListener.postMessage({
|
||||
'type': 'menuItemClicked',
|
||||
'data': {
|
||||
menuItem: menuItem,
|
||||
} as MenuItemClickedEvent
|
||||
});
|
||||
}
|
19
front/src/Api/iframe/Ui/Popup.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {sendToWorkadventure} from "../IframeApiContribution";
|
||||
import type {ClosePopupEvent} from "../../Events/ClosePopupEvent";
|
||||
|
||||
export class Popup {
|
||||
constructor(private id: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the popup
|
||||
*/
|
||||
public close(): void {
|
||||
sendToWorkadventure({
|
||||
'type': 'closePopup',
|
||||
'data': {
|
||||
'popupId': this.id,
|
||||
} as ClosePopupEvent
|
||||
});
|
||||
}
|
||||
}
|
38
front/src/Api/iframe/chat.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import type { ChatEvent } from '../Events/ChatEvent'
|
||||
import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent'
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import {Subject} from "rxjs";
|
||||
|
||||
const chatStream = new Subject<string>();
|
||||
|
||||
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
|
||||
|
||||
callbacks = [apiCallback({
|
||||
callback: (event: UserInputChatEvent) => {
|
||||
chatStream.next(event.message);
|
||||
},
|
||||
type: "userInputChat",
|
||||
typeChecker: isUserInputChatEvent
|
||||
})]
|
||||
|
||||
|
||||
sendChatMessage(message: string, author: string) {
|
||||
sendToWorkadventure({
|
||||
type: 'chat',
|
||||
data: {
|
||||
'message': message,
|
||||
'author': author
|
||||
} as ChatEvent
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to messages sent by the local user, in the chat.
|
||||
*/
|
||||
onChatMessage(callback: (message: string) => void) {
|
||||
chatStream.subscribe(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkadventureChatCommands()
|
16
front/src/Api/iframe/controls.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
|
||||
class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
|
||||
callbacks = []
|
||||
|
||||
disablePlayerControls(): void {
|
||||
sendToWorkadventure({ 'type': 'disablePlayerControls', data: null });
|
||||
}
|
||||
|
||||
restorePlayerControls(): void {
|
||||
sendToWorkadventure({ 'type': 'restorePlayerControls', data: null });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureControlsCommands();
|
57
front/src/Api/iframe/nav.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import type { GoToPageEvent } from '../Events/GoToPageEvent';
|
||||
import type { OpenTabEvent } from '../Events/OpenTabEvent';
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent";
|
||||
import type {LoadPageEvent} from "../Events/LoadPageEvent";
|
||||
|
||||
|
||||
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
|
||||
callbacks = []
|
||||
|
||||
|
||||
openTab(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'openTab',
|
||||
"data": {
|
||||
url
|
||||
} as OpenTabEvent
|
||||
});
|
||||
}
|
||||
|
||||
goToPage(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'goToPage',
|
||||
"data": {
|
||||
url
|
||||
} as GoToPageEvent
|
||||
});
|
||||
}
|
||||
|
||||
goToRoom(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'loadPage',
|
||||
"data": {
|
||||
url
|
||||
} as LoadPageEvent
|
||||
});
|
||||
}
|
||||
|
||||
openCoWebSite(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'openCoWebSite',
|
||||
"data": {
|
||||
url
|
||||
} as OpenCoWebSiteEvent
|
||||
});
|
||||
}
|
||||
|
||||
closeCoWebSite(): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'closeCoWebSite',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureNavigationCommands();
|
29
front/src/Api/iframe/player.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution";
|
||||
import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent";
|
||||
import {Subject} from "rxjs";
|
||||
import {apiCallback} from "./registeredCallbacks";
|
||||
import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent";
|
||||
|
||||
const moveStream = new Subject<HasPlayerMovedEvent>();
|
||||
|
||||
class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: 'hasPlayerMoved',
|
||||
typeChecker: isHasPlayerMovedEvent,
|
||||
callback: (payloadData) => {
|
||||
moveStream.next(payloadData);
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
|
||||
moveStream.subscribe(callback);
|
||||
sendToWorkadventure({
|
||||
type: 'onPlayerMove',
|
||||
data: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkadventurePlayerCommands();
|
16
front/src/Api/iframe/registeredCallbacks.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import type {IframeResponseEventMap} from "../../Api/Events/IframeEvent";
|
||||
import type {IframeCallback} from "../../Api/iframe/IframeApiContribution";
|
||||
import type {IframeCallbackContribution} from "../../Api/iframe/IframeApiContribution";
|
||||
|
||||
export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: IframeCallback<K> } = {}
|
||||
|
||||
export function apiCallback<T extends keyof IframeResponseEventMap>(callbackData: IframeCallbackContribution<T>): IframeCallbackContribution<keyof IframeResponseEventMap> {
|
||||
const iframeCallback = {
|
||||
typeChecker: callbackData.typeChecker,
|
||||
callback: callbackData.callback
|
||||
} as IframeCallback<T>;
|
||||
|
||||
const newCallback = { [callbackData.type]: iframeCallback };
|
||||
Object.assign(registeredCallbacks, newCallback)
|
||||
return callbackData as unknown as IframeCallbackContribution<keyof IframeResponseEventMap>;
|
||||
}
|
148
front/src/Api/iframe/room.ts
Normal file
|
@ -0,0 +1,148 @@
|
|||
import { Subject } from "rxjs";
|
||||
import { EnterLeaveEvent, isEnterLeaveEvent } from '../Events/EnterLeaveEvent';
|
||||
import {IframeApiContribution, sendToWorkadventure} from './IframeApiContribution';
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import type {LayerEvent} from "../Events/LayerEvent";
|
||||
import type {SetPropertyEvent} from "../Events/setPropertyEvent";
|
||||
import type {GameStateEvent} from "../Events/GameStateEvent";
|
||||
import type {ITiledMap} from "../../Phaser/Map/ITiledMap";
|
||||
import type {DataLayerEvent} from "../Events/DataLayerEvent";
|
||||
import {isGameStateEvent} from "../Events/GameStateEvent";
|
||||
import {isDataLayerEvent} from "../Events/DataLayerEvent";
|
||||
|
||||
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const dataLayerResolver = new Subject<DataLayerEvent>();
|
||||
const stateResolvers = new Subject<GameStateEvent>();
|
||||
|
||||
let immutableData: GameStateEvent;
|
||||
|
||||
interface Room {
|
||||
id: string,
|
||||
mapUrl: string,
|
||||
map: ITiledMap,
|
||||
startLayer: string | null
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string | undefined,
|
||||
nickName: string | null,
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
interface TileDescriptor {
|
||||
x: number
|
||||
y: number
|
||||
tile: number | string
|
||||
layer: string
|
||||
}
|
||||
|
||||
|
||||
function getGameState(): Promise<GameStateEvent> {
|
||||
if (immutableData) {
|
||||
return Promise.resolve(immutableData);
|
||||
}
|
||||
else {
|
||||
return new Promise<GameStateEvent>((resolver, thrower) => {
|
||||
stateResolvers.subscribe(resolver);
|
||||
sendToWorkadventure({type: "getState", data: null});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getDataLayer(): Promise<DataLayerEvent> {
|
||||
return new Promise<DataLayerEvent>((resolver, thrower) => {
|
||||
dataLayerResolver.subscribe(resolver);
|
||||
sendToWorkadventure({type: "getDataLayer", data: null})
|
||||
})
|
||||
}
|
||||
|
||||
class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
callback: (payloadData: EnterLeaveEvent) => {
|
||||
enterStreams.get(payloadData.name)?.next();
|
||||
},
|
||||
type: "enterEvent",
|
||||
typeChecker: isEnterLeaveEvent
|
||||
}),
|
||||
apiCallback({
|
||||
type: "leaveEvent",
|
||||
typeChecker: isEnterLeaveEvent,
|
||||
callback: (payloadData) => {
|
||||
leaveStreams.get(payloadData.name)?.next();
|
||||
}
|
||||
}),
|
||||
apiCallback({
|
||||
type: "gameState",
|
||||
typeChecker: isGameStateEvent,
|
||||
callback: (payloadData) => {
|
||||
stateResolvers.next(payloadData);
|
||||
}
|
||||
}),
|
||||
apiCallback({
|
||||
type: "dataLayer",
|
||||
typeChecker: isDataLayerEvent,
|
||||
callback: (payloadData) => {
|
||||
dataLayerResolver.next(payloadData);
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
onEnterZone(name: string, callback: () => void): void {
|
||||
let subject = enterStreams.get(name);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<EnterLeaveEvent>();
|
||||
enterStreams.set(name, subject);
|
||||
}
|
||||
subject.subscribe(callback);
|
||||
|
||||
}
|
||||
onLeaveZone(name: string, callback: () => void): void {
|
||||
let subject = leaveStreams.get(name);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<EnterLeaveEvent>();
|
||||
leaveStreams.set(name, subject);
|
||||
}
|
||||
subject.subscribe(callback);
|
||||
}
|
||||
showLayer(layerName: string): void {
|
||||
sendToWorkadventure({type: 'showLayer', data: {'name': layerName} as LayerEvent});
|
||||
}
|
||||
hideLayer(layerName: string): void {
|
||||
sendToWorkadventure({type: 'hideLayer', data: {'name': layerName} as LayerEvent});
|
||||
}
|
||||
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
||||
sendToWorkadventure({
|
||||
type: 'setProperty',
|
||||
data: {
|
||||
'layerName': layerName,
|
||||
'propertyName': propertyName,
|
||||
'propertyValue': propertyValue,
|
||||
} as SetPropertyEvent
|
||||
})
|
||||
}
|
||||
getCurrentRoom(): Promise<Room> {
|
||||
return getGameState().then((gameState) => {
|
||||
return getDataLayer().then((mapJson) => {
|
||||
return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName};
|
||||
})
|
||||
})
|
||||
}
|
||||
getCurrentUser(): Promise<User> {
|
||||
return getGameState().then((gameState) => {
|
||||
return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags};
|
||||
})
|
||||
}
|
||||
changeTile(tiles: TileDescriptor[]) {
|
||||
sendToWorkadventure({
|
||||
type: 'changeTile',
|
||||
data: tiles
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureRoomCommands();
|
17
front/src/Api/iframe/sound.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { LoadSoundEvent } from '../Events/LoadSoundEvent';
|
||||
import type { PlaySoundEvent } from '../Events/PlaySoundEvent';
|
||||
import type { StopSoundEvent } from '../Events/StopSoundEvent';
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
import {Sound} from "./Sound/Sound";
|
||||
|
||||
class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
|
||||
callbacks = []
|
||||
|
||||
loadSound(url: string): Sound {
|
||||
return new Sound(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureSoundCommands();
|
106
front/src/Api/iframe/ui.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent';
|
||||
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent';
|
||||
import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent';
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
|
||||
import { Popup } from "./Ui/Popup";
|
||||
|
||||
let popupId = 0;
|
||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>();
|
||||
|
||||
const menuCallbacks: Map<string, (command: string) => void> = new Map()
|
||||
|
||||
interface ZonedPopupOptions {
|
||||
zone: string
|
||||
objectLayerName?: string,
|
||||
popupText: string,
|
||||
delay?: number
|
||||
popupOptions: Array<ButtonDescriptor>
|
||||
}
|
||||
|
||||
|
||||
class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
||||
|
||||
callbacks = [apiCallback({
|
||||
type: "buttonClickedEvent",
|
||||
typeChecker: isButtonClickedEvent,
|
||||
callback: (payloadData) => {
|
||||
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
||||
const popup = popups.get(payloadData.popupId);
|
||||
if (popup === undefined) {
|
||||
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
|
||||
}
|
||||
if (callback) {
|
||||
callback(popup);
|
||||
}
|
||||
}
|
||||
}),
|
||||
apiCallback({
|
||||
type: "menuItemClicked",
|
||||
typeChecker: isMenuItemClickedEvent,
|
||||
callback: event => {
|
||||
const callback = menuCallbacks.get(event.menuItem);
|
||||
if (callback) {
|
||||
callback(event.menuItem)
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||
popupId++;
|
||||
const popup = new Popup(popupId);
|
||||
const btnMap = new Map<number, () => void>();
|
||||
popupCallbacks.set(popupId, btnMap);
|
||||
let id = 0;
|
||||
for (const button of buttons) {
|
||||
const callback = button.callback;
|
||||
if (callback) {
|
||||
btnMap.set(id, () => {
|
||||
callback(popup);
|
||||
});
|
||||
}
|
||||
id++;
|
||||
}
|
||||
|
||||
sendToWorkadventure({
|
||||
'type': 'openPopup',
|
||||
'data': {
|
||||
popupId,
|
||||
targetObject,
|
||||
message,
|
||||
buttons: buttons.map((button) => {
|
||||
return {
|
||||
label: button.label,
|
||||
className: button.className
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
popups.set(popupId, popup)
|
||||
return popup;
|
||||
}
|
||||
|
||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
||||
menuCallbacks.set(commandDescriptor, callback);
|
||||
sendToWorkadventure({
|
||||
'type': 'registerMenuCommand',
|
||||
'data': {
|
||||
menutItem: commandDescriptor
|
||||
} as MenuItemRegisterEvent
|
||||
});
|
||||
}
|
||||
|
||||
displayBubble(): void {
|
||||
sendToWorkadventure({ 'type': 'displayBubble', data: null });
|
||||
}
|
||||
|
||||
removeBubble(): void {
|
||||
sendToWorkadventure({ 'type': 'removeBubble', data: null });
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkAdventureUiCommands();
|
|
@ -1,11 +1,93 @@
|
|||
<script lang="typescript">
|
||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||
import {menuIconVisible} from "../Stores/MenuStore";
|
||||
import {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
|
||||
import CameraControls from "./CameraControls.svelte";
|
||||
import MyCamera from "./MyCamera.svelte";
|
||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||
import {selectCompanionSceneVisibleStore} from "../Stores/SelectCompanionStore";
|
||||
import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore";
|
||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||
import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore";
|
||||
import {errorStore} from "../Stores/ErrorStore";
|
||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||
import LoginScene from "./Login/LoginScene.svelte";
|
||||
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
|
||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||
import {requestVisitCardsStore} from "../Stores/GameStore";
|
||||
|
||||
import type {Game} from "../Phaser/Game/Game";
|
||||
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
|
||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
||||
|
||||
export let game: Game;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<!-- {#if $menuIconVisible}
|
||||
<MenuIcon />
|
||||
{/if} -->
|
||||
{#if $loginSceneVisibleStore}
|
||||
<div class="scrollable">
|
||||
<LoginScene game={game}></LoginScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCharacterScene game={ game }></SelectCharacterScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $customCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<CustomCharacterScene game={ game }></CustomCharacterScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCompanionSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCompanionScene game={ game }></SelectCompanionScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $enableCameraSceneVisibilityStore}
|
||||
<div class="scrollable">
|
||||
<EnableCameraScene game={game}></EnableCameraScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $soundPlayingStore}
|
||||
<div>
|
||||
<AudioPlaying url={$soundPlayingStore} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!--
|
||||
{#if $menuIconVisible}
|
||||
<div>
|
||||
<MenuIcon />
|
||||
</div>
|
||||
{/if}
|
||||
-->
|
||||
{#if $gameOverlayVisibilityStore}
|
||||
<div>
|
||||
<MyCamera></MyCamera>
|
||||
<CameraControls></CameraControls>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $consoleGlobalMessageManagerVisibleStore}
|
||||
<div>
|
||||
<ConsoleGlobalMessageManager game={game}></ConsoleGlobalMessageManager>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $helpCameraSettingsVisibleStore}
|
||||
<div>
|
||||
<HelpCameraSettingsPopup></HelpCameraSettingsPopup>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $requestVisitCardsStore}
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore}></VisitCard>
|
||||
{/if}
|
||||
{#if $errorStore.length > 0}
|
||||
<div>
|
||||
<ErrorDialog></ErrorDialog>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
61
front/src/Components/CameraControls.svelte
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script lang="typescript">
|
||||
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||
import monitorImg from "./images/monitor.svg";
|
||||
import monitorCloseImg from "./images/monitor-close.svg";
|
||||
import cinemaImg from "./images/cinema.svg";
|
||||
import cinemaCloseImg from "./images/cinema-close.svg";
|
||||
import microphoneImg from "./images/microphone.svg";
|
||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if ($requestedScreenSharingState === true) {
|
||||
requestedScreenSharingState.disableScreenSharing();
|
||||
} else {
|
||||
requestedScreenSharingState.enableScreenSharing();
|
||||
}
|
||||
}
|
||||
|
||||
function cameraClick(): void {
|
||||
if ($requestedCameraState === true) {
|
||||
requestedCameraState.disableWebcam();
|
||||
} else {
|
||||
requestedCameraState.enableWebcam();
|
||||
}
|
||||
}
|
||||
|
||||
function microphoneClick(): void {
|
||||
if ($requestedMicrophoneState === true) {
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
} else {
|
||||
requestedMicrophoneState.enableMicrophone();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState}
|
||||
<img src={monitorImg} alt="Start screen sharing">
|
||||
{:else}
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}>
|
||||
{#if $requestedCameraState}
|
||||
<img src={cinemaImg} alt="Turn on webcam">
|
||||
{:else}
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
|
||||
{#if $requestedMicrophoneState}
|
||||
<img src={microphoneImg} alt="Turn on microphone">
|
||||
{:else}
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,44 @@
|
|||
<script lang="typescript">
|
||||
import InputTextGlobalMessage from "./InputTextGlobalMessage.svelte";
|
||||
import UploadAudioGlobalMessage from "./UploadAudioGlobalMessage.svelte";
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
|
||||
export let game: Game;
|
||||
let inputSendTextActive = true;
|
||||
let uploadMusicActive = false;
|
||||
|
||||
function inputSendTextActivate() {
|
||||
inputSendTextActive = true;
|
||||
uploadMusicActive = false;
|
||||
}
|
||||
|
||||
function inputUploadMusicActivate() {
|
||||
uploadMusicActive = true;
|
||||
inputSendTextActive = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="main-console nes-container is-rounded">
|
||||
<!-- <div class="console nes-container is-rounded">
|
||||
<img class="btn-close" src="resources/logos/send-yellow.svg" alt="Close">
|
||||
</div>-->
|
||||
<div class="main-global-message">
|
||||
<h2> Global Message </h2>
|
||||
<div class="global-message">
|
||||
<div class="menu">
|
||||
<button class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
|
||||
<button class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
|
||||
</div>
|
||||
<div class="main-input">
|
||||
{#if inputSendTextActive}
|
||||
<InputTextGlobalMessage game={game} gameManager={gameManager}></InputTextGlobalMessage>
|
||||
{/if}
|
||||
{#if uploadMusicActive}
|
||||
<UploadAudioGlobalMessage game={game} gameManager={gameManager}></UploadAudioGlobalMessage>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,99 @@
|
|||
<script lang="ts">
|
||||
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import {onMount} from "svelte";
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import type {GameManager} from "../../Phaser/Game/GameManager";
|
||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
||||
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
|
||||
import type {Quill} from "quill";
|
||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
|
||||
//toolbar
|
||||
export const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{'header': 1}, {'header': 2}], // custom button values
|
||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
|
||||
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
|
||||
[{'direction': 'rtl'}], // text direction
|
||||
|
||||
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
|
||||
[{'header': [1, 2, 3, 4, 5, 6, false]}],
|
||||
|
||||
[{'color': []}, {'background': []}], // dropdown with defaults from theme
|
||||
[{'font': []}],
|
||||
[{'align': []}],
|
||||
|
||||
['clean'],
|
||||
|
||||
['link', 'image', 'video']
|
||||
// remove formatting button
|
||||
];
|
||||
|
||||
export let game: Game;
|
||||
export let gameManager: GameManager;
|
||||
|
||||
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
|
||||
let quill: Quill;
|
||||
let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
|
||||
|
||||
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||
|
||||
//Quill
|
||||
onMount(async () => {
|
||||
|
||||
// Import quill
|
||||
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
quill = new Quill(INPUT_CONSOLE_MESSAGE, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions
|
||||
},
|
||||
});
|
||||
|
||||
quill.on('selection-change', function (range, oldRange) {
|
||||
if (range === null && oldRange !== null) {
|
||||
consoleGlobalMessageManagerFocusStore.set(false);
|
||||
} else if (range !== null && oldRange === null)
|
||||
consoleGlobalMessageManagerFocusStore.set(true);
|
||||
});
|
||||
});
|
||||
|
||||
function disableConsole() {
|
||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||
consoleGlobalMessageManagerFocusStore.set(false);
|
||||
}
|
||||
|
||||
function SendTextMessage() {
|
||||
if (gameScene == undefined) {
|
||||
return;
|
||||
}
|
||||
const text = quill.getText(0, quill.getLength());
|
||||
|
||||
const GlobalMessage: PlayGlobalMessageInterface = {
|
||||
id: "1", // FIXME: use another ID?
|
||||
message: text,
|
||||
type: MESSAGE_TYPE
|
||||
};
|
||||
|
||||
quill.deleteText(0, quill.getLength());
|
||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
||||
disableConsole();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<section class="section-input-send-text">
|
||||
<div class="input-send-text" bind:this={INPUT_CONSOLE_MESSAGE}></div>
|
||||
<div class="btn-action">
|
||||
<button class="nes-btn is-primary" on:click|preventDefault={SendTextMessage}>Send</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import 'https://cdn.quilljs.com/1.3.7/quill.snow.css';
|
||||
</style>
|
|
@ -0,0 +1,130 @@
|
|||
<script lang="ts">
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import type {GameManager} from "../../Phaser/Game/GameManager";
|
||||
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore} from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
|
||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
||||
import uploadFile from "../images/music-file.svg";
|
||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
|
||||
interface EventTargetFiles extends EventTarget {
|
||||
files: Array<File>;
|
||||
}
|
||||
|
||||
export let game: Game;
|
||||
export let gameManager: GameManager;
|
||||
|
||||
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
|
||||
let fileinput: HTMLInputElement;
|
||||
let filename: string;
|
||||
let filesize: string;
|
||||
let errorfile: boolean;
|
||||
|
||||
const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||
|
||||
|
||||
async function SendAudioMessage() {
|
||||
if (gameScene == undefined) {
|
||||
return;
|
||||
}
|
||||
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>("input-send-audio");
|
||||
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
||||
if (!selectedFile) {
|
||||
errorfile = true;
|
||||
throw 'no file selected';
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('file', selectedFile);
|
||||
const res = await gameScene.connection?.uploadAudio(fd);
|
||||
|
||||
const GlobalMessage: PlayGlobalMessageInterface = {
|
||||
id: (res as { id: string }).id,
|
||||
message: (res as { path: string }).path,
|
||||
type: AUDIO_TYPE
|
||||
}
|
||||
inputAudio.value = '';
|
||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
||||
disableConsole();
|
||||
}
|
||||
|
||||
function inputAudioFile(event: Event) {
|
||||
const eventTarget : EventTargetFiles = (event.target as EventTargetFiles);
|
||||
if(!eventTarget || !eventTarget.files || eventTarget.files.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
const file = eventTarget.files[0];
|
||||
if(!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
filename = file.name;
|
||||
filesize = getFileSize(file.size);
|
||||
errorfile = false;
|
||||
}
|
||||
|
||||
function getFileSize(number: number) {
|
||||
if (number < 1024) {
|
||||
return number + 'bytes';
|
||||
} else if (number >= 1024 && number < 1048576) {
|
||||
return (number / 1024).toFixed(1) + 'KB';
|
||||
} else if (number >= 1048576) {
|
||||
return (number / 1048576).toFixed(1) + 'MB';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function disableConsole() {
|
||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||
consoleGlobalMessageManagerFocusStore.set(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<section class="section-input-send-audio">
|
||||
<div class="input-send-audio">
|
||||
<img src="{uploadFile}" alt="Upload a file" on:click|preventDefault={ () => {fileinput.click();}}>
|
||||
{#if filename != undefined}
|
||||
<label for="input-send-audio">{filename} : {filesize}</label>
|
||||
{/if}
|
||||
{#if errorfile}
|
||||
<p class="err">No file selected. You need to upload a file before sending it.</p>
|
||||
{/if}
|
||||
<input type="file" id="input-send-audio" bind:this={fileinput} on:change={(e) => {inputAudioFile(e)}}>
|
||||
</div>
|
||||
<div class="btn-action">
|
||||
<button class="nes-btn is-primary" on:click|preventDefault={SendAudioMessage}>Send</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
//UploadAudioGlobalMessage
|
||||
.section-input-send-audio {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.section-input-send-audio .input-send-audio {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-input-send-audio #input-send-audio{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.section-input-send-audio div.input-send-audio label{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.section-input-send-audio div.input-send-audio p.err {
|
||||
color: #ce372b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-input-send-audio div.input-send-audio img{
|
||||
height: 150px;
|
||||
cursor: url('../../../style/images/cursor_pointer.png'), pointer;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,119 @@
|
|||
<script lang="typescript">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
||||
let activeRow = customCharacterScene.activeRow;
|
||||
|
||||
function selectLeft() {
|
||||
customCharacterScene.moveCursorHorizontally(-1);
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
customCharacterScene.moveCursorHorizontally(1);
|
||||
}
|
||||
|
||||
function selectUp() {
|
||||
customCharacterScene.moveCursorVertically(-1);
|
||||
activeRow = customCharacterScene.activeRow;
|
||||
}
|
||||
|
||||
function selectDown() {
|
||||
customCharacterScene.moveCursorVertically(1);
|
||||
activeRow = customCharacterScene.activeRow;
|
||||
}
|
||||
|
||||
function previousScene() {
|
||||
customCharacterScene.backToPreviousScene();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
customCharacterScene.nextSceneToCamera();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="customCharacterScene">
|
||||
<section class="text-center">
|
||||
<h2>Customize your WOKA</h2>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customCharacterSceneButton customCharacterSceneButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
{#if activeRow === 0}
|
||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
||||
{/if}
|
||||
{#if activeRow !== 0}
|
||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
||||
{/if}
|
||||
{#if activeRow === 5}
|
||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
||||
{/if}
|
||||
{#if activeRow !== 5}
|
||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
||||
{/if}
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.customCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.customCharacterSceneButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button.customCharacterSceneFormBack {
|
||||
color: #292929;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.customCharacterSceneButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
&.customCharacterSceneButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.customCharacterScene button.customCharacterSceneButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.customCharacterScene button.customCharacterSceneButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
229
front/src/Components/EnableCamera/EnableCameraScene.svelte
Normal file
|
@ -0,0 +1,229 @@
|
|||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {
|
||||
audioConstraintStore,
|
||||
cameraListStore,
|
||||
localStreamStore,
|
||||
microphoneListStore,
|
||||
videoConstraintStore
|
||||
} from "../../Stores/MediaStore";
|
||||
import {onDestroy} from "svelte";
|
||||
import HorizontalSoundMeterWidget from "./HorizontalSoundMeterWidget.svelte";
|
||||
import cinemaCloseImg from "../images/cinema-close.svg";
|
||||
import cinemaImg from "../images/cinema.svg";
|
||||
import microphoneImg from "../images/microphone.svg";
|
||||
|
||||
export let game: Game;
|
||||
let selectedCamera : string|undefined = undefined;
|
||||
let selectedMicrophone : string|undefined = undefined;
|
||||
|
||||
const enableCameraScene = game.scene.getScene(EnableCameraSceneName) as EnableCameraScene;
|
||||
|
||||
function submit() {
|
||||
enableCameraScene.login();
|
||||
}
|
||||
|
||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
update(newStream: MediaStream) {
|
||||
if (node.srcObject != newStream) {
|
||||
node.srcObject = newStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stream: MediaStream | null;
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
stream = value.stream;
|
||||
|
||||
if (stream !== null) {
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
if (videoTracks.length > 0) {
|
||||
selectedCamera = videoTracks[0].getSettings().deviceId;
|
||||
}
|
||||
const audioTracks = stream.getAudioTracks();
|
||||
if (audioTracks.length > 0) {
|
||||
selectedMicrophone = audioTracks[0].getSettings().deviceId;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stream = null;
|
||||
selectedCamera = undefined;
|
||||
selectedMicrophone = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
function normalizeDeviceName(label: string): string {
|
||||
// remove IDs (that can appear in Chrome, like: "HD Pro Webcam (4df7:4eda)"
|
||||
return label.replace(/(\([[0-9a-f]{4}:[0-9a-f]{4}\))/g, '').trim();
|
||||
}
|
||||
|
||||
function selectCamera() {
|
||||
videoConstraintStore.setDeviceId(selectedCamera);
|
||||
}
|
||||
|
||||
function selectMicrophone() {
|
||||
audioConstraintStore.setDeviceId(selectedMicrophone);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="enableCameraScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<h2>Turn on your camera and microphone</h2>
|
||||
</section>
|
||||
{#if $localStreamStore.type === 'success' && $localStreamStore.stream}
|
||||
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||
{:else }
|
||||
<div class="webrtcsetup">
|
||||
<img class="background-img" src={cinemaCloseImg} alt="">
|
||||
</div>
|
||||
{/if}
|
||||
<HorizontalSoundMeterWidget stream={stream}></HorizontalSoundMeterWidget>
|
||||
|
||||
<section class="selectWebcamForm">
|
||||
|
||||
{#if $cameraListStore.length > 1 }
|
||||
<div class="control-group">
|
||||
<img src={cinemaImg} alt="Camera" />
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={selectedCamera} on:change={selectCamera}>
|
||||
{#each $cameraListStore as camera}
|
||||
<option value={camera.deviceId}>
|
||||
{normalizeDeviceName(camera.label)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $microphoneListStore.length > 1 }
|
||||
<div class="control-group">
|
||||
<img src={microphoneImg} alt="Microphone" />
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={selectedMicrophone} on:change={selectMicrophone}>
|
||||
{#each $microphoneListStore as microphone}
|
||||
<option value={microphone.deviceId}>
|
||||
{normalizeDeviceName(microphone.label)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary letsgo" >Let's go!</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.enableCameraScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
|
||||
section.selectWebcamForm {
|
||||
margin-top: 3vh;
|
||||
margin-bottom: 3vh;
|
||||
min-height: 10vh;
|
||||
width: 50vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
select {
|
||||
font-family: "Press Start 2P";
|
||||
margin-top: 1vh;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
option {
|
||||
font-family: "Press Start 2P";
|
||||
}
|
||||
}
|
||||
|
||||
section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.letsgo {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-height: 60px;
|
||||
margin-top: 10px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.webrtcsetup{
|
||||
margin-top: 2vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 28.125vw;
|
||||
width: 50vw;
|
||||
border: white 6px solid;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img.background-img {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.myCamVideoSetup {
|
||||
margin-top: 2vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: 50vh;
|
||||
width: 50vw;
|
||||
border: white 6px solid;
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.enableCameraScene h2 {
|
||||
font-size: 80%;
|
||||
}
|
||||
.enableCameraScene .control-group .nes-select {
|
||||
font-size: 80%;
|
||||
}
|
||||
.enableCameraScene button.letsgo {
|
||||
font-size: 160%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<script lang="typescript">
|
||||
import { AudioContext } from 'standardized-audio-context';
|
||||
import {SoundMeter} from "../../Phaser/Components/SoundMeter";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
export let stream: MediaStream | null;
|
||||
let volume = 0;
|
||||
|
||||
const NB_BARS = 20;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try{
|
||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||
//console.log(volume);
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function color(i: number, volume: number) {
|
||||
const red = 255 * i / NB_BARS;
|
||||
const green = 255 * (1 - i / NB_BARS);
|
||||
|
||||
let alpha = 1;
|
||||
if (i >= volume) {
|
||||
alpha = 0.5;
|
||||
}
|
||||
|
||||
return 'background-color:rgba('+red+', '+green+', 0, '+alpha+')';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="horizontal-sound-meter" class:active={display}>
|
||||
{#each [...Array(NB_BARS).keys()] as i}
|
||||
<div style={color(i, volume)}></div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.horizontal-sound-meter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 50%;
|
||||
height: 30px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.horizontal-sound-meter div {
|
||||
margin-left: 5px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
|
||||
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
||||
|
||||
let isAndroid = window.navigator.userAgent.includes('Android');
|
||||
let isFirefox = window.navigator.userAgent.includes('Firefox');
|
||||
let isChrome = window.navigator.userAgent.includes('Chrome');
|
||||
|
||||
function refresh() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function close() {
|
||||
helpCameraSettingsVisibleStore.set(false);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="helpCameraSettings nes-container" on:submit|preventDefault={close} transition:fly="{{ y: -900, duration: 500 }}">
|
||||
<section>
|
||||
<h2>Camera / Microphone access needed</h2>
|
||||
<p class="err">Permission denied</p>
|
||||
<p>You must allow camera and microphone access in your browser.</p>
|
||||
<p>
|
||||
{#if isFirefox }
|
||||
<p class="err">Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.</p>
|
||||
<img src={firefoxImg} alt="" />
|
||||
{:else if isChrome && !isAndroid }
|
||||
<img src={chromeImg} alt="" />
|
||||
{/if}
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}>Refresh</button>
|
||||
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}>Continue without webcam</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
.helpCameraSettings {
|
||||
pointer-events: auto;
|
||||
background: #eceeee;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 10vh;
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-family: 'Press Start 2P';
|
||||
}
|
||||
|
||||
section {
|
||||
p {
|
||||
margin: 15px;
|
||||
font-family: 'Press Start 2P';
|
||||
|
||||
& .err {
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
img {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
122
front/src/Components/Login/LoginScene.svelte
Normal file
|
@ -0,0 +1,122 @@
|
|||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
import {DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH} from "../../Enum/EnvironmentVariable";
|
||||
import logoImg from "../images/logo.png";
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const loginScene = game.scene.getScene(LoginSceneName) as LoginScene;
|
||||
|
||||
let name = gameManager.getPlayerName() || '';
|
||||
let startValidating = false;
|
||||
|
||||
function submit() {
|
||||
startValidating = true;
|
||||
|
||||
let finalName = name.trim();
|
||||
if (finalName !== '') {
|
||||
loginScene.login(finalName);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="loginScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<img src={logoImg} alt="WorkAdventure logo" />
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h2>Enter your name</h2>
|
||||
</section>
|
||||
<input type="text" name="loginSceneName" class="nes-input is-dark" autofocus maxlength={MAX_USERNAME_LENGTH} bind:value={name} on:keypress={() => {startValidating = true}} class:is-error={name.trim() === '' && startValidating} />
|
||||
<section class="error-section">
|
||||
{#if name.trim() === '' && startValidating }
|
||||
<p class="err">The name is empty</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if DISPLAY_TERMS_OF_USE}
|
||||
<section class="terms-and-conditions">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
{/if}
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.loginScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
color: #ebeeee;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
font-family: "Press Start 2P";
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.terms-and-conditions {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
p.err {
|
||||
color: #ce372b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.error-section {
|
||||
min-height: 2rem;
|
||||
margin: 0;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
46
front/src/Components/MyCamera.svelte
Normal file
|
@ -0,0 +1,46 @@
|
|||
<script lang="typescript">
|
||||
import {localStreamStore} from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
update(newStream: MediaStream) {
|
||||
if (node.srcObject != newStream) {
|
||||
node.srcObject = newStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stream : MediaStream|null;
|
||||
/*$: {
|
||||
if ($localStreamStore.type === 'success') {
|
||||
stream = $localStreamStore.stream;
|
||||
} else {
|
||||
stream = null;
|
||||
}
|
||||
}*/
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
stream = value.stream;
|
||||
} else {
|
||||
stream = null;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
|
||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream }
|
||||
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,87 @@
|
|||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const selectCompanionScene = game.scene.getScene(SelectCompanionSceneName) as SelectCompanionScene;
|
||||
|
||||
function selectLeft() {
|
||||
selectCompanionScene.moveToLeft();
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
selectCompanionScene.moveToRight();
|
||||
}
|
||||
|
||||
function noCompanion() {
|
||||
selectCompanionScene.closeScene();
|
||||
}
|
||||
|
||||
function selectCompanion() {
|
||||
selectCompanionScene.selectCompanion();
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="selectCompanionScene">
|
||||
<section class="text-center">
|
||||
<h2>Select your companion</h2>
|
||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}> < </button>
|
||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={selectRight}> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}>No companion</button>
|
||||
<button type="submit" class="selectCompanionSceneFormSubmit nes-btn is-primary" on:click|preventDefault={selectCompanion}>Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.selectCompanionScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.selectCharacterButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.selectCharacterButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
button.selectCharacterButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.selectCompanionScene button.selectCharacterButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.selectCompanionScene button.selectCharacterButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
53
front/src/Components/SoundMeterWidget.svelte
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script lang="typescript">
|
||||
import { AudioContext } from 'standardized-audio-context';
|
||||
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
export let stream: MediaStream|null;
|
||||
let volume = 0;
|
||||
|
||||
const NB_BARS = 5;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try{
|
||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||
//console.log(volume);
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<div class="sound-progress" class:active={display}>
|
||||
<span class:active={volume > 1}></span>
|
||||
<span class:active={volume > 2}></span>
|
||||
<span class:active={volume > 3}></span>
|
||||
<span class:active={volume > 4}></span>
|
||||
<span class:active={volume > 5}></span>
|
||||
</div>
|
52
front/src/Components/UI/AudioPlaying.svelte
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import megaphoneImg from "./images/megaphone.svg";
|
||||
import {soundPlayingStore} from "../../Stores/SoundPlayingStore";
|
||||
import {afterUpdate} from "svelte";
|
||||
|
||||
export let url: string;
|
||||
let audio: HTMLAudioElement;
|
||||
|
||||
function soundEnded() {
|
||||
soundPlayingStore.soundEnded();
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
audio.play();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="audio-playing" transition:fly="{{ x: 210, duration: 500 }}">
|
||||
<img src={megaphoneImg} alt="Audio playing" />
|
||||
<p>Audio message</p>
|
||||
<audio bind:this={audio} src={url} on:ended={soundEnded} >
|
||||
<track kind="captions">
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
/*audio html when audio message playing*/
|
||||
.audio-playing {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
height: 54px;
|
||||
right: 0;
|
||||
top: 40px;
|
||||
transition: all 0.1s ease-out;
|
||||
background-color: black;
|
||||
border-radius: 30px 0 0 30px;
|
||||
display: inline-flex;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
background-color: #ffda01;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: white;
|
||||
margin-left: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
48
front/src/Components/UI/ErrorDialog.svelte
Normal file
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import {errorStore} from "../../Stores/ErrorStore";
|
||||
|
||||
function close(): boolean {
|
||||
errorStore.clearMessages();
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="error-div nes-container is-dark is-rounded" open>
|
||||
<p class="nes-text is-error title">Error</p>
|
||||
<div class="body">
|
||||
{#each $errorStore as error}
|
||||
<p>{error}</p>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="nes-btn is-error" on:click={close}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.error-div {
|
||||
pointer-events: auto;
|
||||
margin-top: 10vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: max-content;
|
||||
max-width: 80vw;
|
||||
|
||||
.button-bar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body {
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
18
front/src/Components/UI/images/megaphone.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 451.7 512" style="enable-background:new 0 0 451.7 512;" xml:space="preserve">
|
||||
<path d="M436.9,212.6L237.2,12.9c-11.7-11.7-30.7-11.7-42.4,0s-11.7,30.7,0,42.4L394.5,255c11.5,11.9,30.5,12.2,42.4,0.7
|
||||
c11.9-11.5,12.2-30.5,0.7-42.4C437.4,213.1,437.2,212.8,436.9,212.6z"/>
|
||||
<path d="M179.5,83.1l-1.5,7.5c-10.4,53-36,103.4-70.6,144.3l109,108.3c40.7-34.9,90.2-61.5,143.1-72.3l7.5-1.5L179.5,83.1z"/>
|
||||
<path d="M87.4,257l-74.2,74.2c-17.6,17.6-17.6,46.1,0,63.6c0,0,0,0,0,0l42.4,42.4c17.6,17.6,46.1,17.6,63.6,0c0,0,0,0,0,0l74.2-74.2
|
||||
L87.4,257z M98,373.7c-6.1,5.6-15.6,5.3-21.2-0.8c-5.4-5.8-5.4-14.7,0-20.5l21.2-21.2c6-5.8,15.5-5.6,21.2,0.4
|
||||
c5.6,5.8,5.6,15,0,20.8L98,373.7z"/>
|
||||
<path d="M256.1,445.3l20.4-20.4c17.6-17.6,17.6-46.1,0-63.6l-15.1-15.2c-8.4,5.7-16.4,11.7-24.2,18.3l18.1,18.1
|
||||
c5.8,5.9,5.8,15.3,0,21.2l-20.7,20.8l-30.5-29.5l-42.4,42.4l68.1,65.9c11.7,11.7,30.7,11.7,42.4,0c11.7-11.7,11.7-30.7,0-42.4l0,0
|
||||
L256.1,445.3z"/>
|
||||
<path d="M316.7,0c-8.3,0-15,6.7-15,15v30c0,8.3,6.7,15,15,15c8.3,0,15-6.7,15-15V15C331.7,6.7,325,0,316.7,0z"/>
|
||||
<path d="M436.7,120h-30c-8.3,0-15,6.7-15,15s6.7,15,15,15h30c8.3,0,15-6.7,15-15S445,120,436.7,120z"/>
|
||||
<path d="M417.3,34.4c-5.9-5.9-15.4-5.9-21.2,0l-30,30c-6,5.8-6.1,15.3-0.4,21.2c5.8,6,15.3,6.1,21.2,0.4c0.1-0.1,0.2-0.2,0.4-0.4
|
||||
l30-30C423.2,49.7,423.2,40.2,417.3,34.4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
84
front/src/Components/VisitCard/VisitCard.svelte
Normal file
|
@ -0,0 +1,84 @@
|
|||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
||||
import {onMount} from "svelte";
|
||||
|
||||
export let visitCardUrl: string;
|
||||
let w = '500px';
|
||||
let h = '250px';
|
||||
let hidden = true;
|
||||
let cvIframe: HTMLIFrameElement;
|
||||
|
||||
function closeCard() {
|
||||
requestVisitCardsStore.set(null);
|
||||
}
|
||||
|
||||
function handleIframeMessage(message:any) {
|
||||
if (message.data.type === 'cvIframeSize') {
|
||||
w = (message.data.data.w) + 'px';
|
||||
h = (message.data.data.h) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
cvIframe.onload = () => hidden = false
|
||||
cvIframe.onerror = () => hidden = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin:auto;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.visitCard {
|
||||
pointer-events: all;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 200px;
|
||||
max-width: 80vw;
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
max-width: 80vw;
|
||||
overflow: hidden;
|
||||
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}" style="width: {w}">
|
||||
{#if hidden}
|
||||
<div class="loader"></div>
|
||||
{/if}
|
||||
<iframe title="visitCard" src={visitCardUrl} allow="clipboard-read; clipboard-write self {visitCardUrl}" style="width: {w}; height: {h}" class:hidden={hidden} bind:this={cvIframe}></iframe>
|
||||
{#if !hidden}
|
||||
<div class="buttonContainer">
|
||||
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
|
||||
<svelte:window on:message={handleIframeMessage}/>
|
41
front/src/Components/images/cinema-close.svg
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
|
||||
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
|
||||
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
|
||||
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
|
||||
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="134.8" y="-45.3" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 376.0669 224.8258)" class="st0" width="19.6" height="460.7"/>
|
||||
<path class="st0" d="M90.6,83.3c-0.2-2.2-1.3-8.9-6.7-14.9c-5.4-5.9-11.9-7.6-14.1-8.1C59.7,49.2,49.5,38,39.4,26.8
|
||||
c24.3-9.8,52-4.4,70.2,13.6c19.9,19.7,24.7,50.8,11.5,76.4C110.9,105.6,100.8,94.5,90.6,83.3z"/>
|
||||
<path class="st0" d="M10.1,51.6c9.4,10.2,18.8,20.4,28.2,30.6c-0.2,1.8-1.4,11.7,5.5,20.5c8.2,10.3,20.7,10.2,22.1,10.1
|
||||
c9.2,10.3,18.5,20.6,27.7,30.8c-4.8,2.3-24.6,11.2-48.3,4.1c-6-1.8-20.7-7.3-32.1-22C-0.3,108.1-0.2,89.1,0.1,83.4
|
||||
C0.8,68,6.8,56.8,10.1,51.6z"/>
|
||||
<g>
|
||||
<path class="st0" d="M243.4,178.2c0.1,24.5,0.2,49,0.2,73.5c-30.7-33.8-61.3-67.7-92-101.5c5.9,3.9,20.9,12.4,41.6,12.4
|
||||
c16,0,28.2-5.2,34.4-8.4c2.5,1.5,7,4.6,10.7,10.3C242,170,243,175.4,243.4,178.2z"/>
|
||||
<g>
|
||||
<path class="st0" d="M211.2,311C150.8,258.7,90.4,206.5,30,154.2c6.1,3.1,18.2,8.4,34.4,8.4c18.1,0,31.5-6.5,37.5-9.9
|
||||
c44.5,49,89.1,98.1,133.6,147.1c-1.8,2.1-5.3,5.5-10.6,8.1C219.2,310.6,214,311,211.2,311z"/>
|
||||
<path class="st0" d="M46.8,311C36,267.7,25.2,224.3,14.4,181c0.1-3.2,0.7-11.3,6.5-18.8c3.1-4.1,6.7-6.6,9.1-8
|
||||
C90.4,206.5,150.8,258.7,211.2,311C156.4,311,101.6,311,46.8,311z"/>
|
||||
<path class="st0" d="M14.4,278.6L14.4,278.6c0-32.5,0-65.1,0-97.6c10.8,43.3,21.6,86.7,32.4,130c-2.6,0-12.7-0.4-21.5-8.1
|
||||
C14.7,293.5,14.4,280.7,14.4,278.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
front/src/Components/images/logo.png
Normal file
After Width: | Height: | Size: 16 KiB |
27
front/src/Components/images/microphone-close.svg
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<rect x="257" y="-47.9" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 643.9641 283.6469)" class="st0" width="20.4" height="628.3"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M333.6,250.3c-52.6-43.9-105.1-87.9-157.7-131.8c0-17.9,0-35.8,0-53.6c6.5-38.6,40.3-67,79.3-66.8
|
||||
c38.6,0.2,71.9,28.5,78.4,66.8C333.6,126.7,333.6,188.5,333.6,250.3z"/>
|
||||
<path class="st0" d="M322.6,279.9c-48.9-53.8-97.8-107.6-146.6-161.4l0,0c52.6,43.9,105.1,87.9,157.7,131.8
|
||||
c-0.2,1.6-0.5,3.3-0.9,5C330.5,265.2,326.6,273.5,322.6,279.9z"/>
|
||||
</g>
|
||||
<path class="st0" d="M292.5,308.1c-2.3,1.2-39.5,20.3-76.7-1c-36.4-20.8-39.4-61.2-39.6-64.1c-0.1-21-0.1-42.1-0.2-63.1
|
||||
C214.8,222.6,253.6,265.3,292.5,308.1z"/>
|
||||
</g>
|
||||
<path class="st0" d="M431.6,238.5c-0.9-8.4-8.5-14.4-16.6-13.5c-7.9,0.9-13.9,8.1-13.2,16.3c-0.1,13.3-2.2,34.6-12.6,57.9
|
||||
c-6.3,14.2-14,25.2-20.6,33.1c6.8,7.5,13.6,14.9,20.3,22.4c9.5-10.9,23.4-29.7,32.8-56.3C430.3,273.9,431.8,252.5,431.6,238.5z"/>
|
||||
<line class="st0" x1="354.5" y1="347.2" x2="374.6" y2="369.4"/>
|
||||
<path class="st0" d="M338.5,359.9c6.8,7.4,13.5,14.9,20.3,22.3c-52.6,37.6-121.5,43.7-179.2,15.8c-60.3-29.1-98.9-90.7-99.3-158.2
|
||||
c0-8.2,6.8-15,15-15s15,6.8,15,15c0.1,13.5,2.4,54.4,32.4,91.6c4.2,5.2,45.1,54.1,113.3,54.1C297,385.6,326.7,367.9,338.5,359.9z"/>
|
||||
<rect x="241" y="409.6" class="st0" width="29.9" height="102.3"/>
|
||||
<path class="st0" d="M304.2,511.9h-97.1c-8-0.4-14.3-7.1-14.3-15c0-8.1,6.7-14.9,15-15c31.7,0,63.4,0.1,95.1,0.1
|
||||
c8.9-0.6,16.3,6.5,16.3,14.9C319.2,504.8,312.6,511.7,304.2,511.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
27
front/src/Components/images/music-file.svg
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 448 448" style="enable-background:new 0 0 448 448;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFDA01;}
|
||||
</style>
|
||||
<path class="st0" d="M348,288c-44.2,0-80,35.8-80,80s35.8,80,80,80s80-35.8,80-80C428,323.8,392.2,288,348,288z M387.6,359.6
|
||||
c-3.1,3.1-8.2,3.1-11.3,0L356,339.3V416c0,4.4-3.6,8-8,8s-8-3.6-8-8v-76.7l-20.3,20.3c-3.1,3-8.1,3-11.2-0.1s-3.1-8.1-0.1-11.2
|
||||
l33.9-33.9c0.7-0.7,1.6-1.3,2.6-1.7c2-0.8,4.2-0.8,6.1,0c1,0.4,1.9,1,2.6,1.7l33.9,33.9C390.7,351.4,390.7,356.5,387.6,359.6z"/>
|
||||
<path class="st0" d="M244,154.6L148,182v15.4l96-27.4V154.6z"/>
|
||||
<path class="st0" d="M244,280c0,8.8-7.2,16-16,16s-16-7.2-16-16s7.2-16,16-16S244,271.2,244,280z"/>
|
||||
<path class="st0" d="M132,312c0,8.8-7.2,16-16,16s-16-7.2-16-16s7.2-16,16-16S132,303.2,132,312z"/>
|
||||
<path class="st0" d="M31.3,80H100V11.3L31.3,80z"/>
|
||||
<path class="st0" d="M20,448h275c-0.1-0.1-0.2-0.1-0.3-0.2c-2.9-2-5.8-4.1-8.5-6.4c-0.7-0.6-1.4-1.3-2.1-1.9
|
||||
c-1.9-1.7-3.8-3.5-5.6-5.4c-0.8-0.9-1.6-1.8-2.4-2.7c-1.6-1.8-3.2-3.8-4.7-5.7c-0.7-0.9-1.4-1.8-2-2.7c-1.8-2.6-3.5-5.2-5-8
|
||||
c-0.2-0.4-0.4-0.7-0.6-1c-1.7-3.1-3.2-6.4-4.6-9.7c-0.4-1-0.7-2-1.1-3c-0.9-2.4-1.7-4.9-2.4-7.4c-0.3-1.2-0.6-2.4-0.9-3.6
|
||||
c-0.6-2.5-1.1-5-1.5-7.6c-0.2-1.1-0.4-2.3-0.5-3.4c-0.9-6.9-1-13.9-0.2-20.8c0.1-1.1,0.3-2.1,0.5-3.1c0.3-2.1,0.5-4.1,0.9-6.2
|
||||
c0.2-1.2,0.6-2.4,0.9-3.7c0.4-1.8,0.8-3.6,1.4-5.3c0.4-1.3,0.9-2.5,1.3-3.8c0.6-1.6,1.1-3.3,1.8-4.9c0.5-1.3,1.1-2.5,1.7-3.7
|
||||
c0.7-1.5,1.4-3,2.2-4.5c0.6-1.2,1.4-2.4,2-3.6c0.8-1.4,1.7-2.8,2.6-4.2c0.8-1.2,1.6-2.3,2.4-3.4c0.9-1.3,1.9-2.6,2.9-3.9
|
||||
c0.9-1.1,1.8-2.1,2.7-3.2c1.1-1.2,2.1-2.4,3.2-3.6c1-1,2-2,3-2.9c1.2-1.1,2.3-2.2,3.6-3.2c1.1-0.9,2.1-1.8,3.2-2.7
|
||||
c1.3-1,2.6-2,3.9-2.9c1.1-0.8,2.3-1.6,3.5-2.4c1.4-0.9,2.8-1.7,4.2-2.5c1.2-0.7,2.4-1.4,3.6-2c1.5-0.8,2.9-1.5,4.4-2.1
|
||||
c1.3-0.6,2.5-1.2,3.8-1.7c1.6-0.6,3.1-1.2,4.7-1.7c1.3-0.4,2.6-0.9,3.9-1.3c1.6-0.5,3.3-0.9,5-1.3c1.3-0.3,2.6-0.7,4-0.9
|
||||
c1.8-0.3,3.5-0.6,5.3-0.8c1.3-0.2,2.6-0.4,4-0.5c0.3,0,0.6-0.1,1-0.1V0H116v88c0,4.4-3.6,8-8,8H20V448z M116,280
|
||||
c5.6,0,11.2,1.6,16,4.4V176c0-3.6,2.4-6.7,5.8-7.7l112-32c2.4-0.7,5-0.2,7,1.3s3.2,3.9,3.2,6.4v136c0,17.7-14.3,32-32,32
|
||||
s-32-14.3-32-32s14.3-32,32-32c5.6,0,11.2,1.6,16,4.4v-65.8L148,214v98c0,17.7-14.3,32-32,32s-32-14.3-32-32S98.3,280,116,280z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,92 @@
|
|||
<script lang="typescript">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName) as SelectCharacterScene;
|
||||
|
||||
function selectLeft() {
|
||||
selectCharacterScene.moveToLeft();
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
selectCharacterScene.moveToRight();
|
||||
}
|
||||
|
||||
function cameraScene() {
|
||||
selectCharacterScene.nextSceneToCameraScene();
|
||||
}
|
||||
|
||||
function customizeScene() {
|
||||
selectCharacterScene.nextSceneToCustomizeScene();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="selectCharacterScene">
|
||||
<section class="text-center">
|
||||
<h2>Select your WOKA</h2>
|
||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" class="selectCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ cameraScene }>Continue</button>
|
||||
<button type="submit" class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn" on:click|preventDefault={ customizeScene }>Customize your WOKA</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.selectCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.selectCharacterButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.selectCharacterButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
&.selectCharacterButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.selectCharacterScene button.selectCharacterButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.selectCharacterScene button.selectCharacterButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -4,7 +4,7 @@ import {RoomConnection} from "./RoomConnection";
|
|||
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
||||
import {localUserStore} from "./LocalUserStore";
|
||||
import {LocalUser} from "./LocalUser";
|
||||
import {CharacterTexture, LocalUser} from "./LocalUser";
|
||||
import {Room} from "./Room";
|
||||
|
||||
|
||||
|
@ -46,8 +46,8 @@ class ConnectionManager {
|
|||
urlManager.pushRoomIdToUrl(room);
|
||||
return Promise.resolve(room);
|
||||
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
|
||||
let localUser = localUserStore.getLocalUser();
|
||||
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
|
||||
this.localUser = localUser;
|
||||
try {
|
||||
|
@ -57,16 +57,42 @@ class ConnectionManager {
|
|||
console.error('JWT token invalid. Did it expire? Login anonymously instead.');
|
||||
await this.anonymousLogin();
|
||||
}
|
||||
} else {
|
||||
}else{
|
||||
await this.anonymousLogin();
|
||||
}
|
||||
let roomId: string
|
||||
|
||||
localUser = localUserStore.getLocalUser();
|
||||
if(!localUser){
|
||||
throw "Error to store local user data";
|
||||
}
|
||||
|
||||
let roomId: string;
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
roomId = START_ROOM_URL;
|
||||
} else {
|
||||
roomId = window.location.pathname + window.location.search + window.location.hash;
|
||||
}
|
||||
return Promise.resolve(new Room(roomId));
|
||||
|
||||
//get detail map for anonymous login and set texture in local storage
|
||||
const room = new Room(roomId);
|
||||
const mapDetail = await room.getMapDetail();
|
||||
if(mapDetail.textures != undefined && mapDetail.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if(localUser.textures.length === 0){
|
||||
localUser.textures = mapDetail.textures;
|
||||
}else{
|
||||
mapDetail.textures.forEach((newTexture) => {
|
||||
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
||||
if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
|
||||
return;
|
||||
}
|
||||
localUser?.textures.push(newTexture)
|
||||
});
|
||||
}
|
||||
this.localUser = localUser;
|
||||
localUserStore.saveUser(localUser);
|
||||
}
|
||||
return Promise.resolve(room);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Invalid URL'));
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import type {SignalData} from "simple-peer";
|
||||
import type {RoomConnection} from "./RoomConnection";
|
||||
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
|
@ -47,6 +45,7 @@ export interface MessageUserPositionInterface {
|
|||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
visitCardUrl: string|null;
|
||||
companion: string|null;
|
||||
}
|
||||
|
||||
|
@ -60,6 +59,7 @@ export interface MessageUserJoined {
|
|||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
visitCardUrl: string | null;
|
||||
companion: string|null;
|
||||
}
|
||||
|
||||
|
@ -85,11 +85,6 @@ export interface WebRtcSignalReceivedMessageInterface {
|
|||
webRtcPassword: string | undefined
|
||||
}
|
||||
|
||||
export interface StartMapInterface {
|
||||
mapUrlStart: string,
|
||||
startInstance: string
|
||||
}
|
||||
|
||||
export interface ViewportInterface {
|
||||
left: number,
|
||||
top: number,
|
||||
|
|
|
@ -9,9 +9,8 @@ export interface CharacterTexture {
|
|||
|
||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||
|
||||
export function isUserNameValid(value: string): boolean {
|
||||
const regexp = new RegExp('^[A-Za-z0-9]{1,'+maxUserNameLength+'}$');
|
||||
return regexp.test(value);
|
||||
export function isUserNameValid(value: unknown): boolean {
|
||||
return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && /\S/.test(value);
|
||||
}
|
||||
|
||||
export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||
|
@ -25,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
|
|||
}
|
||||
|
||||
export class LocalUser {
|
||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
|
||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import Axios from "axios";
|
||||
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
||||
import type {CharacterTexture} from "./LocalUser";
|
||||
|
||||
export class MapDetail{
|
||||
constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private mapUrl: string|undefined;
|
||||
private textures: CharacterTexture[]|undefined;
|
||||
private instance: string|undefined;
|
||||
private _search: URLSearchParams;
|
||||
|
||||
|
@ -50,10 +57,10 @@ export class Room {
|
|||
return {roomId, hash}
|
||||
}
|
||||
|
||||
public async getMapUrl(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (this.mapUrl !== undefined) {
|
||||
resolve(this.mapUrl);
|
||||
public async getMapDetail(): Promise<MapDetail> {
|
||||
return new Promise<MapDetail>((resolve, reject) => {
|
||||
if (this.mapUrl !== undefined && this.textures != undefined) {
|
||||
resolve(new MapDetail(this.mapUrl, this.textures));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -61,7 +68,7 @@ export class Room {
|
|||
const match = /_\/[^/]+\/(.+)/.exec(this.id);
|
||||
if (!match) throw new Error('Could not extract url from "'+this.id+'"');
|
||||
this.mapUrl = window.location.protocol+'//'+match[1];
|
||||
resolve(this.mapUrl);
|
||||
resolve(new MapDetail(this.mapUrl, this.textures));
|
||||
return;
|
||||
} else {
|
||||
// We have a private ID, we need to query the map URL from the server.
|
||||
|
@ -71,7 +78,7 @@ export class Room {
|
|||
params: urlParts
|
||||
}).then(({data}) => {
|
||||
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
||||
resolve(data.mapUrl);
|
||||
resolve(data);
|
||||
return;
|
||||
}).catch((reason) => {
|
||||
reject(reason);
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
EmoteEventMessage,
|
||||
EmotePromptMessage,
|
||||
SendUserMessage,
|
||||
BanUserMessage
|
||||
BanUserMessage,
|
||||
} from "../Messages/generated/messages_pb"
|
||||
|
||||
import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
||||
|
@ -177,9 +177,9 @@ export class RoomConnection implements RoomConnection {
|
|||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
this.closed = true;
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
this.closed = true;
|
||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
||||
|
@ -343,6 +343,7 @@ export class RoomConnection implements RoomConnection {
|
|||
userId: message.getUserid(),
|
||||
name: message.getName(),
|
||||
characterLayers,
|
||||
visitCardUrl: message.getVisitcardurl(),
|
||||
position: ProtobufClientUtils.toPointInterface(position),
|
||||
companion: companion ? companion.getName() : null
|
||||
}
|
||||
|
@ -617,7 +618,7 @@ export class RoomConnection implements RoomConnection {
|
|||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||
}
|
||||
|
||||
public getAllTag() : string[] {
|
||||
public getAllTags() : string[] {
|
||||
return this.tags;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
|||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
|
||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
|
||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
|
||||
|
||||
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
|
||||
|
||||
|
|
|
@ -28,13 +28,13 @@ export class MobileJoystick extends VirtualJoystick {
|
|||
this.visible = false;
|
||||
this.enable = false;
|
||||
|
||||
this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => {
|
||||
this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||
if (!pointer.wasTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's only display the joystick if there is one finger on the screen
|
||||
if (pointer.event.touches.length === 1) {
|
||||
if ((pointer.event as TouchEvent).touches.length === 1) {
|
||||
this.x = pointer.x;
|
||||
this.y = pointer.y;
|
||||
this.visible = true;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context';
|
||||
|
||||
/**
|
||||
* Class to measure the sound volume of a media stream
|
||||
*/
|
||||
|
@ -5,10 +7,10 @@ export class SoundMeter {
|
|||
private instant: number;
|
||||
private clip: number;
|
||||
//private script: ScriptProcessorNode;
|
||||
private analyser: AnalyserNode|undefined;
|
||||
private analyser: IAnalyserNode<IAudioContext>|undefined;
|
||||
private dataArray: Uint8Array|undefined;
|
||||
private context: AudioContext|undefined;
|
||||
private source: MediaStreamAudioSourceNode|undefined;
|
||||
private context: IAudioContext|undefined;
|
||||
private source: IMediaStreamAudioSourceNode<IAudioContext>|undefined;
|
||||
|
||||
constructor() {
|
||||
this.instant = 0.0;
|
||||
|
@ -16,7 +18,7 @@ export class SoundMeter {
|
|||
//this.script = context.createScriptProcessor(2048, 1, 1);
|
||||
}
|
||||
|
||||
private init(context: AudioContext) {
|
||||
private init(context: IAudioContext) {
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
|
||||
|
@ -25,8 +27,12 @@ export class SoundMeter {
|
|||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
public connectToSource(stream: MediaStream, context: AudioContext): void
|
||||
public connectToSource(stream: MediaStream, context: IAudioContext): void
|
||||
{
|
||||
if (this.source !== undefined) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
this.init(context);
|
||||
|
||||
this.source = this.context?.createMediaStreamSource(stream);
|
||||
|
@ -81,56 +87,3 @@ export class SoundMeter {
|
|||
|
||||
}
|
||||
|
||||
|
||||
// Meter class that generates a number correlated to audio volume.
|
||||
// The meter class itself displays nothing, but it makes the
|
||||
// instantaneous and time-decaying volumes available for inspection.
|
||||
// It also reports on the fraction of samples that were at or near
|
||||
// the top of the measurement range.
|
||||
/*function SoundMeter(context) {
|
||||
this.context = context;
|
||||
this.instant = 0.0;
|
||||
this.slow = 0.0;
|
||||
this.clip = 0.0;
|
||||
this.script = context.createScriptProcessor(2048, 1, 1);
|
||||
const that = this;
|
||||
this.script.onaudioprocess = function(event) {
|
||||
const input = event.inputBuffer.getChannelData(0);
|
||||
let i;
|
||||
let sum = 0.0;
|
||||
let clipcount = 0;
|
||||
for (i = 0; i < input.length; ++i) {
|
||||
sum += input[i] * input[i];
|
||||
if (Math.abs(input[i]) > 0.99) {
|
||||
clipcount += 1;
|
||||
}
|
||||
}
|
||||
that.instant = Math.sqrt(sum / input.length);
|
||||
that.slow = 0.95 * that.slow + 0.05 * that.instant;
|
||||
that.clip = clipcount / input.length;
|
||||
};
|
||||
}
|
||||
|
||||
SoundMeter.prototype.connectToSource = function(stream, callback) {
|
||||
console.log('SoundMeter connecting');
|
||||
try {
|
||||
this.mic = this.context.createMediaStreamSource(stream);
|
||||
this.mic.connect(this.script);
|
||||
// necessary to make sample run, but should not be.
|
||||
this.script.connect(this.context.destination);
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(null);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SoundMeter.prototype.stop = function() {
|
||||
this.mic.disconnect();
|
||||
this.script.disconnect();
|
||||
};
|
||||
*/
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import Container = Phaser.GameObjects.Container;
|
||||
import type {Scene} from "phaser";
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
|
||||
|
||||
export class SoundMeterSprite extends Container {
|
||||
private rectangles: Rectangle[] = new Array<Rectangle>();
|
||||
private static readonly NB_BARS = 20;
|
||||
|
||||
constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) {
|
||||
super(scene, x, y, children);
|
||||
|
||||
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
|
||||
const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16));
|
||||
this.add(rectangle);
|
||||
this.rectangles.push(rectangle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A number between 0 and 100
|
||||
*
|
||||
* @param volume
|
||||
*/
|
||||
public setVolume(volume: number): void {
|
||||
|
||||
const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS;
|
||||
for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
|
||||
if (normalizedVolume < i) {
|
||||
this.rectangles[i].alpha = 0.5;
|
||||
} else {
|
||||
this.rectangles[i].alpha = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getWidth(): number {
|
||||
return SoundMeterSprite.NB_BARS * 13;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
|
||||
import {SpeechBubble} from "./SpeechBubble";
|
||||
import BitmapText = Phaser.GameObjects.BitmapText;
|
||||
import Text = Phaser.GameObjects.Text;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {TextureError} from "../../Exception/TextureError";
|
||||
|
@ -8,6 +8,7 @@ import {Companion} from "../Companion/Companion";
|
|||
import type {GameScene} from "../Game/GameScene";
|
||||
import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
|
||||
const playerNameY = - 25;
|
||||
|
||||
|
@ -23,7 +24,7 @@ const interactiveRadius = 35;
|
|||
|
||||
export abstract class Character extends Container {
|
||||
private bubble: SpeechBubble|null = null;
|
||||
private readonly playerName: BitmapText;
|
||||
private readonly playerName: Text;
|
||||
public PlayerValue: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||
|
@ -32,6 +33,7 @@ export abstract class Character extends Container {
|
|||
public companion?: Companion;
|
||||
private emote: Phaser.GameObjects.Sprite | null = null;
|
||||
private emoteTween: Phaser.Tweens.Tween|null = null;
|
||||
scene: GameScene;
|
||||
|
||||
constructor(scene: GameScene,
|
||||
x: number,
|
||||
|
@ -41,10 +43,12 @@ export abstract class Character extends Container {
|
|||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
frame: string | number,
|
||||
isClickable: boolean,
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(scene, x, y/*, texture, frame*/);
|
||||
this.scene = scene;
|
||||
this.PlayerValue = name;
|
||||
this.invisible = true
|
||||
|
||||
|
@ -56,16 +60,29 @@ export abstract class Character extends Container {
|
|||
this.invisible = false
|
||||
})
|
||||
|
||||
this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
|
||||
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"});
|
||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerName);
|
||||
|
||||
if (this.isClickable()) {
|
||||
if (isClickable) {
|
||||
this.setInteractive({
|
||||
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||
useHandCursor: true,
|
||||
});
|
||||
|
||||
this.on('pointerover',() => {
|
||||
this.getOutlinePlugin()?.add(this.playerName, {
|
||||
thickness: 2,
|
||||
outlineColor: 0xffff00
|
||||
});
|
||||
this.scene.markDirty();
|
||||
});
|
||||
this.on('pointerout',() => {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.scene.markDirty();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
scene.add.existing(this);
|
||||
|
@ -79,23 +96,25 @@ export abstract class Character extends Container {
|
|||
this.setDepth(-1);
|
||||
|
||||
this.playAnimation(direction, moving);
|
||||
|
||||
|
||||
if (typeof companion === 'string') {
|
||||
this.addCompanion(companion, companionTexturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
private getOutlinePlugin(): OutlinePipelinePlugin|undefined {
|
||||
return this.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined;
|
||||
}
|
||||
|
||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||
if (typeof texturePromise !== 'undefined') {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract isClickable(): boolean;
|
||||
|
||||
public addTextures(textures: string[], frame?: string | number): void {
|
||||
for (const texture of textures) {
|
||||
if(!this.scene.textures.exists(texture)){
|
||||
if(this.scene && !this.scene.textures.exists(texture)){
|
||||
throw new TextureError('texture not found');
|
||||
}
|
||||
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
|
||||
|
@ -240,28 +259,28 @@ export abstract class Character extends Container {
|
|||
this.scene.sys.updateList.remove(sprite);
|
||||
}
|
||||
}
|
||||
this.list.forEach(objectContaining => objectContaining.destroy())
|
||||
this.list.forEach(objectContaining => objectContaining.destroy())
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
||||
playEmote(emoteKey: string) {
|
||||
this.cancelPreviousEmote();
|
||||
|
||||
const scalingFactor = waScaleManager.uiScalingFactor * 0.05;
|
||||
const emoteY = -30 - scalingFactor * 10;
|
||||
|
||||
|
||||
this.playerName.setVisible(false);
|
||||
this.emote = new Sprite(this.scene, 0, 0, emoteKey);
|
||||
this.emote.setAlpha(0);
|
||||
this.emote.setScale(0.1 * scalingFactor);
|
||||
this.add(this.emote);
|
||||
this.scene.sys.updateList.add(this.emote);
|
||||
|
||||
|
||||
this.createStartTransition(scalingFactor, emoteY);
|
||||
}
|
||||
|
||||
private createStartTransition(scalingFactor: number, emoteY: number) {
|
||||
this.emoteTween = this.scene.tweens.add({
|
||||
this.emoteTween = this.scene?.tweens.add({
|
||||
targets: this.emote,
|
||||
props: {
|
||||
scale: scalingFactor,
|
||||
|
@ -277,7 +296,7 @@ export abstract class Character extends Container {
|
|||
}
|
||||
|
||||
private startPulseTransition(emoteY: number, scalingFactor: number) {
|
||||
this.emoteTween = this.scene.tweens.add({
|
||||
this.emoteTween = this.scene?.tweens.add({
|
||||
targets: this.emote,
|
||||
props: {
|
||||
y: emoteY * 1.3,
|
||||
|
@ -294,7 +313,7 @@ export abstract class Character extends Container {
|
|||
}
|
||||
|
||||
private startExitTransition(emoteY: number) {
|
||||
this.emoteTween = this.scene.tweens.add({
|
||||
this.emoteTween = this.scene?.tweens.add({
|
||||
targets: this.emote,
|
||||
props: {
|
||||
alpha: 0,
|
||||
|
|
20
front/src/Phaser/Entity/CustomizedCharacter.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import Container = Phaser.GameObjects.Container;
|
||||
import type {Scene} from "phaser";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
|
||||
/**
|
||||
* A sprite of a customized character (used in the Customize Scene only)
|
||||
*/
|
||||
export class CustomizedCharacter extends Container {
|
||||
public constructor(scene: Scene, x: number, y: number, layers: string[]) {
|
||||
super(scene, x, y);
|
||||
this.updateSprites(layers);
|
||||
}
|
||||
|
||||
public updateSprites(layers: string[]): void {
|
||||
this.removeAll(true);
|
||||
for (const layer of layers) {
|
||||
this.add(new Sprite(this.scene, 0, 0, layer));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,15 @@ import type {GameScene} from "../Game/GameScene";
|
|||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {Character} from "../Entity/Character";
|
||||
import type {PlayerAnimationDirections} from "../Player/Animation";
|
||||
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
||||
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
*/
|
||||
export class RemotePlayer extends Character {
|
||||
userId: number;
|
||||
private visitCardUrl: string|null;
|
||||
|
||||
constructor(
|
||||
userId: number,
|
||||
|
@ -18,13 +21,19 @@ export class RemotePlayer extends Character {
|
|||
texturesPromise: Promise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
visitCardUrl: string|null,
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, !!visitCardUrl, companion, companionTexturePromise);
|
||||
|
||||
//set data
|
||||
this.userId = userId;
|
||||
this.visitCardUrl = visitCardUrl;
|
||||
|
||||
this.on('pointerdown', () => {
|
||||
requestVisitCardsStore.set(this.visitCardUrl);
|
||||
})
|
||||
}
|
||||
|
||||
updatePosition(position: PointInterface): void {
|
||||
|
@ -38,8 +47,4 @@ export class RemotePlayer extends Character {
|
|||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||
}
|
||||
}
|
||||
|
||||
isClickable(): boolean {
|
||||
return false; //todo: make remote players clickable if they are logged in.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ export interface AddPlayerInterface {
|
|||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
visitCardUrl: string|null;
|
||||
companion: string|null;
|
||||
}
|
||||
|
|
|
@ -70,7 +70,11 @@ export abstract class DirtyScene extends ResizableScene {
|
|||
return this.dirty || this.objectListChanged;
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
public markDirty(): void {
|
||||
this.events.once(Phaser.Scenes.Events.POST_UPDATE, () => this.dirty = true);
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
this.objectListChanged = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,14 +21,22 @@ export class Game extends Phaser.Game {
|
|||
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
|
||||
super(GameConfig);
|
||||
|
||||
window.addEventListener('resize', (event) => {
|
||||
this.scale.on(Phaser.Scale.Events.RESIZE, () => {
|
||||
for (const scene of this.scene.getScenes(true)) {
|
||||
if (scene instanceof ResizableScene) {
|
||||
scene.onResize();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/*window.addEventListener('resize', (event) => {
|
||||
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
||||
for (const scene of this.scene.getScenes(true)) {
|
||||
if (scene instanceof ResizableScene) {
|
||||
scene.onResize(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
public step(time: number, delta: number)
|
||||
|
|
|
@ -2,11 +2,13 @@ import {GameScene} from "./GameScene";
|
|||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import type {Room} from "../../Connexion/Room";
|
||||
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
|
||||
import {LoginSceneName} from "../Login/LoginScene";
|
||||
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
|
||||
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {get} from "svelte/store";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
|
||||
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
|
||||
|
||||
|
||||
|
||||
|
@ -71,11 +73,11 @@ export class GameManager {
|
|||
|
||||
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
||||
const roomID = room.id;
|
||||
const mapUrl = await room.getMapUrl();
|
||||
const mapDetail = await room.getMapDetail();
|
||||
|
||||
const gameIndex = scenePlugin.getIndex(roomID);
|
||||
if(gameIndex === -1){
|
||||
const game : Phaser.Scene = new GameScene(room, mapUrl);
|
||||
const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl);
|
||||
scenePlugin.add(roomID, game, false);
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +86,11 @@ export class GameManager {
|
|||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
||||
scenePlugin.launch(MenuSceneName);
|
||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||
|
||||
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
|
||||
helpCameraSettingsVisibleStore.set(true);
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
}
|
||||
}
|
||||
|
||||
public gameSceneIsCreated(scene: GameScene) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type {ITiledMap, ITiledMapLayer, ITiledMapTileLayer} from "../Map/ITiledMap";
|
||||
import type {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty} from "../Map/ITiledMap";
|
||||
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
|
||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
|
||||
import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes";
|
||||
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
||||
|
||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
||||
|
||||
|
@ -10,12 +10,16 @@ export type PropertyChangeCallback = (newValue: string | number | boolean | unde
|
|||
* It is used to handle layer properties.
|
||||
*/
|
||||
export class GameMap {
|
||||
private key: number|undefined;
|
||||
private lastProperties = new Map<string, string|boolean|number>();
|
||||
private key: number | undefined;
|
||||
private lastProperties = new Map<string, string | boolean | number>();
|
||||
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
|
||||
|
||||
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapLayerProperty> } = {}
|
||||
public readonly flatLayers: ITiledMapLayer[];
|
||||
public readonly phaserLayers: TilemapLayer[] = [];
|
||||
|
||||
public exitUrls: Array<string> = []
|
||||
|
||||
public constructor(private map: ITiledMap, phaserMap: Phaser.Tilemaps.Tilemap, terrains: Array<Phaser.Tilemaps.Tileset>) {
|
||||
this.flatLayers = flattenGroupLayersMap(map);
|
||||
let depth = -2;
|
||||
|
@ -27,8 +31,22 @@ export class GameMap {
|
|||
depth = DEPTH_OVERLAY_INDEX;
|
||||
}
|
||||
}
|
||||
for (const tileset of map.tilesets) {
|
||||
tileset?.tiles?.forEach(tile => {
|
||||
if (tile.properties) {
|
||||
this.tileSetPropertyMap[tileset.firstgid + tile.id] = tile.properties
|
||||
tile.properties.forEach(prop => {
|
||||
if (prop.name == "exitUrl" && typeof prop.value == "string") {
|
||||
this.exitUrls.push(prop.value);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the position of the current player (in pixels)
|
||||
* This will trigger events if properties are changing.
|
||||
|
@ -63,21 +81,27 @@ export class GameMap {
|
|||
}
|
||||
}
|
||||
|
||||
public getCurrentProperties(): Map<string, string|boolean|number> {
|
||||
public getCurrentProperties(): Map<string, string | boolean | number> {
|
||||
return this.lastProperties;
|
||||
}
|
||||
|
||||
private getProperties(key: number): Map<string, string|boolean|number> {
|
||||
const properties = new Map<string, string|boolean|number>();
|
||||
private getProperties(key: number): Map<string, string | boolean | number> {
|
||||
const properties = new Map<string, string | boolean | number>();
|
||||
|
||||
for (const layer of this.flatLayers) {
|
||||
if (layer.type !== 'tilelayer') {
|
||||
continue;
|
||||
}
|
||||
const tiles = layer.data as number[];
|
||||
if (tiles[key] == 0) {
|
||||
continue;
|
||||
|
||||
let tileIndex: number | undefined = undefined;
|
||||
if (layer.data) {
|
||||
const tiles = layer.data as number[];
|
||||
if (tiles[key] == 0) {
|
||||
continue;
|
||||
}
|
||||
tileIndex = tiles[key]
|
||||
}
|
||||
|
||||
// There is a tile in this layer, let's embed the properties
|
||||
if (layer.properties !== undefined) {
|
||||
for (const layerProperty of layer.properties) {
|
||||
|
@ -87,6 +111,16 @@ export class GameMap {
|
|||
properties.set(layerProperty.name, layerProperty.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (tileIndex) {
|
||||
this.tileSetPropertyMap[tileIndex]?.forEach(property => {
|
||||
if (property.value) {
|
||||
properties.set(property.name, property.value)
|
||||
} else if (properties.has(property.name)) {
|
||||
properties.delete(property.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
|
@ -118,11 +152,11 @@ export class GameMap {
|
|||
}
|
||||
|
||||
public findLayer(layerName: string): ITiledMapLayer | undefined {
|
||||
return this.flatLayers.find((layer) => layer.name = layerName);
|
||||
return this.flatLayers.find((layer) => layer.name === layerName);
|
||||
}
|
||||
|
||||
public findPhaserLayer(layerName: string): TilemapLayer | undefined {
|
||||
return this.phaserLayers.find((layer) => layer.layer.name = layerName);
|
||||
return this.phaserLayers.find((layer) => layer.layer.name === layerName);
|
||||
}
|
||||
|
||||
public addTerrain(terrain : Phaser.Tilemaps.Tileset): void {
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import {gameManager} from "./GameManager";
|
||||
import { Queue } from 'queue-typescript';
|
||||
import type { Subscription } from "rxjs";
|
||||
import { GlobalMessageManager } from "../../Administration/GlobalMessageManager";
|
||||
import { userMessageManager } from "../../Administration/UserMessageManager";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import type {
|
||||
GroupCreatedUpdatedMessageInterface,
|
||||
MessageUserJoined,
|
||||
|
@ -9,72 +14,73 @@ import type {
|
|||
PositionInterface,
|
||||
RoomJoinedMessageInterface
|
||||
} from "../../Connexion/ConnexionModels";
|
||||
import { hasMovedEventName, Player , requestEmoteEventName} from "../Player/Player";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { Room } from "../../Connexion/Room";
|
||||
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
||||
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
|
||||
import {
|
||||
DEBUG_MODE,
|
||||
JITSI_PRIVATE_MODE,
|
||||
MAX_PER_GROUP,
|
||||
POSITION_DELAY,
|
||||
POSITION_DELAY
|
||||
} from "../../Enum/EnvironmentVariable";
|
||||
import type {
|
||||
ITiledMap,
|
||||
ITiledMapLayer,
|
||||
ITiledMapLayerProperty,
|
||||
ITiledMapObject,
|
||||
ITiledText,
|
||||
ITiledMapTileLayer,
|
||||
ITiledTileSet
|
||||
} from "../Map/ITiledMap";
|
||||
import type { AddPlayerInterface } from "./AddPlayerInterface";
|
||||
import { PlayerAnimationDirections } from "../Player/Animation";
|
||||
import { PlayerMovement } from "./PlayerMovement";
|
||||
import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
import { Queue } from 'queue-typescript';
|
||||
import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer";
|
||||
import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene";
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { TextureError } from "../../Exception/TextureError";
|
||||
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
|
||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||
import { peerStore } from "../../Stores/PeerStore";
|
||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||
import { urlManager } from "../../Url/UrlManager";
|
||||
import { audioManager } from "../../WebRtc/AudioManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||
import {
|
||||
CenterListener,
|
||||
AUDIO_LOOP_PROPERTY, AUDIO_VOLUME_PROPERTY, CenterListener,
|
||||
JITSI_MESSAGE_PROPERTIES,
|
||||
layoutManager,
|
||||
LayoutMode,
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
TRIGGER_JITSI_PROPERTIES,
|
||||
TRIGGER_WEBSITE_PROPERTIES,
|
||||
WEBSITE_MESSAGE_PROPERTIES,
|
||||
AUDIO_VOLUME_PROPERTY,
|
||||
AUDIO_LOOP_PROPERTY
|
||||
WEBSITE_MESSAGE_PROPERTIES
|
||||
} from "../../WebRtc/LayoutManager";
|
||||
import { GameMap } from "./GameMap";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
||||
import type { ActionableItem } from "../Items/ActionableItem";
|
||||
import { UserInputManager } from "../UserInput/UserInputManager";
|
||||
import {soundManager} from "./SoundManager";
|
||||
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
|
||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
||||
import { GlobalMessageManager } from "../../Administration/GlobalMessageManager";
|
||||
import { userMessageManager } from "../../Administration/UserMessageManager";
|
||||
import { ConsoleGlobalMessageManager } from "../../Administration/ConsoleGlobalMessageManager";
|
||||
import { ResizableScene } from "../Login/ResizableScene";
|
||||
import { Room } from "../../Connexion/Room";
|
||||
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||
import { urlManager } from "../../Url/UrlManager";
|
||||
import { audioManager } from "../../WebRtc/AudioManager";
|
||||
import { PresentationModeIcon } from "../Components/PresentationModeIcon";
|
||||
import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import { ChatModeIcon } from "../Components/ChatModeIcon";
|
||||
import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||
import { TextureError } from "../../Exception/TextureError";
|
||||
import { addLoader } from "../Components/Loader";
|
||||
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
|
||||
import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
|
||||
import { PresentationModeIcon } from "../Components/PresentationModeIcon";
|
||||
import { TextUtils } from "../Components/TextUtils";
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
import type { ActionableItem } from "../Items/ActionableItem";
|
||||
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||
import type {
|
||||
ITiledMap,
|
||||
ITiledMapLayer,
|
||||
ITiledMapLayerProperty,
|
||||
ITiledMapObject,
|
||||
ITiledMapTileLayer,
|
||||
ITiledTileSet } from "../Map/ITiledMap";
|
||||
import { MenuScene, MenuSceneName } from '../Menu/MenuScene';
|
||||
import { PlayerAnimationDirections } from "../Player/Animation";
|
||||
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
|
||||
import { ErrorSceneName } from "../Reconnecting/ErrorScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { PinchManager } from "../UserInput/PinchManager";
|
||||
import { UserInputManager } from "../UserInput/UserInputManager";
|
||||
import type { AddPlayerInterface } from "./AddPlayerInterface";
|
||||
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
|
||||
import { DirtyScene } from "./DirtyScene";
|
||||
import { EmoteManager } from "./EmoteManager";
|
||||
import { gameManager } from "./GameManager";
|
||||
import { GameMap } from "./GameMap";
|
||||
import { PlayerMovement } from "./PlayerMovement";
|
||||
import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||
|
@ -82,22 +88,12 @@ import GameObject = Phaser.GameObjects.GameObject;
|
|||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import EVENT_TYPE = Phaser.Scenes.Events
|
||||
import type { Subscription } from "rxjs";
|
||||
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import RenderTexture = Phaser.GameObjects.RenderTexture;
|
||||
import Tilemap = Phaser.Tilemaps.Tilemap;
|
||||
import {DirtyScene} from "./DirtyScene";
|
||||
import {TextUtils} from "../Components/TextUtils";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
|
||||
import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import {peerStore} from "../../Stores/PeerStore";
|
||||
import {EmoteManager} from "./EmoteManager";
|
||||
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
|
||||
import { MenuScene, MenuSceneName } from '../Menu/MenuScene';
|
||||
|
||||
import AnimatedTiles from "phaser-animated-tiles";
|
||||
import {soundManager} from "./SoundManager";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null,
|
||||
|
@ -144,6 +140,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
Map!: Phaser.Tilemaps.Tilemap;
|
||||
Objects!: Array<Phaser.Physics.Arcade.Sprite>;
|
||||
mapFile!: ITiledMap;
|
||||
animatedTiles!: AnimatedTiles;
|
||||
groups: Map<number, Sprite>;
|
||||
startX!: number;
|
||||
startY!: number;
|
||||
|
@ -155,13 +152,13 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
public connection: RoomConnection | undefined;
|
||||
private simplePeer!: SimplePeer;
|
||||
private GlobalMessageManager!: GlobalMessageManager;
|
||||
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
|
||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||
private connectionAnswerPromiseResolve!: (value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
||||
private createPromise: Promise<void>;
|
||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||
private iframeSubscriptionList!: Array<Subscription>;
|
||||
private peerStoreUnsubscribe!: () => void;
|
||||
MapUrlFile: string;
|
||||
RoomId: string;
|
||||
instance: string;
|
||||
|
@ -230,11 +227,11 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
this.load.image(joystickBaseKey, joystickBaseImg);
|
||||
this.load.image(joystickThumbKey, joystickThumbImg);
|
||||
}
|
||||
//todo: in an emote manager.
|
||||
this.load.spritesheet('emote-music', 'resources/emotes/pipo-popupemotes005.png', {
|
||||
frameHeight: 32,
|
||||
frameWidth: 32,
|
||||
});
|
||||
this.load.audio('audio-webrtc-in', '/resources/objects/webrtc-in.mp3');
|
||||
this.load.audio('audio-webrtc-out', '/resources/objects/webrtc-out.mp3');
|
||||
//this.load.audio('audio-report-message', '/resources/objects/report-message.mp3');
|
||||
this.sound.pauseOnBlur = false;
|
||||
|
||||
this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
|
||||
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
|
||||
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
|
||||
|
@ -267,6 +264,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
message: this.originalMapUrl ?? file.src
|
||||
});
|
||||
});
|
||||
this.load.scenePlugin('AnimatedTiles', AnimatedTiles, 'animatedTiles', 'animatedTiles');
|
||||
this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||
this.onMapLoad(data);
|
||||
});
|
||||
|
@ -281,6 +279,14 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
|
||||
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', { frameWidth: 32, frameHeight: 32 });
|
||||
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
//eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(this.load as any).rexWebFont({
|
||||
custom: {
|
||||
families: ['Press Start 2P'],
|
||||
urls: ['/resources/fonts/fonts.css'],
|
||||
testString: 'abcdefg'
|
||||
},
|
||||
});
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
|
@ -435,6 +441,10 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
}
|
||||
}
|
||||
|
||||
this.gameMap.exitUrls.forEach(exitUrl => {
|
||||
this.loadNextGame(exitUrl)
|
||||
})
|
||||
|
||||
this.initStartXAndStartY();
|
||||
|
||||
//add entities
|
||||
|
@ -458,6 +468,9 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
|
||||
this.initCamera();
|
||||
|
||||
this.animatedTiles.init(this.Map);
|
||||
this.events.on('tileanimationupdate', () => this.dirty = true);
|
||||
|
||||
this.initCirclesCanvas();
|
||||
|
||||
// Let's pause the scene if the connection is not established yet
|
||||
|
@ -509,6 +522,21 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
}
|
||||
|
||||
this.emoteManager = new EmoteManager(this);
|
||||
|
||||
let oldPeerNumber = 0;
|
||||
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
|
||||
const newPeerNumber = peers.size;
|
||||
if (newPeerNumber > oldPeerNumber) {
|
||||
this.sound.play('audio-webrtc-in', {
|
||||
volume: 0.2
|
||||
});
|
||||
} else if (newPeerNumber < oldPeerNumber) {
|
||||
this.sound.play('audio-webrtc-out', {
|
||||
volume: 0.2
|
||||
});
|
||||
}
|
||||
oldPeerNumber = newPeerNumber;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -541,6 +569,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
characterLayers: message.characterLayers,
|
||||
name: message.name,
|
||||
position: message.position,
|
||||
visitCardUrl: message.visitCardUrl,
|
||||
companion: message.companion
|
||||
}
|
||||
this.addPlayer(userMessage);
|
||||
|
@ -645,7 +674,6 @@ export class GameScene extends DirtyScene implements CenterListener {
|
|||
//this.initUsersPosition(roomJoinedMessage.users);
|
||||
this.connectionAnswerPromiseResolve(onConnect.room);
|
||||
// Analyze tags to find if we are admin. If yes, show console.
|
||||
this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager, this.connection.isAdmin());
|
||||
|
||||
|
||||
this.scene.wake();
|
||||
|
@ -868,49 +896,31 @@ ${escapedMessage}
|
|||
this.userInputManager.disableControls();
|
||||
}));
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent)=>
|
||||
{
|
||||
const url = new URL(playSoundEvent.url, this.MapUrlFile);
|
||||
soundManager.playSound(this.load,this.sound,url.toString(),playSoundEvent.config);
|
||||
}))
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent)=>
|
||||
{
|
||||
const url = new URL(stopSoundEvent.url, this.MapUrlFile);
|
||||
soundManager.stopSound(this.sound,url.toString());
|
||||
this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent) => {
|
||||
const url = new URL(playSoundEvent.url, this.MapUrlFile);
|
||||
soundManager.playSound(this.load, this.sound, url.toString(), playSoundEvent.config);
|
||||
}))
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent)=>
|
||||
{
|
||||
this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent) => {
|
||||
const url = new URL(stopSoundEvent.url, this.MapUrlFile);
|
||||
soundManager.stopSound(this.sound, url.toString());
|
||||
}))
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent) => {
|
||||
const url = new URL(loadSoundEvent.url, this.MapUrlFile);
|
||||
soundManager.loadSound(this.load,this.sound,url.toString());
|
||||
soundManager.loadSound(this.load, this.sound, url.toString());
|
||||
}))
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{
|
||||
this.userInputManager.restoreControls();
|
||||
}))
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.changeTileStream.subscribe(event => {
|
||||
for (const eventTile of event) {
|
||||
const layer = this.gameMap.findPhaserLayer(eventTile.layer);
|
||||
if (layer) {
|
||||
console.log('layer : ', layer);
|
||||
const tile = layer.getTileAt(eventTile.x, eventTile.y, true)
|
||||
if (typeof eventTile.tile == "string") {
|
||||
const tileIndex = this.getIndexForTileType(eventTile.tile);
|
||||
if (tileIndex) {
|
||||
tile.index = tileIndex
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
tile.index = eventTile.tile //+ firsrtgid du layer
|
||||
}
|
||||
}
|
||||
}
|
||||
//this.dirty = true;
|
||||
}))
|
||||
|
||||
}));
|
||||
this.iframeSubscriptionList.push(iframeListener.loadPageStream.subscribe((url: string) => {
|
||||
this.loadNextGame(url).then(() => {
|
||||
this.events.once(EVENT_TYPE.POST_UPDATE, () => {
|
||||
this.onMapExit(url);
|
||||
})
|
||||
})
|
||||
}));
|
||||
let scriptedBubbleSprite: Sprite;
|
||||
this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(() => {
|
||||
scriptedBubbleSprite = new Sprite(this, this.CurrentPlayer.x + 25, this.CurrentPlayer.y, 'circleSprite-white');
|
||||
|
@ -939,23 +949,16 @@ ${escapedMessage}
|
|||
}))
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => {
|
||||
iframeListener.sendFrozenGameStateEvent({
|
||||
iframeListener.sendGameStateEvent({
|
||||
mapUrl: this.MapUrlFile,
|
||||
startLayerName: this.startLayerName,
|
||||
uuid: localUserStore.getLocalUser()?.uuid,
|
||||
nickname: localUserStore.getName(),
|
||||
roomId: this.RoomId,
|
||||
tags: this.connection ? this.connection.getAllTag() : []
|
||||
tags: this.connection ? this.connection.getAllTags() : []
|
||||
})
|
||||
}));
|
||||
|
||||
/* this.iframeSubscriptionList.push(iframeListener.tilesetLoaderStream.subscribe((tileset) => {
|
||||
//this.load.tilemapTiledJSON('logo', tileset.imgUrl);
|
||||
this.load.image('logo', tileset.imgUrl);
|
||||
this.Terrains.push(this.Map.addTilesetImage(tileset.name, tileset.imgUrl, tileset.tilewidth, tileset.tileheight, tileset.margin, tileset.spacing));
|
||||
this.gameMap.addTerrain(this.Terrains[this.Terrains.length - 1]);
|
||||
}))*/
|
||||
|
||||
}
|
||||
|
||||
private setPropertyLayer(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
||||
|
@ -1045,6 +1048,9 @@ ${escapedMessage}
|
|||
this.userInputManager.destroy();
|
||||
this.pinchManager?.destroy();
|
||||
this.emoteManager.destroy();
|
||||
this.peerStoreUnsubscribe();
|
||||
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
for (const iframeEvents of this.iframeSubscriptionList) {
|
||||
iframeEvents.unsubscribe();
|
||||
|
@ -1156,10 +1162,10 @@ ${escapedMessage}
|
|||
}
|
||||
|
||||
//todo: push that into the gameManager
|
||||
private loadNextGame(exitSceneIdentifier: string) : void{
|
||||
private loadNextGame(exitSceneIdentifier: string) : Promise<void>{
|
||||
const { roomId, hash } = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
||||
const room = new Room(roomId);
|
||||
gameManager.loadMap(room, this.scene).catch(() => {});
|
||||
return gameManager.loadMap(room, this.scene).catch(() => { });
|
||||
}
|
||||
|
||||
private startUser(layer: ITiledMapTileLayer): PositionInterface {
|
||||
|
@ -1233,7 +1239,10 @@ ${escapedMessage}
|
|||
this.companion,
|
||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||
);
|
||||
this.CurrentPlayer.on('pointerdown', () => {
|
||||
this.CurrentPlayer.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||
if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) {
|
||||
return; //we don't want the menu to open when pinching on a touch screen.
|
||||
}
|
||||
this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements))
|
||||
})
|
||||
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
|
||||
|
@ -1433,6 +1442,7 @@ ${escapedMessage}
|
|||
texturesPromise,
|
||||
addPlayerData.position.direction as PlayerAnimationDirections,
|
||||
addPlayerData.position.moving,
|
||||
addPlayerData.visitCardUrl,
|
||||
addPlayerData.companion,
|
||||
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
|
||||
);
|
||||
|
@ -1539,8 +1549,8 @@ ${escapedMessage}
|
|||
this.connection?.emitActionableEvent(itemId, eventName, state, parameters);
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
super.onResize(ev);
|
||||
public onResize(): void {
|
||||
super.onResize();
|
||||
this.reposition();
|
||||
|
||||
// Send new viewport to server
|
||||
|
|
|
@ -17,7 +17,9 @@ class SoundManager {
|
|||
return res(sound);
|
||||
}
|
||||
loadPlugin.audio(soundUrl, soundUrl);
|
||||
loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl)));
|
||||
loadPlugin.once('filecomplete-audio-' + soundUrl, () => {
|
||||
res(soundManager.add(soundUrl));
|
||||
});
|
||||
loadPlugin.start();
|
||||
});
|
||||
this.soundPromises.set(soundUrl,soundPromise);
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
* It has coordinates and an "activation radius"
|
||||
*/
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {OutlinePipeline} from "../Shaders/OutlinePipeline";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
|
||||
type EventCallback = (state: unknown, parameters: unknown) => void;
|
||||
|
||||
|
@ -42,11 +42,11 @@ export class ActionableItem {
|
|||
return;
|
||||
}
|
||||
this.isSelectable = true;
|
||||
if (this.sprite.pipeline) {
|
||||
// Commented out to try to fix MacOS issue
|
||||
/*this.sprite.setPipeline(OutlinePipeline.KEY);
|
||||
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/
|
||||
}
|
||||
|
||||
this.getOutlinePlugin()?.add(this.sprite, {
|
||||
thickness: 2,
|
||||
outlineColor: 0xffff00
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,8 +57,11 @@ export class ActionableItem {
|
|||
return;
|
||||
}
|
||||
this.isSelectable = false;
|
||||
// Commented out to try to fix MacOS issue
|
||||
//this.sprite.resetPipeline();
|
||||
this.getOutlinePlugin()?.remove(this.sprite);
|
||||
}
|
||||
|
||||
private getOutlinePlugin(): OutlinePipelinePlugin|undefined {
|
||||
return this.sprite.scene.plugins.get('rexOutlinePipeline') as unknown as OutlinePipelinePlugin|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,30 +2,32 @@ import {EnableCameraSceneName} from "./EnableCameraScene";
|
|||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import { MenuScene } from "../Menu/MenuScene";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import {isMobile} from "../../Enum/EnvironmentVariable";
|
||||
import {CustomizedCharacter} from "../Entity/CustomizedCharacter";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
export const CustomizeSceneKey = "CustomizeScene";
|
||||
const customizeSceneKey = 'customizeScene';
|
||||
|
||||
export class CustomizeScene extends AbstractCharacterScene {
|
||||
private Rectangle!: Rectangle;
|
||||
|
||||
private selectedLayers: number[] = [0];
|
||||
private containersRow: Container[][] = [];
|
||||
private activeRow:number = 0;
|
||||
private containersRow: CustomizedCharacter[][] = [];
|
||||
public activeRow:number = 0;
|
||||
private layers: BodyResourceDescriptionInterface[][] = [];
|
||||
|
||||
private customizeSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
||||
|
||||
private moveHorizontally: number = 0;
|
||||
private moveVertically: number = 0;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -34,9 +36,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(customizeSceneKey, 'resources/html/CustomCharacterScene.html');
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
|
||||
|
@ -44,43 +44,28 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
});
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.customizeSceneElement = this.add.dom(-1000, 0).createFromCache(customizeSceneKey);
|
||||
this.centerXDomElement(this.customizeSceneElement, 150);
|
||||
MenuScene.revealMenusAfterInit(this.customizeSceneElement, customizeSceneKey);
|
||||
|
||||
this.customizeSceneElement.addListener('click');
|
||||
this.customizeSceneElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'customizeSceneButtonLeft') {
|
||||
this.moveCursorHorizontally(-1);
|
||||
}else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonRight') {
|
||||
this.moveCursorHorizontally(1);
|
||||
}else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonDown') {
|
||||
this.moveCursorVertically(1);
|
||||
}else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonUp') {
|
||||
this.moveCursorVertically(-1);
|
||||
}else if((event?.target as HTMLInputElement).id === 'customizeSceneFormBack') {
|
||||
if(this.activeRow > 0){
|
||||
this.moveCursorVertically(-1);
|
||||
}else{
|
||||
this.backToPreviousScene();
|
||||
}
|
||||
}else if((event?.target as HTMLButtonElement).id === 'customizeSceneFormSubmit') {
|
||||
if(this.activeRow < 5){
|
||||
this.moveCursorVertically(1);
|
||||
}else{
|
||||
this.nextSceneToCamera();
|
||||
}
|
||||
}
|
||||
customCharacterSceneVisibleStore.set(true);
|
||||
this.events.addListener('wake', () => {
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
||||
customCharacterSceneVisibleStore.set(true);
|
||||
});
|
||||
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
||||
|
||||
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33)
|
||||
this.Rectangle.setStrokeStyle(2, 0xFFFFFF);
|
||||
this.add.existing(this.Rectangle);
|
||||
|
@ -100,10 +85,13 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
this.backToPreviousScene();
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1));
|
||||
this.input.keyboard.on('keyup-LEFT', () => this.moveCursorHorizontally(-1));
|
||||
this.input.keyboard.on('keyup-DOWN', () => this.moveCursorVertically(1));
|
||||
this.input.keyboard.on('keyup-UP', () => this.moveCursorVertically(-1));
|
||||
// Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
|
||||
// because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
|
||||
// explain, the list of sprites managed by the update list become immense
|
||||
this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1);
|
||||
this.input.keyboard.on('keyup-LEFT', () => this.moveHorizontally = -1);
|
||||
this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1);
|
||||
this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1);
|
||||
|
||||
const customCursorPosition = localUserStore.getCustomCursorPosition();
|
||||
if (customCursorPosition) {
|
||||
|
@ -116,7 +104,15 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
this.onResize();
|
||||
}
|
||||
|
||||
private moveCursorHorizontally(index: number): void {
|
||||
public moveCursorHorizontally(index: number): void {
|
||||
this.moveHorizontally = index;
|
||||
}
|
||||
|
||||
public moveCursorVertically(index: number): void {
|
||||
this.moveVertically = index;
|
||||
}
|
||||
|
||||
private doMoveCursorHorizontally(index: number): void {
|
||||
this.selectedLayers[this.activeRow] += index;
|
||||
if (this.selectedLayers[this.activeRow] < 0) {
|
||||
this.selectedLayers[this.activeRow] = 0
|
||||
|
@ -128,27 +124,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
this.saveInLocalStorage();
|
||||
}
|
||||
|
||||
private moveCursorVertically(index:number): void {
|
||||
|
||||
if(index === -1 && this.activeRow === 5){
|
||||
const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
|
||||
button.innerHTML = `Next <img src="resources/objects/arrow_up.png"/>`;
|
||||
}
|
||||
|
||||
if(index === 1 && this.activeRow === 4){
|
||||
const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
|
||||
button.innerText = 'Finish';
|
||||
}
|
||||
|
||||
if(index === -1 && this.activeRow === 1){
|
||||
const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement;
|
||||
button.innerText = `Return`;
|
||||
}
|
||||
|
||||
if(index === 1 && this.activeRow === 0){
|
||||
const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement;
|
||||
button.innerHTML = `Back <img src="resources/objects/arrow_up.png"/>`;
|
||||
}
|
||||
private doMoveCursorVertically(index:number): void {
|
||||
|
||||
this.activeRow += index;
|
||||
if (this.activeRow < 0) {
|
||||
|
@ -197,20 +173,20 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
* @param selectedItem, The number of the item select (0 for black body...)
|
||||
*/
|
||||
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
|
||||
return new Container(this, x, y,this.getContainerChildren(layerNumber,selectedItem));
|
||||
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem));
|
||||
}
|
||||
|
||||
private getContainerChildren(layerNumber: number, selectedItem: number): Array<Sprite> {
|
||||
const children: Array<Sprite> = new Array<Sprite>();
|
||||
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
|
||||
const children: Array<string> = new Array<string>();
|
||||
for (let j = 0; j <= layerNumber; j++) {
|
||||
if (j === layerNumber) {
|
||||
children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name));
|
||||
children.push(this.layers[j][selectedItem].name);
|
||||
} else {
|
||||
const layer = this.selectedLayers[j];
|
||||
if (layer === undefined) {
|
||||
continue;
|
||||
}
|
||||
children.push(this.generateLayers(0, 0, this.layers[j][layer].name));
|
||||
children.push(this.layers[j][layer].name);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
|
@ -247,33 +223,47 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
* @return a new sprite
|
||||
*/
|
||||
private generateLayers(x: number, y: number, name: string): Sprite {
|
||||
return new Sprite(this, x, y, name);
|
||||
//return new Sprite(this, x, y, name);
|
||||
return this.add.sprite(0, 0, name);
|
||||
}
|
||||
|
||||
private updateSelectedLayer() {
|
||||
for(let i = 0; i < this.containersRow.length; i++){
|
||||
for(let j = 0; j < this.containersRow[i].length; j++){
|
||||
const children = this.getContainerChildren(i, j);
|
||||
this.containersRow[i][j].removeAll(true);
|
||||
this.containersRow[i][j].add(children);
|
||||
const children = this.getContainerChildren(i, j);
|
||||
this.containersRow[i][j].updateSprites(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
||||
if(this.lazyloadingAttempt){
|
||||
this.moveLayers();
|
||||
this.lazyloadingAttempt = false;
|
||||
}
|
||||
|
||||
if (this.moveHorizontally !== 0) {
|
||||
this.doMoveCursorHorizontally(this.moveHorizontally);
|
||||
this.moveHorizontally = 0;
|
||||
}
|
||||
if (this.moveVertically !== 0) {
|
||||
this.doMoveCursorVertically(this.moveVertically);
|
||||
this.moveVertically = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public onResize(): void {
|
||||
this.moveLayers();
|
||||
|
||||
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
|
||||
|
||||
this.centerXDomElement(this.customizeSceneElement, 150);
|
||||
}
|
||||
|
||||
private nextSceneToCamera(){
|
||||
public nextSceneToCamera(){
|
||||
const layers: string[] = [];
|
||||
let i = 0;
|
||||
for (const layerItem of this.selectedLayers) {
|
||||
|
@ -288,12 +278,16 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||
|
||||
gameManager.setCharacterLayers(layers);
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
this.scene.remove(SelectCharacterSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.events.removeListener('wake');
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
private backToPreviousScene(){
|
||||
public backToPreviousScene(){
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.scene.run(SelectCharacterSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import {TextField} from "../Components/TextField";
|
|||
import Image = Phaser.GameObjects.Image;
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {SoundMeter} from "../Components/SoundMeter";
|
||||
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
|
@ -11,304 +10,41 @@ import Zone = Phaser.GameObjects.Zone;
|
|||
import { MenuScene } from "../Menu/MenuScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import {
|
||||
audioConstraintStore,
|
||||
enableCameraSceneVisibilityStore,
|
||||
localStreamStore,
|
||||
mediaStreamConstraintsStore,
|
||||
videoConstraintStore
|
||||
} from "../../Stores/MediaStore";
|
||||
import type {Unsubscriber} from "svelte/store";
|
||||
|
||||
export const EnableCameraSceneName = "EnableCameraScene";
|
||||
enum LoginTextures {
|
||||
playButton = "play_button",
|
||||
icon = "icon",
|
||||
mainFont = "main_font",
|
||||
arrowRight = "arrow_right",
|
||||
arrowUp = "arrow_up"
|
||||
}
|
||||
|
||||
const enableCameraSceneKey = 'enableCameraScene';
|
||||
|
||||
export class EnableCameraScene extends ResizableScene {
|
||||
private textField!: TextField;
|
||||
private cameraNameField!: TextField;
|
||||
private arrowLeft!: Image;
|
||||
private arrowRight!: Image;
|
||||
private arrowDown!: Image;
|
||||
private arrowUp!: Image;
|
||||
private microphonesList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
|
||||
private camerasList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
|
||||
private cameraSelected: number = 0;
|
||||
private microphoneSelected: number = 0;
|
||||
private soundMeter: SoundMeter;
|
||||
private soundMeterSprite!: SoundMeterSprite;
|
||||
private microphoneNameField!: TextField;
|
||||
|
||||
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
|
||||
private mobileTapZone!: Zone;
|
||||
private localStreamStoreUnsubscriber!: Unsubscriber;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: EnableCameraSceneName
|
||||
});
|
||||
this.soundMeter = new SoundMeter();
|
||||
}
|
||||
|
||||
preload() {
|
||||
|
||||
this.load.html(enableCameraSceneKey, 'resources/html/EnableCameraScene.html');
|
||||
|
||||
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png");
|
||||
this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
this.enableCameraSceneElement = this.add.dom(-1000, 0).createFromCache(enableCameraSceneKey);
|
||||
this.centerXDomElement(this.enableCameraSceneElement, 300);
|
||||
|
||||
MenuScene.revealMenusAfterInit(this.enableCameraSceneElement, enableCameraSceneKey);
|
||||
|
||||
const continuingButton = this.enableCameraSceneElement.getChildByID('enableCameraSceneFormSubmit') as HTMLButtonElement;
|
||||
continuingButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.login();
|
||||
});
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
new PinchManager(this);
|
||||
}
|
||||
//this.scale.setZoom(ZOOM_LEVEL);
|
||||
//Phaser.Display.Align.In.BottomCenter(this.pressReturnField, zone);
|
||||
|
||||
/* FIX ME */
|
||||
this.textField = new TextField(this, this.scale.width / 2, 20, '');
|
||||
|
||||
// For mobile purposes - we need a big enough touchable area.
|
||||
this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50)
|
||||
.setInteractive().on("pointerdown", () => {
|
||||
this.login();
|
||||
});
|
||||
|
||||
this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, '');
|
||||
|
||||
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
|
||||
|
||||
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowRight.setVisible(false);
|
||||
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
|
||||
this.add.existing(this.arrowRight);
|
||||
|
||||
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowLeft.setVisible(false);
|
||||
this.arrowLeft.flipX = true;
|
||||
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
|
||||
this.add.existing(this.arrowLeft);
|
||||
|
||||
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowUp.setVisible(false);
|
||||
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
|
||||
this.add.existing(this.arrowUp);
|
||||
|
||||
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowDown.setVisible(false);
|
||||
this.arrowDown.flipY = true;
|
||||
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
||||
this.add.existing(this.arrowDown);
|
||||
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
this.login();
|
||||
});
|
||||
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
|
||||
|
||||
this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => {
|
||||
if (result.type === 'error') {
|
||||
// TODO: proper handling of the error
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
this.getDevices();
|
||||
if (result.stream !== null) {
|
||||
this.setupStream(result.stream);
|
||||
}
|
||||
});
|
||||
/*const mediaPromise = mediaManager.getCamera();
|
||||
mediaPromise.then(this.getDevices.bind(this));
|
||||
mediaPromise.then(this.setupStream.bind(this));*/
|
||||
|
||||
this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
|
||||
this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
|
||||
this.input.keyboard.on('keydown-DOWN', this.nextMic.bind(this));
|
||||
this.input.keyboard.on('keydown-UP', this.previousMic.bind(this));
|
||||
|
||||
this.soundMeterSprite = new SoundMeterSprite(this, 50, 50);
|
||||
this.soundMeterSprite.setVisible(false);
|
||||
this.add.existing(this.soundMeterSprite);
|
||||
|
||||
this.onResize();
|
||||
|
||||
enableCameraSceneVisibilityStore.showEnableCameraScene();
|
||||
}
|
||||
|
||||
private previousCam(): void {
|
||||
if (this.cameraSelected === 0 || this.camerasList.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.cameraSelected--;
|
||||
videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
|
||||
|
||||
//mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
|
||||
}
|
||||
|
||||
private nextCam(): void {
|
||||
if (this.cameraSelected === this.camerasList.length - 1 || this.camerasList.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.cameraSelected++;
|
||||
videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
|
||||
|
||||
// TODO: the change of camera should be OBSERVED (reactive)
|
||||
//mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
|
||||
}
|
||||
|
||||
private previousMic(): void {
|
||||
if (this.microphoneSelected === 0 || this.microphonesList.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.microphoneSelected--;
|
||||
audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
|
||||
//mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
|
||||
}
|
||||
|
||||
private nextMic(): void {
|
||||
if (this.microphoneSelected === this.microphonesList.length - 1 || this.microphonesList.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.microphoneSelected++;
|
||||
audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
|
||||
// TODO: the change of camera should be OBSERVED (reactive)
|
||||
//mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called each time a camera is changed
|
||||
*/
|
||||
private setupStream(stream: MediaStream): void {
|
||||
const img = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
|
||||
img.style.display = 'none';
|
||||
|
||||
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||
div.srcObject = stream;
|
||||
|
||||
this.soundMeter.connectToSource(stream, new window.AudioContext());
|
||||
this.soundMeterSprite.setVisible(true);
|
||||
|
||||
this.updateWebCamName();
|
||||
}
|
||||
|
||||
private updateWebCamName(): void {
|
||||
if (this.camerasList.length > 1) {
|
||||
let label = this.camerasList[this.cameraSelected].label;
|
||||
// remove text in parenthesis
|
||||
label = label.replace(/\([^()]*\)/g, '').trim();
|
||||
// remove accents
|
||||
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
this.cameraNameField.text = label;
|
||||
|
||||
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
|
||||
this.arrowLeft.setVisible(this.cameraSelected > 0);
|
||||
}
|
||||
if (this.microphonesList.length > 1) {
|
||||
let label = this.microphonesList[this.microphoneSelected].label;
|
||||
// remove text in parenthesis
|
||||
label = label.replace(/\([^()]*\)/g, '').trim();
|
||||
// remove accents
|
||||
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
|
||||
this.microphoneNameField.text = label;
|
||||
|
||||
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
|
||||
this.arrowUp.setVisible(this.microphoneSelected > 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
let div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||
let bounds = div.getBoundingClientRect();
|
||||
if (!div.srcObject) {
|
||||
div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
|
||||
bounds = div.getBoundingClientRect();
|
||||
}
|
||||
|
||||
this.textField.x = this.game.renderer.width / 2;
|
||||
this.mobileTapZone.x = this.game.renderer.width / 2;
|
||||
this.cameraNameField.x = this.game.renderer.width / 2;
|
||||
this.microphoneNameField.x = this.game.renderer.width / 2;
|
||||
|
||||
this.cameraNameField.y = bounds.top / this.scale.zoom - 8;
|
||||
|
||||
this.soundMeterSprite.x = this.game.renderer.width / 2 - this.soundMeterSprite.getWidth() / 2;
|
||||
this.soundMeterSprite.y = bounds.bottom / this.scale.zoom + 16;
|
||||
|
||||
this.microphoneNameField.y = this.soundMeterSprite.y + 22;
|
||||
|
||||
this.arrowRight.x = bounds.right / this.scale.zoom + 16;
|
||||
this.arrowRight.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
|
||||
|
||||
this.arrowLeft.x = bounds.left / this.scale.zoom - 16;
|
||||
this.arrowLeft.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
|
||||
|
||||
this.arrowDown.x = this.microphoneNameField.x + this.microphoneNameField.width / 2 + 16;
|
||||
this.arrowDown.y = this.microphoneNameField.y;
|
||||
|
||||
this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16;
|
||||
this.arrowUp.y = this.microphoneNameField.y;
|
||||
|
||||
const actionBtn = document.querySelector<HTMLDivElement>('#enableCameraScene .action');
|
||||
if (actionBtn !== null) {
|
||||
actionBtn.style.top = (this.scale.height - 65) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
|
||||
|
||||
this.centerXDomElement(this.enableCameraSceneElement, 300);
|
||||
}
|
||||
|
||||
private login(): void {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.soundMeter.stop();
|
||||
|
||||
public login(): void {
|
||||
enableCameraSceneVisibilityStore.hideEnableCameraScene();
|
||||
this.localStreamStoreUnsubscriber();
|
||||
//mediaManager.stopCamera();
|
||||
//mediaManager.stopMicrophone();
|
||||
|
||||
this.scene.sleep(EnableCameraSceneName);
|
||||
gameManager.goToStartingMap(this.scene);
|
||||
}
|
||||
|
||||
private async getDevices() {
|
||||
// TODO: switch this in a store.
|
||||
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
|
||||
this.microphonesList = [];
|
||||
this.camerasList = [];
|
||||
for (const mediaDeviceInfo of mediaDeviceInfos) {
|
||||
if (mediaDeviceInfo.kind === 'audioinput') {
|
||||
this.microphonesList.push(mediaDeviceInfo);
|
||||
} else if (mediaDeviceInfo.kind === 'videoinput') {
|
||||
this.camerasList.push(mediaDeviceInfo);
|
||||
}
|
||||
}
|
||||
this.updateWebCamName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import {gameManager} from "../Game/GameManager";
|
||||
import {SelectCharacterSceneName} from "./SelectCharacterScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import {MenuScene} from "../Menu/MenuScene";
|
||||
import { isUserNameValid } from "../../Connexion/LocalUser";
|
||||
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
|
||||
|
||||
export const LoginSceneName = "LoginScene";
|
||||
|
||||
const loginSceneKey = 'loginScene';
|
||||
|
||||
export class LoginScene extends ResizableScene {
|
||||
|
||||
private loginSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
private name: string = '';
|
||||
|
||||
constructor() {
|
||||
|
@ -22,65 +17,25 @@ export class LoginScene extends ResizableScene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(loginSceneKey, 'resources/html/loginScene.html');
|
||||
}
|
||||
|
||||
create() {
|
||||
this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey);
|
||||
this.centerXDomElement(this.loginSceneElement, 200);
|
||||
MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey);
|
||||
|
||||
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
|
||||
const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement;
|
||||
inputElement.value = localUserStore.getName() ?? '';
|
||||
inputElement.focus();
|
||||
inputElement.addEventListener('keypress', (event: KeyboardEvent) => {
|
||||
if(inputElement.value.length > 7){
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
pErrorElement.innerHTML = '';
|
||||
if(inputElement.value && !isUserNameValid(inputElement.value)){
|
||||
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
this.login(inputElement);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement;
|
||||
continuingButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.login(inputElement);
|
||||
});
|
||||
loginSceneVisibleStore.set(true);
|
||||
}
|
||||
|
||||
private login(inputElement: HTMLInputElement): void {
|
||||
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
|
||||
this.name = inputElement.value;
|
||||
if (this.name === '') {
|
||||
pErrorElement.innerHTML = 'The name is empty';
|
||||
return
|
||||
}
|
||||
if(!isUserNameValid(this.name)){
|
||||
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
|
||||
return
|
||||
}
|
||||
if (this.name === '') return
|
||||
gameManager.setPlayerName(this.name);
|
||||
public login(name: string): void {
|
||||
name = name.trim();
|
||||
gameManager.setPlayerName(name);
|
||||
|
||||
this.scene.stop(LoginSceneName)
|
||||
gameManager.tryResumingGame(this, SelectCharacterSceneName);
|
||||
this.scene.remove(LoginSceneName)
|
||||
this.scene.remove(LoginSceneName);
|
||||
loginSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
this.centerXDomElement(this.loginSceneElement, 200);
|
||||
public onResize(): void {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {Scene} from "phaser";
|
|||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
|
||||
export abstract class ResizableScene extends Scene {
|
||||
public abstract onResize(ev: UIEvent): void;
|
||||
public abstract onResize(): void;
|
||||
|
||||
/**
|
||||
* Centers the DOM element on the X axis.
|
||||
|
@ -17,7 +17,7 @@ export abstract class ResizableScene extends Scene {
|
|||
&& object.node
|
||||
&& object.node.getBoundingClientRect().width > 0
|
||||
? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom)
|
||||
: (300 / this.scale.zoom)
|
||||
: (defaultWidth / this.scale.zoom)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,49 +4,50 @@ export class SelectCharacterMobileScene extends SelectCharacterScene {
|
|||
|
||||
create(){
|
||||
super.create();
|
||||
this.onResize();
|
||||
this.selectedRectangle.destroy();
|
||||
}
|
||||
|
||||
protected defineSetupPlayer(numero: number){
|
||||
protected defineSetupPlayer(num: number){
|
||||
const deltaX = 30;
|
||||
const deltaY = 2;
|
||||
let [playerX, playerY] = this.getCharacterPosition();
|
||||
let playerVisible = true;
|
||||
let playerScale = 1.5;
|
||||
let playserOpactity = 1;
|
||||
let playerOpacity = 1;
|
||||
|
||||
if( this.currentSelectUser !== numero ){
|
||||
if( this.currentSelectUser !== num ){
|
||||
playerVisible = false;
|
||||
}
|
||||
if( numero === (this.currentSelectUser + 1) ){
|
||||
if( num === (this.currentSelectUser + 1) ){
|
||||
playerY -= deltaY;
|
||||
playerX += deltaX;
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( numero === (this.currentSelectUser + 2) ){
|
||||
if( num === (this.currentSelectUser + 2) ){
|
||||
playerY -= deltaY;
|
||||
playerX += (deltaX * 2);
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( numero === (this.currentSelectUser - 1) ){
|
||||
if( num === (this.currentSelectUser - 1) ){
|
||||
playerY -= deltaY;
|
||||
playerX -= deltaX;
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
if( numero === (this.currentSelectUser - 2) ){
|
||||
if( num === (this.currentSelectUser - 2) ){
|
||||
playerY -= deltaY;
|
||||
playerX -= (deltaX * 2);
|
||||
playerScale = 0.8;
|
||||
playserOpactity = 0.6;
|
||||
playerOpacity = 0.6;
|
||||
playerVisible = true;
|
||||
}
|
||||
return {playerX, playerY, playerScale, playserOpactity, playerVisible}
|
||||
return {playerX, playerY, playerScale, playerOpacity, playerVisible}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,13 +10,13 @@ import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
|||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import {MenuScene} from "../Menu/MenuScene";
|
||||
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import {isMobile} from "../../Enum/EnvironmentVariable";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
|
||||
const selectCharacterKey = 'selectCharacterScene';
|
||||
|
||||
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
protected readonly nbCharactersPerRow = 6;
|
||||
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
|
||||
|
@ -25,8 +25,11 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
|
||||
protected selectedRectangle!: Rectangle;
|
||||
|
||||
protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
protected currentSelectUser = 0;
|
||||
protected pointerClicked: boolean = false;
|
||||
protected pointerTimer: number = 0;
|
||||
|
||||
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -35,50 +38,41 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(selectCharacterKey, 'resources/html/selectCharacterScene.html');
|
||||
|
||||
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
})
|
||||
this.lazyloadingAttempt = true;
|
||||
});
|
||||
this.playerModels = loadAllDefaultModels(this.load);
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
this.selectCharacterSceneElement = this.add.dom(-1000, 0).createFromCache(selectCharacterKey);
|
||||
this.centerXDomElement(this.selectCharacterSceneElement, 150);
|
||||
MenuScene.revealMenusAfterInit(this.selectCharacterSceneElement, selectCharacterKey);
|
||||
|
||||
this.selectCharacterSceneElement.addListener('click');
|
||||
this.selectCharacterSceneElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') {
|
||||
this.moveToLeft();
|
||||
}else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') {
|
||||
this.moveToRight();
|
||||
}else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormSubmit') {
|
||||
this.nextSceneToCameraScene();
|
||||
}else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormCustomYourOwnSubmit') {
|
||||
this.nextSceneToCustomizeScene();
|
||||
}
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
this.events.addListener('wake', () => {
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
});
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
new PinchManager(this);
|
||||
}
|
||||
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||
|
||||
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
||||
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF);
|
||||
this.selectedRectangle.setDepth(2);
|
||||
|
||||
/*create user*/
|
||||
this.createCurrentPlayer();
|
||||
const playerNumber = localUserStore.getPlayerCharacterIndex();
|
||||
|
||||
this.input.keyboard.on('keyup-ENTER', () => {
|
||||
return this.nextSceneToCameraScene();
|
||||
|
@ -98,7 +92,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
});
|
||||
}
|
||||
|
||||
protected nextSceneToCameraScene(): void {
|
||||
public nextSceneToCameraScene(): void {
|
||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
||||
return;
|
||||
}
|
||||
|
@ -106,23 +100,33 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
return;
|
||||
}
|
||||
this.scene.stop(SelectCharacterSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
this.scene.remove(SelectCharacterSceneName);
|
||||
this.players = [];
|
||||
selectCharacterSceneVisibleStore.set(false);
|
||||
this.events.removeListener('wake');
|
||||
}
|
||||
|
||||
protected nextSceneToCustomizeScene(): void {
|
||||
public nextSceneToCustomizeScene(): void {
|
||||
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
|
||||
return;
|
||||
}
|
||||
this.scene.sleep(SelectCharacterSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.scene.run(CustomizeSceneName);
|
||||
selectCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
createCurrentPlayer(): void {
|
||||
for (let i = 0; i <this.playerModels.length; i++) {
|
||||
const playerResource = this.playerModels[i];
|
||||
|
||||
//check already exist texture
|
||||
if(this.players.find((c) => c.texture.key === playerResource.name)){
|
||||
continue;
|
||||
}
|
||||
|
||||
const [middleX, middleY] = this.getCharacterPosition();
|
||||
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
|
||||
this.setUpPlayer(player, i);
|
||||
|
@ -133,15 +137,22 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
repeat: -1
|
||||
});
|
||||
player.setInteractive().on("pointerdown", () => {
|
||||
if(this.currentSelectUser === i){
|
||||
if (this.pointerClicked) {
|
||||
return;
|
||||
}
|
||||
if (this.currentSelectUser === i) {
|
||||
return;
|
||||
}
|
||||
//To not trigger two time the pointerdown events :
|
||||
// We set a boolean to true so that pointerdown events does nothing when the boolean is true
|
||||
// We set a timer that we decrease in update function to not trigger the pointerdown events twice
|
||||
this.pointerClicked = true;
|
||||
this.pointerTimer = 250;
|
||||
this.currentSelectUser = i;
|
||||
this.moveUser();
|
||||
});
|
||||
this.players.push(player);
|
||||
}
|
||||
|
||||
this.selectedPlayer = this.players[this.currentSelectUser];
|
||||
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
|
||||
}
|
||||
|
@ -154,7 +165,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
this.updateSelectedPlayer();
|
||||
}
|
||||
|
||||
protected moveToLeft(){
|
||||
public moveToLeft(){
|
||||
if(this.currentSelectUser === 0){
|
||||
return;
|
||||
}
|
||||
|
@ -162,7 +173,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
this.moveUser();
|
||||
}
|
||||
|
||||
protected moveToRight(){
|
||||
public moveToRight(){
|
||||
if(this.currentSelectUser === (this.players.length - 1)){
|
||||
return;
|
||||
}
|
||||
|
@ -186,35 +197,35 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
this.moveUser();
|
||||
}
|
||||
|
||||
protected defineSetupPlayer(numero: number){
|
||||
protected defineSetupPlayer(num: number){
|
||||
const deltaX = 32;
|
||||
const deltaY = 32;
|
||||
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
|
||||
|
||||
playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (numero % this.nbCharactersPerRow)) ); // calcul position on line users
|
||||
playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(numero / this.nbCharactersPerRow) )) ); // calcul position on column users
|
||||
playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (num % this.nbCharactersPerRow)) ); // calcul position on line users
|
||||
playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(num / this.nbCharactersPerRow) )) ); // calcul position on column users
|
||||
|
||||
const playerVisible = true;
|
||||
const playerScale = 1;
|
||||
const playserOpactity = 1;
|
||||
const playerOpacity = 1;
|
||||
|
||||
// if selected
|
||||
if( numero === this.currentSelectUser ){
|
||||
if( num === this.currentSelectUser ){
|
||||
this.selectedRectangle.setX(playerX);
|
||||
this.selectedRectangle.setY(playerY);
|
||||
}
|
||||
|
||||
return {playerX, playerY, playerScale, playserOpactity, playerVisible}
|
||||
return {playerX, playerY, playerScale, playerOpacity, playerVisible}
|
||||
}
|
||||
|
||||
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, numero: number){
|
||||
protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number){
|
||||
|
||||
const {playerX, playerY, playerScale, playserOpactity, playerVisible} = this.defineSetupPlayer(numero);
|
||||
const {playerX, playerY, playerScale, playerOpacity, playerVisible} = this.defineSetupPlayer(num);
|
||||
player.setBounce(0.2);
|
||||
player.setCollideWorldBounds(true);
|
||||
player.setCollideWorldBounds(false);
|
||||
player.setVisible( playerVisible );
|
||||
player.setScale(playerScale, playerScale);
|
||||
player.setAlpha(playserOpactity);
|
||||
player.setAlpha(playerOpacity);
|
||||
player.setX(playerX);
|
||||
player.setY(playerY);
|
||||
}
|
||||
|
@ -238,12 +249,23 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
// pointerTimer is set to 250 when pointerdown events is trigger
|
||||
// After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
|
||||
this.pointerTimer -= delta;
|
||||
if (this.pointerTimer <= 0) {
|
||||
this.pointerClicked = false;
|
||||
}
|
||||
|
||||
if(this.lazyloadingAttempt){
|
||||
//re-render players list
|
||||
this.createCurrentPlayer();
|
||||
this.moveUser();
|
||||
this.lazyloadingAttempt = false;
|
||||
}
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
public onResize(): void {
|
||||
//move position of user
|
||||
this.moveUser();
|
||||
|
||||
this.centerXDomElement(this.selectCharacterSceneElement, 150);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,21 @@ import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingM
|
|||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import { MenuScene } from "../Menu/MenuScene";
|
||||
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import {isMobile} from "../../Enum/EnvironmentVariable";
|
||||
|
||||
export const SelectCompanionSceneName = "SelectCompanionScene";
|
||||
|
||||
const selectCompanionSceneKey = 'selectCompanionScene';
|
||||
|
||||
export class SelectCompanionScene extends ResizableScene {
|
||||
private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
|
||||
private companions: Array<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
private companionModels: Array<CompanionResourceDescriptionInterface> = [];
|
||||
private saveZoom: number = 0;
|
||||
|
||||
private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement;
|
||||
private currentCompanion = 0;
|
||||
private pointerClicked: boolean = false;
|
||||
private pointerTimer: number = 0;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -30,8 +33,6 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(selectCompanionSceneKey, 'resources/html/SelectCompanionScene.html');
|
||||
|
||||
getAllCompanionResources(this.load).forEach(model => {
|
||||
this.companionModels.push(model);
|
||||
});
|
||||
|
@ -42,30 +43,17 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
|
||||
create() {
|
||||
|
||||
this.selectCompanionSceneElement = this.add.dom(-1000, 0).createFromCache(selectCompanionSceneKey);
|
||||
this.centerXDomElement(this.selectCompanionSceneElement, 150);
|
||||
MenuScene.revealMenusAfterInit(this.selectCompanionSceneElement, selectCompanionSceneKey);
|
||||
selectCompanionSceneVisibleStore.set(true);
|
||||
|
||||
this.selectCompanionSceneElement.addListener('click');
|
||||
this.selectCompanionSceneElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') {
|
||||
this.moveToLeft();
|
||||
}else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') {
|
||||
this.moveToRight();
|
||||
}else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormSubmit') {
|
||||
this.nextScene();
|
||||
}else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormBack') {
|
||||
this._nextScene();
|
||||
}
|
||||
});
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
new PinchManager(this);
|
||||
}
|
||||
|
||||
// input events
|
||||
this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this));
|
||||
this.input.keyboard.on('keyup-ENTER', this.selectCompanion.bind(this));
|
||||
|
||||
this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this));
|
||||
this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this));
|
||||
|
@ -86,21 +74,28 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
||||
// pointerTimer is set to 250 when pointerdown events is trigger
|
||||
// After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
|
||||
this.pointerTimer -= delta;
|
||||
if (this.pointerTimer <= 0) {
|
||||
this.pointerClicked = false;
|
||||
}
|
||||
}
|
||||
|
||||
private nextScene(): void {
|
||||
public selectCompanion(): void {
|
||||
localUserStore.setCompanion(this.companionModels[this.currentCompanion].name);
|
||||
gameManager.setCompanion(this.companionModels[this.currentCompanion].name);
|
||||
|
||||
this._nextScene();
|
||||
this.closeScene();
|
||||
}
|
||||
|
||||
private _nextScene(){
|
||||
public closeScene(){
|
||||
// next scene
|
||||
this.scene.stop(SelectCompanionSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
gameManager.tryResumingGame(this, EnableCameraSceneName);
|
||||
this.scene.remove(SelectCompanionSceneName);
|
||||
selectCompanionSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
private createCurrentCompanion(): void {
|
||||
|
@ -117,6 +112,14 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
});
|
||||
|
||||
companion.setInteractive().on("pointerdown", () => {
|
||||
if (this.pointerClicked) {
|
||||
return;
|
||||
}
|
||||
//To not trigger two time the pointerdown events :
|
||||
// We set a boolean to true so that pointerdown events does nothing when the boolean is true
|
||||
// We set a timer that we decrease in update function to not trigger the pointerdown events twice
|
||||
this.pointerClicked = true;
|
||||
this.pointerTimer = 250;
|
||||
this.currentCompanion = i;
|
||||
this.moveCompanion();
|
||||
});
|
||||
|
@ -126,10 +129,8 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
this.selectedCompanion = this.companions[this.currentCompanion];
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
public onResize(): void {
|
||||
this.moveCompanion();
|
||||
|
||||
this.centerXDomElement(this.selectCompanionSceneElement, 150);
|
||||
}
|
||||
|
||||
private updateSelectedCompanion(): void {
|
||||
|
@ -147,15 +148,7 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
this.updateSelectedCompanion();
|
||||
}
|
||||
|
||||
private moveToLeft(){
|
||||
if(this.currentCompanion === 0){
|
||||
return;
|
||||
}
|
||||
this.currentCompanion -= 1;
|
||||
this.moveCompanion();
|
||||
}
|
||||
|
||||
private moveToRight(){
|
||||
public moveToRight(){
|
||||
if(this.currentCompanion === (this.companions.length - 1)){
|
||||
return;
|
||||
}
|
||||
|
@ -163,38 +156,46 @@ export class SelectCompanionScene extends ResizableScene {
|
|||
this.moveCompanion();
|
||||
}
|
||||
|
||||
private defineSetupCompanion(numero: number){
|
||||
public moveToLeft(){
|
||||
if(this.currentCompanion === 0){
|
||||
return;
|
||||
}
|
||||
this.currentCompanion -= 1;
|
||||
this.moveCompanion();
|
||||
}
|
||||
|
||||
private defineSetupCompanion(num: number){
|
||||
const deltaX = 30;
|
||||
const deltaY = 2;
|
||||
let [companionX, companionY] = this.getCompanionPosition();
|
||||
let companionVisible = true;
|
||||
let companionScale = 1.5;
|
||||
let companionOpactity = 1;
|
||||
if( this.currentCompanion !== numero ){
|
||||
if( this.currentCompanion !== num ){
|
||||
companionVisible = false;
|
||||
}
|
||||
if( numero === (this.currentCompanion + 1) ){
|
||||
if( num === (this.currentCompanion + 1) ){
|
||||
companionY -= deltaY;
|
||||
companionX += deltaX;
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
if( numero === (this.currentCompanion + 2) ){
|
||||
if( num === (this.currentCompanion + 2) ){
|
||||
companionY -= deltaY;
|
||||
companionX += (deltaX * 2);
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
if( numero === (this.currentCompanion - 1) ){
|
||||
if( num === (this.currentCompanion - 1) ){
|
||||
companionY -= deltaY;
|
||||
companionX -= deltaX;
|
||||
companionScale = 0.8;
|
||||
companionOpactity = 0.6;
|
||||
companionVisible = true;
|
||||
}
|
||||
if( numero === (this.currentCompanion - 2) ){
|
||||
if( num === (this.currentCompanion - 2) ){
|
||||
companionY -= deltaY;
|
||||
companionX -= (deltaX * 2);
|
||||
companionScale = 0.8;
|
||||
|
|
|
@ -170,7 +170,7 @@ export interface ITiledTileSet {
|
|||
tilewidth: number;
|
||||
transparentcolor: string;
|
||||
terrains: ITiledMapTerrain[];
|
||||
tiles: Array<ITile>;
|
||||
tiles?: Array<ITile>;
|
||||
|
||||
/**
|
||||
* Refers to external tileset file (should be JSON)
|
||||
|
@ -181,6 +181,8 @@ export interface ITiledTileSet {
|
|||
export interface ITile {
|
||||
id: number,
|
||||
type?: string
|
||||
|
||||
properties?: Array<ITiledMapLayerProperty>
|
||||
}
|
||||
|
||||
export interface ITiledMapTerrain {
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {DirtyScene} from "../Game/DirtyScene";
|
||||
import {get} from "svelte/store";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
|
||||
|
||||
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
||||
const helpCameraSettings = 'helpCameraSettings';
|
||||
/**
|
||||
* The scene that show how to permit Camera and Microphone access if there are not already allowed
|
||||
*/
|
||||
export class HelpCameraSettingsScene extends DirtyScene {
|
||||
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
|
||||
private helpCameraSettingsOpened: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super({key: HelpCameraSettingsSceneName});
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
|
||||
}
|
||||
|
||||
create(){
|
||||
this.createHelpCameraSettings();
|
||||
}
|
||||
|
||||
private createHelpCameraSettings() : void {
|
||||
const middleX = this.getMiddleX();
|
||||
this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
|
||||
this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
|
||||
this.helpCameraSettingsElement.addListener('click');
|
||||
this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
|
||||
if((event?.target as HTMLInputElement).id === 'mailto') {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
|
||||
window.location.reload();
|
||||
}else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
|
||||
this.closeHelpCameraSettingsOpened();
|
||||
}
|
||||
});
|
||||
|
||||
if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
|
||||
this.openHelpCameraSettingsOpened();
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
}
|
||||
|
||||
mediaManager.setHelpCameraSettingsCallBack(() => {
|
||||
this.openHelpCameraSettingsOpened();
|
||||
});
|
||||
}
|
||||
|
||||
private openHelpCameraSettingsOpened(): void{
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.helpCameraSettingsOpened = true;
|
||||
try{
|
||||
if(window.navigator.userAgent.includes('Firefox')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
|
||||
}else if(window.navigator.userAgent.includes('Chrome')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
|
||||
}
|
||||
}catch(err) {
|
||||
console.error('openHelpCameraSettingsOpened => getElementByIdOrFail => error', err);
|
||||
}
|
||||
const middleY = this.getMiddleY();
|
||||
const middleX = this.getMiddleX();
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: middleY,
|
||||
x: middleX,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private closeHelpCameraSettingsOpened(): void{
|
||||
const middleX = this.getMiddleX();
|
||||
/*const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
|
||||
helpCameraSettingsInfo.innerText = '';
|
||||
helpCameraSettingsInfo.style.display = 'none';*/
|
||||
this.helpCameraSettingsOpened = false;
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: -1000,
|
||||
x: middleX,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
|
||||
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
||||
setTimeout(() => {
|
||||
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
|
||||
}, 250);
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
super.onResize(ev);
|
||||
if (this.helpCameraSettingsOpened) {
|
||||
const middleX = this.getMiddleX();
|
||||
const middleY = this.getMiddleY();
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
x: middleX,
|
||||
y: middleY,
|
||||
duration: 1000,
|
||||
ease: 'Power3'
|
||||
});
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private getMiddleX() : number{
|
||||
return (this.scale.width / 2) -
|
||||
(
|
||||
this.helpCameraSettingsElement
|
||||
&& this.helpCameraSettingsElement.node
|
||||
&& this.helpCameraSettingsElement.node.getBoundingClientRect().width > 0
|
||||
? (this.helpCameraSettingsElement.node.getBoundingClientRect().width / (2 * this.scale.zoom))
|
||||
: (400 / 2)
|
||||
);
|
||||
}
|
||||
|
||||
private getMiddleY() : number{
|
||||
const middleY = ((this.scale.height) - (
|
||||
(this.helpCameraSettingsElement
|
||||
&& this.helpCameraSettingsElement.node
|
||||
&& this.helpCameraSettingsElement.node.getBoundingClientRect().height > 0
|
||||
? this.helpCameraSettingsElement.node.getBoundingClientRect().height : 400 /*FIXME to use a const will be injected in HTMLElement*/)/this.scale.zoom)) / 2;
|
||||
return (middleY > 0 ? middleY : 0);
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return this.dirty;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,10 @@ import { HtmlUtils } from '../../WebRtc/HtmlUtils';
|
|||
import { iframeListener } from '../../Api/IframeListener';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||
import {registerMenuCommandStream} from "../../Api/Events/ui/MenuItemRegisterEvent";
|
||||
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||
import {consoleGlobalMessageManagerVisibleStore} from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import {get} from "svelte/store";
|
||||
|
||||
export const MenuSceneName = 'MenuScene';
|
||||
const gameMenuKey = 'gameMenu';
|
||||
|
@ -42,18 +46,39 @@ export class MenuScene extends Phaser.Scene {
|
|||
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
||||
private subscriptions = new Subscription()
|
||||
constructor() {
|
||||
super({key: MenuSceneName});
|
||||
super({ key: MenuSceneName });
|
||||
|
||||
this.gameQualityValue = localUserStore.getGameQualityValue();
|
||||
this.videoQualityValue = localUserStore.getVideoQualityValue();
|
||||
|
||||
this.subscriptions.add(iframeListener.registerMenuCommandStream.subscribe(menuCommand => {
|
||||
this.subscriptions.add(registerMenuCommandStream.subscribe(menuCommand => {
|
||||
this.addMenuOption(menuCommand);
|
||||
}))
|
||||
|
||||
this.subscriptions.add(iframeListener.unregisterMenuCommandStream.subscribe(menuCommand => {
|
||||
this.destroyMenu(menuCommand);
|
||||
}))
|
||||
}
|
||||
|
||||
preload () {
|
||||
reset() {
|
||||
const addedMenuItems = [...this.menuElement.node.querySelectorAll(".fromApi")];
|
||||
for (let index = addedMenuItems.length - 1; index >= 0; index--) {
|
||||
addedMenuItems[index].remove()
|
||||
}
|
||||
}
|
||||
|
||||
public addMenuOption(menuText: string) {
|
||||
const wrappingSection = document.createElement("section")
|
||||
const escapedHtml = HtmlUtils.escapeHtml(menuText);
|
||||
wrappingSection.innerHTML = `<button class="fromApi" id="${escapedHtml}">${escapedHtml}</button>`
|
||||
const menuItemContainer = this.menuElement.node.querySelector("#gameMenu main");
|
||||
if (menuItemContainer) {
|
||||
menuItemContainer.querySelector(`#${escapedHtml}.fromApi`)?.remove()
|
||||
menuItemContainer.insertBefore(wrappingSection, menuItemContainer.querySelector("#socialLinks"))
|
||||
}
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(gameMenuKey, 'resources/html/gameMenu.html');
|
||||
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
|
||||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||
|
@ -62,13 +87,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.load.html(warningContainerKey, warningContainerHtml);
|
||||
}
|
||||
|
||||
reset() {
|
||||
const addedMenuItems=[...this.menuElement.node.querySelectorAll(".fromApi")];
|
||||
for(let index=addedMenuItems.length-1;index>=0;index--){
|
||||
addedMenuItems[index].remove()
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
menuIconVisible.set(true);
|
||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
||||
|
@ -83,11 +101,11 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
|
||||
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
|
||||
this.gameShareElement.addListener('click');
|
||||
this.gameShareElement.on('click', (event:MouseEvent) => {
|
||||
this.gameShareElement.on('click', (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'gameShareFormSubmit') {
|
||||
if ((event?.target as HTMLInputElement).id === 'gameShareFormSubmit') {
|
||||
this.copyLink();
|
||||
}else if((event?.target as HTMLInputElement).id === 'gameShareFormCancel') {
|
||||
} else if ((event?.target as HTMLInputElement).id === 'gameShareFormCancel') {
|
||||
this.closeGameShare();
|
||||
}
|
||||
});
|
||||
|
@ -143,8 +161,8 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
//TODO bind with future metadata of card
|
||||
//if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){
|
||||
const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
//}
|
||||
this.tweens.add({
|
||||
targets: this.menuElement,
|
||||
|
@ -174,7 +192,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.sideMenuOpened = false;
|
||||
this.closeAll();
|
||||
this.menuButton.getChildByID('openMenuButton').innerHTML = `<img src="/static/images/menu.svg">`;
|
||||
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.disabledMessageConsole();
|
||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||
this.tweens.add({
|
||||
targets: this.menuElement,
|
||||
x: closedSideMenuX,
|
||||
|
@ -194,28 +212,28 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.settingsMenuOpened = true;
|
||||
|
||||
const gameQualitySelect = this.gameQualityMenuElement.getChildByID('select-game-quality') as HTMLInputElement;
|
||||
gameQualitySelect.value = ''+this.gameQualityValue;
|
||||
gameQualitySelect.value = '' + this.gameQualityValue;
|
||||
const videoQualitySelect = this.gameQualityMenuElement.getChildByID('select-video-quality') as HTMLInputElement;
|
||||
videoQualitySelect.value = ''+this.videoQualityValue;
|
||||
videoQualitySelect.value = '' + this.videoQualityValue;
|
||||
|
||||
this.gameQualityMenuElement.addListener('click');
|
||||
this.gameQualityMenuElement.on('click', (event:MouseEvent) => {
|
||||
this.gameQualityMenuElement.on('click', (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if ((event?.target as HTMLInputElement).id === 'gameQualityFormSubmit') {
|
||||
const gameQualitySelect = this.gameQualityMenuElement.getChildByID('select-game-quality') as HTMLInputElement;
|
||||
const videoQualitySelect = this.gameQualityMenuElement.getChildByID('select-video-quality') as HTMLInputElement;
|
||||
this.saveSetting(parseInt(gameQualitySelect.value), parseInt(videoQualitySelect.value));
|
||||
} else if((event?.target as HTMLInputElement).id === 'gameQualityFormCancel') {
|
||||
} else if ((event?.target as HTMLInputElement).id === 'gameQualityFormCancel') {
|
||||
this.closeGameQualityMenu();
|
||||
}
|
||||
});
|
||||
|
||||
let middleY = this.scale.height / 2 - 392/2;
|
||||
if(middleY < 0){
|
||||
let middleY = this.scale.height / 2 - 392 / 2;
|
||||
if (middleY < 0) {
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = this.scale.width / 2 - 457/2;
|
||||
if(middleX < 0){
|
||||
let middleX = this.scale.width / 2 - 457 / 2;
|
||||
if (middleX < 0) {
|
||||
middleX = 0;
|
||||
}
|
||||
this.tweens.add({
|
||||
|
@ -241,7 +259,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
|
||||
private openGameShare(): void{
|
||||
private openGameShare(): void {
|
||||
if (this.gameShareOpened) {
|
||||
this.closeGameShare();
|
||||
return;
|
||||
|
@ -255,11 +273,11 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.gameShareOpened = true;
|
||||
|
||||
let middleY = this.scale.height / 2 - 85;
|
||||
if(middleY < 0){
|
||||
if (middleY < 0) {
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = this.scale.width / 2 - 200;
|
||||
if(middleX < 0){
|
||||
if (middleX < 0) {
|
||||
middleX = 0;
|
||||
}
|
||||
this.tweens.add({
|
||||
|
@ -271,7 +289,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
private closeGameShare(): void{
|
||||
private closeGameShare(): void {
|
||||
const gameShareInfo = this.gameShareElement.getChildByID('gameShareInfo') as HTMLParagraphElement;
|
||||
gameShareInfo.innerText = '';
|
||||
gameShareInfo.style.display = 'none';
|
||||
|
@ -284,17 +302,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
});
|
||||
}
|
||||
|
||||
public addMenuOption(menuText: string) {
|
||||
const wrappingSection = document.createElement("section")
|
||||
const excapedHtml = HtmlUtils.escapeHtml(menuText);
|
||||
wrappingSection.innerHTML = `<button class="fromApi" id="${excapedHtml}">${excapedHtml}</button>`
|
||||
const menuItemContainer = this.menuElement.node.querySelector("#gameMenu main");
|
||||
if (menuItemContainer) {
|
||||
menuItemContainer.querySelector(`#${excapedHtml}.fromApi`)?.remove()
|
||||
menuItemContainer.insertBefore(wrappingSection, menuItemContainer.querySelector("#socialLinks"))
|
||||
}
|
||||
}
|
||||
|
||||
private onMenuClick(event: MouseEvent) {
|
||||
const htmlMenuItem = (event?.target as HTMLInputElement);
|
||||
if (htmlMenuItem.classList.contains('not-button')) {
|
||||
|
@ -303,11 +310,11 @@ export class MenuScene extends Phaser.Scene {
|
|||
event.preventDefault();
|
||||
|
||||
if (htmlMenuItem.classList.contains("fromApi")) {
|
||||
iframeListener.sendMenuClickedEvent(htmlMenuItem.id)
|
||||
sendMenuClickedEvent(htmlMenuItem.id)
|
||||
return
|
||||
}
|
||||
|
||||
switch (htmlMenuItem.id) {
|
||||
switch ((event?.target as HTMLInputElement).id) {
|
||||
case 'changeNameButton':
|
||||
this.closeSideMenu();
|
||||
gameManager.leaveGame(this, LoginSceneName, new LoginScene());
|
||||
|
@ -336,7 +343,11 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.toggleFullscreen();
|
||||
break;
|
||||
case 'adminConsoleButton':
|
||||
gameManager.getCurrentGameScene(this).ConsoleGlobalMessageManager.activeMessageConsole();
|
||||
if (get(consoleGlobalMessageManagerVisibleStore)) {
|
||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||
} else {
|
||||
consoleGlobalMessageManagerVisibleStore.set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +359,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
gameShareInfo.style.display = 'block';
|
||||
}
|
||||
|
||||
private saveSetting(valueGame: number, valueVideo: number){
|
||||
private saveSetting(valueGame: number, valueVideo: number) {
|
||||
if (valueGame !== this.gameQualityValue) {
|
||||
this.gameQualityValue = valueGame;
|
||||
localUserStore.setGameQualityValue(valueGame);
|
||||
|
@ -369,7 +380,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
window.open(sparkHost, '_blank');
|
||||
}
|
||||
|
||||
private closeAll(){
|
||||
private closeAll() {
|
||||
this.closeGameQualityMenu();
|
||||
this.closeGameShare();
|
||||
this.gameReportElement.close();
|
||||
|
@ -386,6 +397,10 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
}
|
||||
|
||||
public destroyMenu(menu: string) {
|
||||
this.menuElement.getChildByID(menu).remove();
|
||||
}
|
||||
|
||||
public isDirty(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ export class Player extends Character {
|
|||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||
|
||||
//the current player model should be push away by other players to prevent conflict
|
||||
this.getBody().setImmovable(false);
|
||||
|
@ -102,10 +102,6 @@ export class Player extends Character {
|
|||
}
|
||||
}
|
||||
|
||||
isClickable(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
openEmoteMenu(emotes:RadialMenuItem[]): void {
|
||||
this.cancelPreviousEmote();
|
||||
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)
|
||||
|
|
|
@ -2,6 +2,8 @@ import {HdpiManager} from "./HdpiManager";
|
|||
import ScaleManager = Phaser.Scale.ScaleManager;
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import type {Game} from "../Game/Game";
|
||||
import {ResizableScene} from "../Login/ResizableScene";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
|
||||
|
||||
class WaScaleManager {
|
||||
|
@ -9,6 +11,7 @@ class WaScaleManager {
|
|||
private scaleManager!: ScaleManager;
|
||||
private game!: Game;
|
||||
private actualZoom: number = 1;
|
||||
private _saveZoom: number = 1;
|
||||
|
||||
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
||||
|
@ -30,7 +33,7 @@ class WaScaleManager {
|
|||
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
|
||||
|
||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio)
|
||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||
|
||||
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
|
||||
|
@ -38,6 +41,19 @@ class WaScaleManager {
|
|||
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
|
||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
|
||||
|
||||
// Resize the game element at the same size at the canvas
|
||||
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game').style;
|
||||
gameStyle.width = style.width;
|
||||
gameStyle.height = style.height;
|
||||
|
||||
// Note: onResize will be called twice (once here and once in Game.ts), but we have no better way.
|
||||
for (const scene of this.game.scene.getScenes(true)) {
|
||||
if (scene instanceof ResizableScene) {
|
||||
// We are delaying the call to the "render" event because otherwise, the "camera" coordinates are not correctly updated.
|
||||
scene.events.once(Phaser.Scenes.Events.RENDER, () => scene.onResize());
|
||||
}
|
||||
}
|
||||
|
||||
this.game.markDirty();
|
||||
}
|
||||
|
||||
|
@ -50,6 +66,15 @@ class WaScaleManager {
|
|||
this.applyNewSize();
|
||||
}
|
||||
|
||||
public saveZoom(): void {
|
||||
this._saveZoom = this.hdpiManager.zoomModifier;
|
||||
}
|
||||
|
||||
public restoreZoom(): void{
|
||||
this.hdpiManager.zoomModifier = this._saveZoom;
|
||||
this.applyNewSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to scale back the ui components to counter-act the zoom.
|
||||
*/
|
||||
|
|