Skip to content

Commit

Permalink
Merge pull request #4 from numengames/feat/new-event-system
Browse files Browse the repository at this point in the history
feat: Implement a new system based on behavior & events instead of on…
  • Loading branch information
DevStarlight authored Oct 3, 2024
2 parents d8ca29a + fc7cd26 commit 086e0f4
Show file tree
Hide file tree
Showing 44 changed files with 2,780 additions and 2,323 deletions.
1,702 changes: 1,662 additions & 40 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
},
"homepage": "https://github.com/numengames/numinia-oncyber#readme",
"devDependencies": {
"@dimforge/rapier3d": "^0.12.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/animejs": "^3.1.12",
"@types/jest": "^29.5.13",
"@types/react": "^18.3.0",
"@types/three": "^0.163.0",
Expand All @@ -39,17 +39,23 @@
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.8.3",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.37.1",
"fetch-mock": "^11.1.4",
"gsap": "^3.12.5",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"pre-commit": "^1.2.2",
"prettier": "^3.2.5",
"react": "^18.2.0",
"react-dom": "^18.3.1",
"three": "^0.163.0",
"ts-jest": "^29.2.5",
"tslib": "^2.6.2"
},
"dependencies": {
"@dimforge/rapier3d": "^0.12.0",
"animejs": "^3.2.2",
"gsap": "^3.12.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"three": "^0.163.0"
}
}
}
64 changes: 64 additions & 0 deletions src/behaviors/emitters/BaseEmitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Folder, Param, $Param, ScriptBehavior } from '@oo/scripting';

import InteractionDirector from '../../common/interactions/InteractionDirector';

interface BaseEmitterParams {
triggerKey?: string;
interactionMode: string;
triggerDistance?: number;
yInteractionAdjustment?: number;
}

/**
* Main class to handle the action in the script.
*/
export default class BaseEmitter extends ScriptBehavior {
static config = {
title: 'Emitter',
tip: 'Use this to launch an event after interacting with the 3D Object',
};

@Param({ name: 'Signal sender' })
private enterSignal = $Param.Signal();

@Param({ type: 'boolean', defaultValue: false, name: 'Can emit signals multiple times?' })
private doesEmitMultipleTimes = false;

@Folder('Interaction Mode')
@Param({
type: 'select',
defaultValue: 'Auto',
name: 'Interaction Mode',
options: ['Auto', 'Key'],
})
private interactionMode = 'Auto';
@Param({
type: 'string',
name: 'Trigger key',
visible: (params: BaseEmitterParams) => params.interactionMode === 'Key',
})
private triggerKey = 'E';
@Param({
step: 0.1,
type: 'number',
name: 'Key dialog adjustment',
visible: (params: BaseEmitterParams) => params.interactionMode === 'Key',
})
private yInteractionAdjustment = 0;
@Param({
min: 0.1,
step: 0.1,
type: 'number',
defaultValue: 2,
name: 'Trigger distance',
visible: (params: BaseEmitterParams) => params.interactionMode === 'Key',
})
private triggerDistance = 2;

/**
* Called when the script is ready.
*/
onReady = async () => {
await InteractionDirector.handle(this, () => this.enterSignal.emit());
};
}
86 changes: 86 additions & 0 deletions src/behaviors/emitters/insert-password/InsertPasswordEmitter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';
import { Folder, Param, $Param, ScriptBehavior, UI } from '@oo/scripting';

import InsertPasswordPrompt from './InsertPasswordPrompt.tsx';
import InteractionDirector from '../../../common/interactions/InteractionDirector';

interface InsertPasswordEmitterParams {
triggerKey?: string;
interactionMode: string;
triggerDistance?: number;
yInteractionAdjustment?: number;
}

/**
* Main class to handle the emitter in the script.
*/
export default class InsertPasswordEmitter extends ScriptBehavior {
static config = {
title: 'Emitter - Insert Password',
tip: 'Use this to launch an event after solving the password',
};

private renderer = UI.createRenderer();

@Param({ name: 'Signal sender' })
private enterSignal = $Param.Signal();

@Param({
type: 'string',
name: 'Master password'
})
private masterPassword = '';

@Param({ type: 'boolean', defaultValue: false, name: 'Can emit signals multiple times?' })
private doesEmitMultipleTimes = false;

@Folder('Interaction Mode')
@Param({
type: 'select',
defaultValue: 'Auto',
name: 'Interaction Mode',
options: ['Auto', 'Key'],
})
private interactionMode = 'Auto';
@Param({
type: 'string',
name: 'Trigger key',
visible: (params: InsertPasswordEmitterParams) => params.interactionMode === 'Key',
})

private triggerKey = 'E';
@Param({
step: 0.1,
type: 'number',
name: 'Key dialog adjustment',
visible: (params: InsertPasswordEmitterParams) => params.interactionMode === 'Key',
})
private yInteractionAdjustment = 0;
@Param({
min: 0.1,
step: 0.1,
type: 'number',
defaultValue: 2,
name: 'Trigger distance',
visible: (params: InsertPasswordEmitterParams) => params.interactionMode === 'Key',
})
private triggerDistance = 2;

onReady = async () => {
await InteractionDirector.handle(this, this.showInsertPassword.bind(this));
};

private showInsertPassword() {
this.renderer.render(
<InsertPasswordPrompt
key={new Date().getTime()}
masterPassword={this.masterPassword}
onSuccess={this.handleSolvedPassword}
/>
);
}

private handleSolvedPassword = () => {
this.passwordSolvedSignal.emit()
}
}
162 changes: 162 additions & 0 deletions src/behaviors/emitters/insert-password/InsertPasswordPrompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import * as React from 'react';
import { useState } from 'react';

interface PasswordPromptProps {
onSuccess: () => void;
masterPassword: string;
}

const styles = `
.ask-for-password-wrapper {
background: radial-gradient(circle, rgba(0, 255, 255, 0.1), transparent 60%),
radial-gradient(circle at top left, rgba(0, 255, 255, 0.15), transparent 70%),
radial-gradient(circle at bottom right, rgba(0, 255, 255, 0.15), transparent 70%);
background-color: #282c34;
position: fixed;
border: 1px solid #3e3e3e;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
padding: 40px;
left: 75%;
top: 50%;
transform: translate(-50%, -50%);
width: 300px;
overflow: hidden;
}
.ask-for-password-wrapper > button {
position: absolute;
right: 10px;
top: 10px;
border: none;
background: none;
color: #f8f8f8;
font-size: 20px;
cursor: pointer;
}
.ask-for-password-wrapper > button:hover {
color: #416792;
}
.ask-for-password-form {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index: 1;
}
.ask-for-password-form > h1 {
font-size: 24px;
color: #b3eaff;
margin-bottom: 20px;
text-shadow: 0 0 5px rgba(179, 234, 255, 0.7);
}
#ask-for-password-input {
background-color: #1c1c1c;
border: 2px solid #3e3e3e;
border-radius: 4px;
color: #f8f8f8;
font-size: 16px;
margin-bottom: 20px;
padding: 10px 15px;
transition: border-color 0.3s;
width: 100%;
}
#ask-for-password-input:focus {
border-color: #b3eaff;
outline: none;
}
.ask-for-password-form > button {
background: linear-gradient(135deg, #00d4ff, #00a6ff);
border: none;
border-radius: 4px;
color: black;
font-weight: bold;
cursor: pointer;
font-size: 18px;
padding: 10px 15px;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
width: 100%;
box-shadow: 0 0 15px rgba(0, 255, 255, 0.4);
}
.ask-for-password-form > button:hover {
background: linear-gradient(135deg, #00a6ff, #00d4ff);
box-shadow: 0 0 25px rgba(0, 255, 255, 0.8);
}
.ask-for-password-form > span {
color: #ff4d4d;
margin-top: 20px;
display: block;
font-size: 16px;
}
`;

const InsertPasswordPrompt = ({
onSuccess,
masterPassword,
}: PasswordPromptProps): JSX.Element | null => {
const [password, setPassword] = useState('');
const [isVisible, setIsVisible] = useState(true);
const [errorMessage, setErrorMessage] = useState('');

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPassword(event.target.value);
};

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if (password === masterPassword) {
setIsVisible(false);
onSuccess();
} else {
setPassword('');
setErrorMessage('Incorrect password');
}
};

const handleClose = () => {
setIsVisible(false);
};

if (!isVisible) return null;

return (
<>
<div className="ask-for-password-wrapper" aria-labelledby="password-prompt-title">
<button onClick={handleClose} aria-label="Close password prompt">
<span> x </span>
</button>
<form className="ask-for-password-form" aria-label="Password prompt form">
<h1 id="password-prompt-title">Enter the Password</h1>
<input
type="password"
name="password"
id="ask-for-password-input"
value={password}
onChange={handleChange}
placeholder="Type your password here"
aria-label="Password input"
/>
<button onClick={handleClick} aria-label="Unlock button">
<span> Unlock </span>
</button>
{errorMessage && (
<span role="alert" aria-live="assertive">
{errorMessage}
</span>
)}
</form>
</div>
<style>{styles}</style>
</>
);
};

export default InsertPasswordPrompt;
Loading

0 comments on commit 086e0f4

Please sign in to comment.