diff --git a/js-src/input-packets.ts b/js-src/input-packets.ts index 88283d8..88090b3 100644 --- a/js-src/input-packets.ts +++ b/js-src/input-packets.ts @@ -91,7 +91,11 @@ export default class InputPackets { if (logPresentationRef.current === null) { return } - scrollData = [logPresentationRef.current.scrollHeight, logPresentationRef.current.scrollTop, logPresentationRef.current.offsetHeight] + scrollData = [ + logPresentationRef.current.scrollHeight, + logPresentationRef.current.scrollTop, + logPresentationRef.current.offsetHeight + ] } function applyScroll (): void { if (scrollData.length < 3) { diff --git a/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm b/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm index cd9c0b2..ac48355 100644 --- a/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm +++ b/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm @@ -13,6 +13,7 @@ use Moo; use LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima; use LasTres::PJAction::GolpearArbolCentralTribuDeLaLima; +use LasTres::TalkingNPC::AncianoTribuLima; with 'LasTres::Location'; @@ -25,7 +26,8 @@ sub name { } sub description { - return 'Desde este árbol se puede contemplar toda la Tribu de la Lima, sus casitas de paja y los cultivos de lima.'; + return +'Desde este árbol se puede contemplar toda la Tribu de la Lima, sus casitas de paja y los cultivos de lima.'; } sub parent { @@ -33,13 +35,11 @@ sub parent { } sub actions { - return [ - LasTres::PJAction::GolpearArbolCentralTribuDeLaLima->new - ]; + return [ LasTres::PJAction::GolpearArbolCentralTribuDeLaLima->new ]; } sub npcs { - return []; + return [ LasTres::TalkingNPC::AncianoTribuLima->new ]; } sub connected_places { @@ -47,9 +47,10 @@ sub connected_places { } my $singleton; + sub instance { my $class = shift; - if (!defined $singleton) { + if ( !defined $singleton ) { $singleton = $class->new(@_); } return $singleton; diff --git a/lib/LasTres/Schema/Result/PJ.pm b/lib/LasTres/Schema/Result/PJ.pm index 180c4ba..f500f54 100644 --- a/lib/LasTres/Schema/Result/PJ.pm +++ b/lib/LasTres/Schema/Result/PJ.pm @@ -502,5 +502,10 @@ sub actions ($self) { @actions = ( @actions, @$location_actions ); return \@actions; } + +sub talking_npcs($self) { + return []; +} + with 'LasTres::CombatCapableEntity'; 1; diff --git a/lib/LasTres/TalkingNPC.pm b/lib/LasTres/TalkingNPC.pm new file mode 100644 index 0000000..b56f88d --- /dev/null +++ b/lib/LasTres/TalkingNPC.pm @@ -0,0 +1,172 @@ +package LasTres::TalkingNPC; + +use v5.36.0; +use strict; +use warnings; +use utf8; + +use feature 'signatures'; + +use Moo::Role; + +requires qw/identifier name/; + +## IMPLEMENTORS MUST IMPLEMENT +# sub identifier; +# +# Identifier must be unique across +# npcs, failure to do so may +# result in a runtime error and +# is not supported. +# +# sub name($self, $pj); +# +## IMPLEMENTORS MUST EXTEND +# sub talk($self,$pj,$word); + +## OVERRIDE +sub icon { + return undef; +} + +## OVERRIDE +sub color ( $self, $pj ) { + return 'blue',; +} + +## OVERRIDE +# Refer to show_wordlessly_talk_started for +# detail about when it is convenient to +# override. +sub show_told_word($self, $pj, $word) { + my $team = $pj->team; + $team->append_log_line( + [ + { + text => $pj, + color => 'green', + }, + { + text => ' dijo "', + + }, + { + text => $word, + color => 'purple', + }, + { + text => '" a ', + }, + { + text => $self->name($pj), + color => $self->color($pj), + }, + { + text => '.', + }, + ] + ); +} + +## OVERRIDE +# Override this if you want a special text +# on the dialog start. +# This is useful when the pj is talking +# with a not talk capable life form. +# For example a for a dog this function +# could be. +# sub show_wordlessly_talk_started($self, $pj) { +# my $team = $pj->team; +# $team->append_log_line( +# [ +# { +# text => $self->name($pj), +# color => $self->color($pj), +# }, +# { +# text => ' ladra a ', +# +# }, +# { +# text => $pj, +# color => 'green', +# }, +# { +# text => '.', +# }, +# ] +# ); +# } +sub show_wordlessly_talk_started($self, $pj) { + my $team = $pj->team; + $team->append_log_line( + [ + { + text => $pj, + color => 'green', + }, + { + text => ' habló con ', + + }, + { + text => $self->name($pj), + color => $self->color($pj), + }, + { + text => '.', + }, + ] + ); +} + +## OVERRIDE (Always use $self->SUPER::talk.) +# You should override with whatever is going to be said +# by the npc using send_response_dialog($self,$pj,$array_text) +# and if you want with what happens on talk with +# the npc that is not a dialog with the common +# append_log_line, is polite to send all +# the team members the contents of +# the conversation so they get context about +# what their partners need to do. +sub talk ( $self, $pj, $word ) { + $pj = $pj->get_from_storage; + my $team = $pj->team; + if ( defined $word ) { + $self->show_told_word($pj, $word); + return; + } + +} + +## OVERRIDE +sub verb($self, $pj) { + return 'dice'; +} + +## DO NOT EXTEND NOT SUPPORTED. +sub send_response_dialog ( $self, $pj, $array_text ) { + $pj = $pj->get_from_storage; + my $team = $pj->team; + if ( !ref $array_text eq 'ARRAY' ) { + die '$array_text should be an array.'; + } + $team->append_log_line( + [ + { text => $self->name($pj), color => $self->color($pj) }, + { text => " @{[$self->verb($pj)]}.- " }, @$array_text + ] + ); +} + +## DO NOT EXTEND NOT SUPPORTED. +{ + my %hash; + sub instance($class) { + if (!exists $hash{$class}) { + $hash{$class} = $class->new; + } + return $hash{$class}; + } +} +1; diff --git a/lib/LasTres/TalkingNPC/.exists b/lib/LasTres/TalkingNPC/.exists new file mode 100644 index 0000000..e69de29 diff --git a/lib/LasTres/TalkingNPC/AncianoTribuLima.pm b/lib/LasTres/TalkingNPC/AncianoTribuLima.pm new file mode 100644 index 0000000..299aa19 --- /dev/null +++ b/lib/LasTres/TalkingNPC/AncianoTribuLima.pm @@ -0,0 +1,47 @@ +package LasTres::TalkingNPC::AncianoTribuLima; + +use v5.36.0; +use strict; +use warnings; +use utf8; + +use feature 'signatures'; + +use Moo; + +with 'LasTres::TalkingNPC'; + +sub talk ( $self, $pj, $word ) { + if ( !defined $word ) { + $self->wordlessly_talk($pj); + return; + } +} + +sub identifier { + return 'anciano_tribu_de_la_lima'; +} + +sub name { + return 'Anciano'; +} + +sub wordlessly_talk ( $self, $pj ) { + $self->send_response_dialog( + [ + { + text => ( + '¿De verdad estabas dormido?' + . ' Una lastima que no hayas visto ese gran pájaro que acaba de pasar' + . ' justo ahora. En cualquier caso, no deberías estar vagueando, ¿Hoy' + . ' no era el día que te marchabas a la Torre? Si se hace de noche' + . ' será demasiado peligroso y tendrás que salir mañana. Ve a hablar' + . ' ahora mismo con la Devota, sin su bendición nadie emprende el viaje a la Torre.' + . ' ¿No sabes donde puede estar la Devota? No tiene perdida, es la única casa hecha de piedra.' + . ' Si no la encuentras siempre puedes preguntar a la gente de la tribu.' + ) + } + ] + ); +} +1; diff --git a/lib/LasTres/TalkingNPCs.pm b/lib/LasTres/TalkingNPCs.pm new file mode 100644 index 0000000..75f7c4e --- /dev/null +++ b/lib/LasTres/TalkingNPCs.pm @@ -0,0 +1,37 @@ +package LasTres::TalkingNPCs; + +use v5.36.0; +use strict; +use warnings; +use utf8; + +use feature 'signatures'; + +use Moo; +use Scalar::Util; +use Module::Pluggable + search_path => ['LasTres::Word'], + instantiate => 'instance', + on_require_error => sub ( $plugin, $error ) { + die $error; + }; + +has hash => ( + is => 'rw', + lazy => 1, + builder => \&_build_hash, +); + +sub _build_hash ($self) { + my @talking_npcs = $self->plugins(); + my %hash; + for my $talking_npc (@talking_npcs) { + my $class = Scalar::Util::blessed($talking_npc); + if ( !$talking_npc->does('LasTres::TalkingNPC') ) { + die "$class must implement LasTres::TalkingNPC."; + } + $hash{ $talking_npc->identifier } = $talking_npc; + } + return \%hash; +} +1; diff --git a/lib/LasTres/Word.pm b/lib/LasTres/Word.pm new file mode 100644 index 0000000..b696358 --- /dev/null +++ b/lib/LasTres/Word.pm @@ -0,0 +1,20 @@ +package LasTres::Word; + +use v5.36.0; +use strict; +use warnings; +use utf8; + +use feature 'signatures'; + +use Moo::Role; + +requires qw/name identifier/; +## IMPLEMENTORS MUST IMPLEMENT +# sub name($self,$pj); +# sub identifier; +# +# Identifier must be unique across words, failure +# to do so can result in a error or undefined +# behavior. +1; diff --git a/lib/LasTres/Word/.exists b/lib/LasTres/Word/.exists new file mode 100644 index 0000000..e69de29 diff --git a/lib/LasTres/Words.pm b/lib/LasTres/Words.pm new file mode 100644 index 0000000..e9fa56f --- /dev/null +++ b/lib/LasTres/Words.pm @@ -0,0 +1,32 @@ +package LasTres::Words; + +use v5.36.0; + +use strict; +use warnings; + +use feature 'signatures'; + +use Moo; + +use Module::Pluggable search_path => ['LasTres::Word'], + instantiate => 'instance', + on_require_error => sub ($plugin, $error) { + die $error; + }; + +has hash => ( + is => 'rw', + lazy => 1, + builder => \&_build_hash, +); + +sub _build_hash($self) { + my @words = $self->plugins(); + my %hash; + for my $word (@words) { + $hash{$word->identifier} = $word; + } + return \%hash; +} +1; diff --git a/public/js/bundle.js b/public/js/bundle.js index 529a979..38000d3 100644 --- a/public/js/bundle.js +++ b/public/js/bundle.js @@ -266,7 +266,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac \*********************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ InputPackets)\n/* harmony export */ });\n/* harmony import */ var _lastres_input_packet_info__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/input-packet/info */ \"./js-src/input-packet/info.ts\");\n/* harmony import */ var _lastres_input_packet_pong__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/input-packet/pong */ \"./js-src/input-packet/pong.ts\");\n\n\nclass InputPackets {\n constructor(setTeamPJs, setEnemyTeamPJs, setIsBattling, setCurrentLocation, setConnectedLocations, logLines, setLogLines, setError, setScrollLog, logPresentationRef, setMovingTo, setRemainingFrames, setActionHash) {\n this.cachedHash = null;\n this.cachedArray = null;\n this.setTeamPJs = setTeamPJs;\n this.setEnemyTeamPJs = setEnemyTeamPJs;\n this.setCurrentLocation = setCurrentLocation;\n this.setConnectedLocations = setConnectedLocations;\n this.logLines = logLines;\n this.setLogLines = setLogLines;\n this.setError = setError;\n this.setScrollLog = setScrollLog;\n this.logPresentationRef = logPresentationRef;\n this.setMovingTo = setMovingTo;\n this.setRemainingFrames = setRemainingFrames;\n this.setIsBattling = setIsBattling;\n this.setActionHash = setActionHash;\n }\n handle(packet) {\n const hash = this.hashAvailablePackets();\n const identifier = packet.command;\n const inputPacket = hash[identifier];\n inputPacket.recv(packet);\n }\n listAvailablePackets() {\n let firstTime = true;\n if (this.cachedArray === null) {\n const infoPacket = new _lastres_input_packet_info__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n const pongPacket = new _lastres_input_packet_pong__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n infoPacket.onReceive((data) => {\n const logPresentationRef = this.logPresentationRef;\n let scrollData = [];\n function saveScroll() {\n if (logPresentationRef.current === null) {\n return;\n }\n scrollData = [logPresentationRef.current.scrollHeight, logPresentationRef.current.scrollTop, logPresentationRef.current.offsetHeight];\n }\n function applyScroll() {\n if (scrollData.length < 3) {\n return;\n }\n if (logPresentationRef.current === null) {\n console.log('Not defined');\n return;\n }\n const logPresentation = logPresentationRef.current;\n const [scrollHeight, scrollTop, offsetHeight] = scrollData;\n if (firstTime) {\n firstTime = false;\n logPresentation.scrollTo(0, logPresentation.scrollHeight);\n return;\n }\n if (scrollHeight <= scrollTop + offsetHeight * (3 / 2)) {\n logPresentation.scrollTo(0, logPresentation.scrollHeight);\n }\n }\n if (data.error !== undefined) {\n this.setError(data.error);\n return;\n }\n if (data.team_pjs !== undefined) {\n this.setTeamPJs(data.team_pjs);\n }\n if (data.enemy_team_pjs !== undefined) {\n this.setEnemyTeamPJs(data.enemy_team_pjs);\n }\n if (data.is_battling !== undefined) {\n this.setIsBattling(data.is_battling);\n }\n if (data.location_data !== undefined) {\n console.log(data.location_data);\n if (data.location_data.connected_places !== undefined) {\n this.setConnectedLocations(data.location_data.connected_places);\n }\n if (data.location_data.current !== undefined) {\n this.setCurrentLocation(data.location_data.current);\n }\n if (data.location_data.moving_to !== undefined) {\n this.setMovingTo(data.location_data.moving_to);\n }\n else {\n this.setMovingTo(null);\n }\n }\n if (data.remaining_frames !== undefined) {\n this.setRemainingFrames(data.remaining_frames);\n }\n if (data.set_log !== undefined) {\n saveScroll();\n this.setLogLines(data.set_log);\n window.setTimeout(() => {\n applyScroll();\n }, 10);\n }\n if (data.append_log !== undefined) {\n const logHash = {};\n saveScroll();\n this.setLogLines((logLines) => {\n if (logLines !== null) {\n for (const item of logLines) {\n logHash[item.uuid] = item;\n }\n logHash[data.append_log.uuid] = data.append_log;\n const outputLog = Object.keys(logHash).map((item, i) => {\n return logHash[item];\n });\n return outputLog;\n }\n return [];\n });\n window.setTimeout(() => {\n applyScroll();\n }, 10);\n }\n if (data.available_actions !== undefined) {\n this.setActionHash(data.available_actions);\n }\n });\n this.cachedArray = [infoPacket, pongPacket];\n }\n return this.cachedArray;\n }\n hashAvailablePackets() {\n if (this.cachedHash === null) {\n this.cachedHash = {};\n for (const inputPacket of this.listAvailablePackets()) {\n this.cachedHash[inputPacket.identifier()] = inputPacket;\n }\n }\n return this.cachedHash;\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/input-packets.ts?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ InputPackets)\n/* harmony export */ });\n/* harmony import */ var _lastres_input_packet_info__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/input-packet/info */ \"./js-src/input-packet/info.ts\");\n/* harmony import */ var _lastres_input_packet_pong__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/input-packet/pong */ \"./js-src/input-packet/pong.ts\");\n\n\nclass InputPackets {\n constructor(setTeamPJs, setEnemyTeamPJs, setIsBattling, setCurrentLocation, setConnectedLocations, logLines, setLogLines, setError, setScrollLog, logPresentationRef, setMovingTo, setRemainingFrames, setActionHash) {\n this.cachedHash = null;\n this.cachedArray = null;\n this.setTeamPJs = setTeamPJs;\n this.setEnemyTeamPJs = setEnemyTeamPJs;\n this.setCurrentLocation = setCurrentLocation;\n this.setConnectedLocations = setConnectedLocations;\n this.logLines = logLines;\n this.setLogLines = setLogLines;\n this.setError = setError;\n this.setScrollLog = setScrollLog;\n this.logPresentationRef = logPresentationRef;\n this.setMovingTo = setMovingTo;\n this.setRemainingFrames = setRemainingFrames;\n this.setIsBattling = setIsBattling;\n this.setActionHash = setActionHash;\n }\n handle(packet) {\n const hash = this.hashAvailablePackets();\n const identifier = packet.command;\n const inputPacket = hash[identifier];\n inputPacket.recv(packet);\n }\n listAvailablePackets() {\n let firstTime = true;\n if (this.cachedArray === null) {\n const infoPacket = new _lastres_input_packet_info__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n const pongPacket = new _lastres_input_packet_pong__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n infoPacket.onReceive((data) => {\n const logPresentationRef = this.logPresentationRef;\n let scrollData = [];\n function saveScroll() {\n if (logPresentationRef.current === null) {\n return;\n }\n scrollData = [\n logPresentationRef.current.scrollHeight,\n logPresentationRef.current.scrollTop,\n logPresentationRef.current.offsetHeight\n ];\n }\n function applyScroll() {\n if (scrollData.length < 3) {\n return;\n }\n if (logPresentationRef.current === null) {\n console.log('Not defined');\n return;\n }\n const logPresentation = logPresentationRef.current;\n const [scrollHeight, scrollTop, offsetHeight] = scrollData;\n if (firstTime) {\n firstTime = false;\n logPresentation.scrollTo(0, logPresentation.scrollHeight);\n return;\n }\n if (scrollHeight <= scrollTop + offsetHeight * (3 / 2)) {\n logPresentation.scrollTo(0, logPresentation.scrollHeight);\n }\n }\n if (data.error !== undefined) {\n this.setError(data.error);\n return;\n }\n if (data.team_pjs !== undefined) {\n this.setTeamPJs(data.team_pjs);\n }\n if (data.enemy_team_pjs !== undefined) {\n this.setEnemyTeamPJs(data.enemy_team_pjs);\n }\n if (data.is_battling !== undefined) {\n this.setIsBattling(data.is_battling);\n }\n if (data.location_data !== undefined) {\n console.log(data.location_data);\n if (data.location_data.connected_places !== undefined) {\n this.setConnectedLocations(data.location_data.connected_places);\n }\n if (data.location_data.current !== undefined) {\n this.setCurrentLocation(data.location_data.current);\n }\n if (data.location_data.moving_to !== undefined) {\n this.setMovingTo(data.location_data.moving_to);\n }\n else {\n this.setMovingTo(null);\n }\n }\n if (data.remaining_frames !== undefined) {\n this.setRemainingFrames(data.remaining_frames);\n }\n if (data.set_log !== undefined) {\n saveScroll();\n this.setLogLines(data.set_log);\n window.setTimeout(() => {\n applyScroll();\n }, 10);\n }\n if (data.append_log !== undefined) {\n const logHash = {};\n saveScroll();\n this.setLogLines((logLines) => {\n if (logLines !== null) {\n for (const item of logLines) {\n logHash[item.uuid] = item;\n }\n logHash[data.append_log.uuid] = data.append_log;\n const outputLog = Object.keys(logHash).map((item, i) => {\n return logHash[item];\n });\n return outputLog;\n }\n return [];\n });\n window.setTimeout(() => {\n applyScroll();\n }, 10);\n }\n if (data.available_actions !== undefined) {\n this.setActionHash(data.available_actions);\n }\n });\n this.cachedArray = [infoPacket, pongPacket];\n }\n return this.cachedArray;\n }\n hashAvailablePackets() {\n if (this.cachedHash === null) {\n this.cachedHash = {};\n for (const inputPacket of this.listAvailablePackets()) {\n this.cachedHash[inputPacket.identifier()] = inputPacket;\n }\n }\n return this.cachedHash;\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/input-packets.ts?"); /***/ }),