Adding ability to talk with words with NPCS.

This commit is contained in:
sergiotarxz 2023-07-13 19:02:29 +02:00
parent 9267f1d950
commit 34c2bbdb72
15 changed files with 172 additions and 38 deletions

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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}/>
</> </>
) )

View File

@ -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 {

View File

@ -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}/>
) )
}) })
} }

View File

@ -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>
) )
} }

View File

@ -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) {

View File

@ -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;
} }
return $ws->send( my $word_identifier = $possible_word_identifier;
to_json( { error => 'Sending a word still not supported.' } ) ); my $maybe_word = $pj->get_word($word_identifier);
if ( !defined $maybe_word ) {
return $ws->send(
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);
} }

View File

@ -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;

View File

@ -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 ) {

View File

@ -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 ) {

View File

@ -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; }

View File

@ -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