Adding ability to talk with words with NPCS.
This commit is contained in:
parent
9267f1d950
commit
34c2bbdb72
1
Build.PL
1
Build.PL
@ -27,6 +27,7 @@ my $build = Module::Build->new(
|
|||||||
'DateTime::Format::Pg' => 0,
|
'DateTime::Format::Pg' => 0,
|
||||||
'DateTime::Format::ISO8601::Format' => 0,
|
'DateTime::Format::ISO8601::Format' => 0,
|
||||||
'Test::Most' => 0,
|
'Test::Most' => 0,
|
||||||
|
'Carp::Always' => 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
$build->create_build_script;
|
$build->create_build_script;
|
||||||
|
@ -12,4 +12,4 @@ perl Build.PL && \
|
|||||||
./Build installdeps && \
|
./Build installdeps && \
|
||||||
npx webpack && \
|
npx webpack && \
|
||||||
bash build_styles.sh && \
|
bash build_styles.sh && \
|
||||||
perl script/las_tres daemon
|
perl -MCarp::Always script/las_tres daemon
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import type { Action, ActionHash } from '@lastres/action'
|
import type { Action, ActionHash } from '@lastres/action'
|
||||||
import type { TalkNPCs } from '@lastres/talk-npc'
|
import type { TalkNPCs, TalkNPC } from '@lastres/talk-npc'
|
||||||
import type { StateGame, OnWordSelectCallback } from '@lastres/components/game'
|
import type { StateGame } from '@lastres/components/game'
|
||||||
|
|
||||||
import OutputPacketExecuteAction from '@lastres/output-packet/execute_action'
|
import OutputPacketExecuteAction from '@lastres/output-packet/execute_action'
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ export interface BottomPanelProps {
|
|||||||
actionHash: ActionHash | null
|
actionHash: ActionHash | null
|
||||||
talkNPCs: TalkNPCs | null
|
talkNPCs: TalkNPCs | null
|
||||||
setStateGame: (set: StateGame) => void
|
setStateGame: (set: StateGame) => void
|
||||||
setOnWordSelect: (set: OnWordSelectCallback) => void
|
setNPCToSayWord: (set: TalkNPC | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Style {
|
export interface Style {
|
||||||
@ -88,7 +88,7 @@ export default function BottomPanel (props: BottomPanelProps): JSX.Element {
|
|||||||
<TalkNPCsComponent websocket={props.websocket}
|
<TalkNPCsComponent websocket={props.websocket}
|
||||||
talkNPCs={props.talkNPCs}
|
talkNPCs={props.talkNPCs}
|
||||||
setStateGame={props.setStateGame}
|
setStateGame={props.setStateGame}
|
||||||
setOnWordSelect={props.setOnWordSelect}/>
|
setNPCToSayWord={props.setNPCToSayWord}/>
|
||||||
</PresentationItem>
|
</PresentationItem>
|
||||||
<PresentationItem>
|
<PresentationItem>
|
||||||
</PresentationItem>
|
</PresentationItem>
|
||||||
|
@ -32,8 +32,6 @@ export enum StateGame {
|
|||||||
SelectingWord,
|
SelectingWord,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnWordSelectCallback = (npc: TalkNPC, word: string) => void
|
|
||||||
|
|
||||||
export default function Game (props: GameProps): JSX.Element {
|
export default function Game (props: GameProps): JSX.Element {
|
||||||
const selectedPJ = props.selectedPJ
|
const selectedPJ = props.selectedPJ
|
||||||
if (selectedPJ === null) {
|
if (selectedPJ === null) {
|
||||||
@ -60,8 +58,8 @@ export default function Game (props: GameProps): JSX.Element {
|
|||||||
const [remainingFrames, setRemainingFrames] = React.useState<number | null>(null)
|
const [remainingFrames, setRemainingFrames] = React.useState<number | null>(null)
|
||||||
const [actionHash, setActionHash] = React.useState<ActionHash | null>(null)
|
const [actionHash, setActionHash] = React.useState<ActionHash | null>(null)
|
||||||
const [talkNPCs, setTalkNPCs] = React.useState<TalkNPCs | null>(null)
|
const [talkNPCs, setTalkNPCs] = React.useState<TalkNPCs | null>(null)
|
||||||
const [onWordSelect, setOnWordSelect] = React.useState<OnWordSelectCallback | null>(null)
|
|
||||||
const [stateGame, setStateGame] = React.useState<StateGame>(StateGame.MainScreen)
|
const [stateGame, setStateGame] = React.useState<StateGame>(StateGame.MainScreen)
|
||||||
|
const [NPCToSayWord, setNPCToSayWord] = React.useState<TalkNPC | null>(null)
|
||||||
const [words, setWords] = React.useState<Words | null>(null)
|
const [words, setWords] = React.useState<Words | null>(null)
|
||||||
const logPresentationRef = React.useRef<HTMLDivElement>(null)
|
const logPresentationRef = React.useRef<HTMLDivElement>(null)
|
||||||
const websocket = props.websocket
|
const websocket = props.websocket
|
||||||
@ -115,7 +113,10 @@ export default function Game (props: GameProps): JSX.Element {
|
|||||||
}, 300)
|
}, 300)
|
||||||
if (stateGame === StateGame.SelectingWord) {
|
if (stateGame === StateGame.SelectingWord) {
|
||||||
return (
|
return (
|
||||||
<WordSelector words={words} setStateGame={setStateGame}/>
|
<WordSelector words={words}
|
||||||
|
setStateGame={setStateGame}
|
||||||
|
websocket={websocket}
|
||||||
|
NPCToSayWord={NPCToSayWord}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (stateGame === StateGame.MainScreen) {
|
if (stateGame === StateGame.MainScreen) {
|
||||||
@ -134,7 +135,7 @@ export default function Game (props: GameProps): JSX.Element {
|
|||||||
<BottomPanel actionHash={actionHash}
|
<BottomPanel actionHash={actionHash}
|
||||||
websocket={websocket}
|
websocket={websocket}
|
||||||
talkNPCs={talkNPCs}
|
talkNPCs={talkNPCs}
|
||||||
setOnWordSelect={setOnWordSelect}
|
setNPCToSayWord={setNPCToSayWord}
|
||||||
setStateGame={setStateGame}/>
|
setStateGame={setStateGame}/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import type { TalkNPC } from '@lastres/talk-npc'
|
import type { TalkNPC } from '@lastres/talk-npc'
|
||||||
import type { OnWordSelectCallback } from '@lastres/components/game'
|
|
||||||
import { StateGame } from '@lastres/components/game'
|
import { StateGame } from '@lastres/components/game'
|
||||||
|
|
||||||
import OutputPacketTalk from '@lastres/output-packet/talk'
|
import OutputPacketTalk from '@lastres/output-packet/talk'
|
||||||
@ -11,7 +10,7 @@ export interface TalkNPCComponentData {
|
|||||||
websocket: WebSocket | null
|
websocket: WebSocket | null
|
||||||
key: string
|
key: string
|
||||||
setStateGame: (set: StateGame) => void
|
setStateGame: (set: StateGame) => void
|
||||||
setOnWordSelect: (set: OnWordSelectCallback) => void
|
setNPCToSayWord: (set: TalkNPC | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TalkNPCComponent (props: TalkNPCComponentData): JSX.Element {
|
export default function TalkNPCComponent (props: TalkNPCComponentData): JSX.Element {
|
||||||
@ -25,9 +24,7 @@ export default function TalkNPCComponent (props: TalkNPCComponentData): JSX.Elem
|
|||||||
|
|
||||||
function onWantToSayWord (): void {
|
function onWantToSayWord (): void {
|
||||||
props.setStateGame(StateGame.SelectingWord)
|
props.setStateGame(StateGame.SelectingWord)
|
||||||
props.setOnWordSelect((npc: TalkNPC, word: string) => {
|
props.setNPCToSayWord(npc)
|
||||||
console.log(`Trying to say ${word} to `, props.npc)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function printAvatar (npc: TalkNPC): JSX.Element {
|
function printAvatar (npc: TalkNPC): JSX.Element {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import type { TalkNPCs } from '@lastres/talk-npc'
|
import type { TalkNPCs, TalkNPC } from '@lastres/talk-npc'
|
||||||
import type { StateGame, OnWordSelectCallback } from '@lastres/components/game'
|
import type { StateGame } from '@lastres/components/game'
|
||||||
|
|
||||||
import TalkNPCComponent from '@lastres/components/talk-npc'
|
import TalkNPCComponent from '@lastres/components/talk-npc'
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ export interface TalkNPCsComponentProps {
|
|||||||
talkNPCs: TalkNPCs | null
|
talkNPCs: TalkNPCs | null
|
||||||
websocket: WebSocket | null
|
websocket: WebSocket | null
|
||||||
setStateGame: (set: StateGame) => void
|
setStateGame: (set: StateGame) => void
|
||||||
setOnWordSelect: (set: OnWordSelectCallback) => void
|
setNPCToSayWord: (set: TalkNPC | null) => void
|
||||||
}
|
}
|
||||||
export default function TalkNPCsComponent (props: TalkNPCsComponentProps): JSX.Element {
|
export default function TalkNPCsComponent (props: TalkNPCsComponentProps): JSX.Element {
|
||||||
const npcs = props.talkNPCs
|
const npcs = props.talkNPCs
|
||||||
@ -32,7 +32,7 @@ export default function TalkNPCsComponent (props: TalkNPCsComponentProps): JSX.E
|
|||||||
npc={npc}
|
npc={npc}
|
||||||
websocket={props.websocket}
|
websocket={props.websocket}
|
||||||
setStateGame={props.setStateGame}
|
setStateGame={props.setStateGame}
|
||||||
setOnWordSelect={props.setOnWordSelect}/>
|
setNPCToSayWord={props.setNPCToSayWord}/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,62 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import type { Words } from '@lastres/word'
|
import type { Words, Word } from '@lastres/word'
|
||||||
import { StateGame } from '@lastres/components/game'
|
import { StateGame } from '@lastres/components/game'
|
||||||
|
import OutputPacketTalk from '@lastres/output-packet/talk'
|
||||||
|
import type { TalkNPC } from '@lastres/talk-npc'
|
||||||
|
|
||||||
export interface WordSelectorProps {
|
export interface WordSelectorProps {
|
||||||
words: Words | null
|
words: Words | null
|
||||||
setStateGame: (set: StateGame) => void
|
setStateGame: (set: StateGame) => void
|
||||||
|
websocket: WebSocket | null
|
||||||
|
NPCToSayWord: TalkNPC | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WordSelector (props: WordSelectorProps): JSX.Element {
|
export default function WordSelector (props: WordSelectorProps): JSX.Element {
|
||||||
const words = props.words
|
const words = props.words
|
||||||
|
|
||||||
|
if (props.NPCToSayWord === null) {
|
||||||
|
console.log('Error, should be a NPC to talk with, exiting this screen.')
|
||||||
|
props.setStateGame(StateGame.MainScreen)
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
function onClickWord (word: Word): void {
|
||||||
|
if (props.NPCToSayWord === null || props.websocket === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
new OutputPacketTalk(props.NPCToSayWord.identifier, word.identifier).send(props.websocket)
|
||||||
|
props.setStateGame(StateGame.MainScreen)
|
||||||
|
}
|
||||||
|
function printWord (word: Word): JSX.Element {
|
||||||
|
return (
|
||||||
|
<a key={word.identifier}
|
||||||
|
onClick={() => {
|
||||||
|
onClickWord(word)
|
||||||
|
}}
|
||||||
|
href="#"
|
||||||
|
className="word">
|
||||||
|
{
|
||||||
|
word.name
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function wordList (): JSX.Element {
|
||||||
|
if (words === null) {
|
||||||
|
return <p>No hay palabras para poder decir aun.</p>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="word-list">
|
||||||
|
{
|
||||||
|
Object.keys(words).map((key) => {
|
||||||
|
const word = words[key]
|
||||||
|
return printWord(word)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="word-selector">
|
<div className="word-selector">
|
||||||
<a href="#" className="close-button"
|
<a href="#" className="close-button"
|
||||||
@ -17,6 +64,9 @@ export default function WordSelector (props: WordSelectorProps): JSX.Element {
|
|||||||
props.setStateGame(StateGame.MainScreen)
|
props.setStateGame(StateGame.MainScreen)
|
||||||
}}>x</a>
|
}}>x</a>
|
||||||
<h2>Selecciona una palabra.</h2>
|
<h2>Selecciona una palabra.</h2>
|
||||||
|
{
|
||||||
|
wordList()
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -168,6 +168,7 @@ export default class InputPackets {
|
|||||||
}, 10)
|
}, 10)
|
||||||
}
|
}
|
||||||
if (data.known_words !== undefined) {
|
if (data.known_words !== undefined) {
|
||||||
|
console.log(data.known_words);
|
||||||
this.setWords(data.known_words)
|
this.setWords(data.known_words)
|
||||||
}
|
}
|
||||||
if (data.append_log !== undefined) {
|
if (data.append_log !== undefined) {
|
||||||
|
@ -10,6 +10,8 @@ use List::AllUtils;
|
|||||||
|
|
||||||
use JSON qw/to_json/;
|
use JSON qw/to_json/;
|
||||||
|
|
||||||
|
use LasTres::Words;
|
||||||
|
|
||||||
sub identifier {
|
sub identifier {
|
||||||
return 'talk';
|
return 'talk';
|
||||||
}
|
}
|
||||||
@ -40,14 +42,26 @@ sub handle ( $self, $ws, $session, $data ) {
|
|||||||
}
|
}
|
||||||
my $possible_word_identifier = $data->{word};
|
my $possible_word_identifier = $data->{word};
|
||||||
if ( !defined $possible_word_identifier ) {
|
if ( !defined $possible_word_identifier ) {
|
||||||
$self->handle_wordlessly_talk( $ws, $pj, $npc );
|
$self->handle_wordlessly_talk( $pj, $npc );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
my $word_identifier = $possible_word_identifier;
|
||||||
|
my $maybe_word = $pj->get_word($word_identifier);
|
||||||
|
if ( !defined $maybe_word ) {
|
||||||
return $ws->send(
|
return $ws->send(
|
||||||
to_json( { error => 'Sending a word still not supported.' } ) );
|
to_json( { error => 'You do not know this word.' } ) );
|
||||||
|
}
|
||||||
|
my $word = $maybe_word;
|
||||||
|
$self->handle_say_word($pj, $npc, $word);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub handle_wordlessly_talk ( $self, $ws, $pj, $npc ) {
|
sub handle_say_word($self, $pj, $npc, $word) {
|
||||||
|
$npc->talk( $pj, $word );
|
||||||
|
$self->end_conversation($pj)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_wordlessly_talk ( $self, $pj, $npc ) {
|
||||||
$npc->talk( $pj, undef );
|
$npc->talk( $pj, undef );
|
||||||
$self->end_conversation($pj);
|
$self->end_conversation($pj);
|
||||||
}
|
}
|
||||||
|
@ -158,6 +158,10 @@ sub known_words_hash ($self) {
|
|||||||
return \%result_words;
|
return \%result_words;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub get_word($self, $word_identifier) {
|
||||||
|
return $self->known_words_hash->{$word_identifier};
|
||||||
|
}
|
||||||
|
|
||||||
sub known_words_hash_serialized ($self) {
|
sub known_words_hash_serialized ($self) {
|
||||||
my %words = %{ $self->known_words_hash };
|
my %words = %{ $self->known_words_hash };
|
||||||
my @identifiers = keys %words;
|
my @identifiers = keys %words;
|
||||||
|
@ -22,7 +22,7 @@ requires qw/identifier name/;
|
|||||||
# sub name($self, $pj);
|
# sub name($self, $pj);
|
||||||
#
|
#
|
||||||
## IMPLEMENTORS MUST EXTEND
|
## IMPLEMENTORS MUST EXTEND
|
||||||
# sub talk($self,$pj,$word);
|
# sub talk($self,$pj,$word = undef);
|
||||||
|
|
||||||
## DO NOT EXTEND NOT SUPPORTED.
|
## DO NOT EXTEND NOT SUPPORTED.
|
||||||
sub hash ($self) {
|
sub hash ($self) {
|
||||||
@ -56,7 +56,7 @@ sub show_told_word ( $self, $pj, $word ) {
|
|||||||
$team->append_log_line(
|
$team->append_log_line(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text => $pj,
|
text => $pj->nick,
|
||||||
color => 'green',
|
color => 'green',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -64,7 +64,7 @@ sub show_told_word ( $self, $pj, $word ) {
|
|||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text => $word,
|
text => $word->name,
|
||||||
color => 'purple',
|
color => 'purple',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -142,7 +142,7 @@ sub show_wordlessly_talk_started ( $self, $pj ) {
|
|||||||
# the team members the contents of
|
# the team members the contents of
|
||||||
# the conversation so they get context about
|
# the conversation so they get context about
|
||||||
# what their partners need to do.
|
# what their partners need to do.
|
||||||
sub talk ( $self, $pj, $word ) {
|
sub talk ( $self, $pj, $word = undef ) {
|
||||||
$pj = $pj->get_from_storage;
|
$pj = $pj->get_from_storage;
|
||||||
my $team = $pj->team;
|
my $team = $pj->team;
|
||||||
if ( defined $word ) {
|
if ( defined $word ) {
|
||||||
|
@ -14,12 +14,45 @@ use LasTres::Word::Devota;
|
|||||||
|
|
||||||
use parent 'LasTres::TalkingNPC';
|
use parent 'LasTres::TalkingNPC';
|
||||||
|
|
||||||
sub talk ( $self, $pj, $word ) {
|
sub talk ( $self, $pj, $word = undef ) {
|
||||||
$self->SUPER::talk( $pj, $word );
|
$self->SUPER::talk( $pj, $word );
|
||||||
if ( !defined $word ) {
|
if ( !defined $word ) {
|
||||||
$self->wordlessly_talk($pj);
|
$self->wordlessly_talk($pj);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$self->word_talk($pj, $word);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub word_talk($self, $pj, $word) {
|
||||||
|
require LasTres::Word::Devota;
|
||||||
|
if ($word->identifier eq LasTres::Word::Devota->instance->identifier) {
|
||||||
|
$self->word_devota($pj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$self->word_unknown($pj);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub word_devota($self, $pj) {
|
||||||
|
$self->send_response_dialog(
|
||||||
|
$pj,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text => '¿La Devota? Suele estar en la casa de piedra.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub word_unknown($self, $pj) {
|
||||||
|
$self->send_response_dialog(
|
||||||
|
$pj,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text => 'Mmmm seguro que eso es muy interesante para otra persona.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub identifier {
|
sub identifier {
|
||||||
@ -35,10 +68,10 @@ sub name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub verb ( $self, $pj ) {
|
sub verb ( $self, $pj ) {
|
||||||
if ( $pj->knows_word( LasTres::Word::Devota->instance ) ) {
|
if ( !$pj->knows_word( LasTres::Word::Devota->instance ) ) {
|
||||||
return 'indica';
|
|
||||||
}
|
|
||||||
return 'farfulla';
|
return 'farfulla';
|
||||||
|
}
|
||||||
|
return 'indica';
|
||||||
}
|
}
|
||||||
|
|
||||||
sub wordlessly_talk ( $self, $pj ) {
|
sub wordlessly_talk ( $self, $pj ) {
|
||||||
|
@ -254,3 +254,18 @@ body {
|
|||||||
body div.word-selector a.close-button:hover {
|
body div.word-selector a.close-button:hover {
|
||||||
background: lightgray;
|
background: lightgray;
|
||||||
color: black; }
|
color: black; }
|
||||||
|
body div.word-selector div.word-list {
|
||||||
|
width: 50%; }
|
||||||
|
body div.word-selector div.word-list a.word {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid black;
|
||||||
|
font-size: 30px;
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center; }
|
||||||
|
body div.word-selector div.word-list a.word:hover {
|
||||||
|
color: white;
|
||||||
|
background: gray; }
|
||||||
|
@ -310,5 +310,23 @@ body {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
div.word-list {
|
||||||
|
width: 50%;
|
||||||
|
a.word {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid black;
|
||||||
|
font-size: 30px;
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user