diff --git a/README.md b/README.md index a893ffb..b913a21 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,291 @@ # LasTres +## Installation instructions. (Prod) + +This is an example, you can configure the server +as you please, use this guide as a reference. + +This guide uses Debian stable 12, this server +requires at least a system compatible with +Redis such as Linux, Windows is not compatible +with Redis, but you can use it virtualized or +with WSL2. + +We recommend Linux or at least something Unix-like +to deploy both in server and in development this +project. + +That said features that improve OS compatibility +will always be appreciated. + +### Postgresql notes. + +We are going to use the peer authentication that +comes preconfigured in Debian, if you are using +other OS you should care of `pg_hba.conf` to +be configured as peer for empty address. + +This is how it looks on the Debian I used. + +You can also configure it with password if the database +server is not the same host that the webserver, it +is supported by the software. + +``` +local all postgres peer + +# TYPE DATABASE USER ADDRESS METHOD + +# "local" is for Unix domain socket connections only +local all all peer +# IPv4 local connections: +host all all 127.0.0.1/32 scram-sha-256 +# IPv6 local connections: +host all all ::1/128 scram-sha-256 +# Allow replication connections from localhost, by a user with the +# replication privilege. +local replication all peer +host replication all 127.0.0.1/32 scram-sha-256 +host replication all ::1/128 scram-sha-256 +``` + +### Creating user + +```shell +sudo useradd -m las_tres -d /var/lib/las_tres +``` + +### Installing dependencies. + +```shell +sudo apt update && \ +sudo apt install git postgresql redis nginx pwgen liblocal-lib-perl libpq-dev +``` + +### Starting an enable dependency services + +```shell +sudo systemctl daemon-reload && \ +sudo systemctl enable redis-server && \ +sudo systemctl start redis-server && \ +sudo systemctl enable postgresql && \ +sudo systemctl start postgresql && \ +sudo systemctl enable nginx && \ +sudo systemctl start nginx +``` + +### Configuring the database + +```shell +( cat << 'EOF' +create user "las_tres"; +create database "las_tres"; +grant all privileges on database "las_tres" to "las_tres"; +alter database "las_tres" owner to "las_tres"; +EOF +) | sudo -u postgres psql +``` + +### Cloning project. + +```shell +sudo -u las_tres bash -c "$(cat <<'EOF' +cd && \ +git clone https://git.owlcode.tech/sergiotarxz/LasTres +EOF +)" +``` + +### If you are going to change the JS. (Only once.) + +```shell +sudo apt update && sudo apt install nodejs npm && \ +sudo -u las_tres bash -c "$(cat <<'EOF' +cd ~/LasTres && \ +npm install +EOF +)" +``` + +### Deploying your js changes + +```shell +sudo -u las_tres bash -c "$(cat <<'EOF' +cd ~/LasTres && \ +npx webpack +EOF +)" +``` + +### If you are going to change the css (Only once.) + +```shell +sudo apt update && sudo apt install sassc +``` + +### Deploying the css changes + +```shell +sudo -u las_tres bash -c "$(cat <<'EOF' +cd ~/LasTres && \ +bash build_styles.sh +EOF +)" +``` + + +### Configuring NGINX. + +#### (TLS is out of the scope of this tutorial, but you must do it if you want to deploy this server to the public world.) + +```shell +( cat << 'EOF' +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} +upstream backend_las_tres { + server 127.0.0.1:3000 fail_timeout=0; +} + +server { + listen 80; + listen [::]:80; + server_name las_tres.example.com; + location /.well-known/acme-challenge/ { allow all; } + + keepalive_timeout 70; + sendfile on; + client_max_body_size 80m; + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon; + + location / { + add_header Strict-Transport-Security "max-age=31536000" always; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Proxy ""; + proxy_pass_header Server; + + proxy_pass http://backend_las_tres; + proxy_buffering on; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + tcp_nodelay on; + } + +} +EOF +) | sudo tee /etc/nginx/sites-enabled/las_tres.conf && \ +sudo systemctl reload nginx +``` + +### Configuring the app + +For non standard configurations you should peek the +config in `las_tres.example.yml` copy to `las_tres.yml` +and modify the copy until it suits your needs, patches +and issues are welcome if you need more options than +example allows you to do. + +```shell +sudo -u las_tres bash -c "$( cat << 'EOF' +cd ~/LasTres && \ +cat << EOF1 > las_tres.yml +secrets: + - $(pwgen -s 512 1) +database: + dbname: las_tres +hypnotoad: + listen: +# Here we have changed it to only listen localhost. + - http://127.0.0.1:3000 +EOF1 +EOF +)" +``` + + +### Installing Perl deps + +```shell +sudo -u las_tres bash -c "$( cat << EOF +source ~/.profile && \ +if ! grep PERL5LIB ~/.profile; then + perl -Mlocal::lib 2>/dev/null >> ~/.profile ; +fi && \ +rm -fr ~/.cpan && \ +( echo y && echo reload cpan && echo o conf commit ) | perl -MCPAN -Mlocal::lib -e shell && \ +cd ~/LasTres && \ +perl Build.PL && \ +./Build installdeps +EOF +)" +``` + +### Creating Systemd service + +```shell +( cat << 'EOF' +[Unit] +Description=LasTres the web text game +After=network.target postgresql.service redis.service + +[Service] +User=las_tres +Group=las_tres +WorkingDirectory=/var/lib/las_tres/LasTres/ +ExecStart=/bin/bash -c "source /var/lib/las_tres/.profile && hypnotoad -f script/las_tres" +User=las_tres +Group=las_tres +Restart=on-failure + +[Install] +WantedBy=multi-user.target +Alias=las_tres.service +EOF +) | sudo tee /etc/systemd/system/las_tres.service +``` + +### Deploying the database + +#### If you are installing for the first time. + +```shell +sudo -u las_tres bash -c "$( cat << EOF +cd ~/LasTres && \ +source ~/.profile && \ +perl script/install.pl +EOF +)" +``` + +#### If you are migrating from a older version + +```shell +sudo -u las_tres bash -c "$( cat << EOF +cd ~/LasTres && \ +source ~/.profile && \ +perl script/upgrade.pl +EOF +)" +``` + +### Enabling and starting service + +```shell +sudo systemctl daemon-reload && \ +sudo systemctl restart las_tres && \ +sudo systemctl enable las_tres +``` diff --git a/js-src/action.ts b/js-src/action.ts new file mode 100644 index 0000000..2d47c2c --- /dev/null +++ b/js-src/action.ts @@ -0,0 +1,8 @@ +export interface Action { + name: string + identifier: string + icon: string | null + is_disabled: boolean + disabled_reason: string | null +}; +export type ActionHash = Record diff --git a/js-src/components/bottom-panel.tsx b/js-src/components/bottom-panel.tsx index 0a8e83f..758bd78 100644 --- a/js-src/components/bottom-panel.tsx +++ b/js-src/components/bottom-panel.tsx @@ -1,18 +1,72 @@ import * as React from 'react' +import type { Action, ActionHash } from '@lastres/action' + import PresentationItem from '@lastres/components/presentation-item' import Presentation from '@lastres/components/presentation' export interface BottomPanelProps { websocket: WebSocket | null + actionHash: ActionHash | null + } -export default function BottomPanel (): JSX.Element { +export interface Style { + background?: string +} + +export default function BottomPanel (props: BottomPanelProps): JSX.Element { + const actionHash = props.actionHash + function printListActions(): JSX.Element { + if (actionHash === null) { + return <> + } + const listOfActionKeys = Object.keys(actionHash).sort((a, b) => { + const isDisabledComparisionValue: number = +actionHash[a].is_disabled - +actionHash[b].is_disabled + if (isDisabledComparisionValue !== 0) { + return isDisabledComparisionValue + } + if (actionHash[a].name < actionHash[b].name) { + return -1 + } + if (actionHash[a].name > actionHash[b].name) { + return 1 + } + return 0 + }) + function printDisabledReason (action: Action): JSX.Element { + if (!action.is_disabled || action.disabled_reason === null) { + return <> + } + return ( +

{action.disabled_reason}

+ ) + } + return <> +

Acciones disponibles.

+ { + listOfActionKeys.map((key) => { + const style: Style = {} + const action = actionHash[key] + if (action.is_disabled) { + style.background = 'lightgray' + } + return
+

{action.name}

+ { + printDisabledReason(action) + } +
+ }) + } + + } return ( - - + { + printListActions() + } diff --git a/js-src/components/game.tsx b/js-src/components/game.tsx index 3d62ec4..37d6d54 100644 --- a/js-src/components/game.tsx +++ b/js-src/components/game.tsx @@ -3,6 +3,7 @@ import * as React from 'react' import type { PJ } from '@lastres/pj' import type { Location } from '@lastres/location' import type { LogLine } from '@lastres/log-line' +import type { ActionHash } from '@lastres/action' import UpperPanel from '@lastres/components/upper-panel' import BottomPanel from '@lastres/components/bottom-panel' @@ -47,6 +48,7 @@ export default function Game (props: GameProps): JSX.Element { const [scrollLog, setScrollLog] = React.useState(null) const [movingTo, setMovingTo] = React.useState(null) const [remainingFrames, setRemainingFrames] = React.useState(null) + const [actionHash, setActionHash] = React.useState(null) const logPresentationRef = React.useRef(null) const websocket = props.websocket const setWebsocket = props.setWebsocket @@ -79,7 +81,8 @@ export default function Game (props: GameProps): JSX.Element { setCurrentLocation, setConnectedLocations, logLines, setLogLines, setError, setScrollLog, logPresentationRef, - setMovingTo, setRemainingFrames) + setMovingTo, setRemainingFrames, + setActionHash) webSocket.onmessage = (event) => { const packet = JSON.parse(event.data) inputPackets.handle(packet) @@ -108,7 +111,7 @@ export default function Game (props: GameProps): JSX.Element { logPresentationRef={logPresentationRef} movingTo={movingTo} remainingFrames={remainingFrames}/> - + ) } diff --git a/js-src/input-packets.ts b/js-src/input-packets.ts index d1f4376..88283d8 100644 --- a/js-src/input-packets.ts +++ b/js-src/input-packets.ts @@ -2,6 +2,7 @@ import type { PJ } from '@lastres/pj' import type { Location } from '@lastres/location' import type InputPacket from '@lastres/input-packet' import type { LogLine } from '@lastres/log-line' +import type { ActionHash } from '@lastres/action' import InputPacketInfo from '@lastres/input-packet/info' import InputPacketPong from '@lastres/input-packet/pong' @@ -19,6 +20,7 @@ type SetScrollLog = (set: number | null | SetScrollLogCallback) => void type LogPresentationRef = React.RefObject type SetMovingTo = (set: Location | null) => void type SetRemainingFrames = (set: number | null) => void +type SetActionHash = (set: ActionHash | null) => void interface Packet { command: string @@ -41,6 +43,7 @@ export default class InputPackets { logPresentationRef: LogPresentationRef setMovingTo: SetMovingTo setRemainingFrames: SetRemainingFrames + setActionHash: SetActionHash constructor (setTeamPJs: SetTeamPJs, setEnemyTeamPJs: SetEnemyTeamPJs, setIsBattling: SetIsBattling, @@ -52,7 +55,8 @@ export default class InputPackets { setScrollLog: SetScrollLog, logPresentationRef: LogPresentationRef, setMovingTo: SetMovingTo, - setRemainingFrames: SetRemainingFrames) { + setRemainingFrames: SetRemainingFrames, + setActionHash: SetActionHash) { this.setTeamPJs = setTeamPJs this.setEnemyTeamPJs = setEnemyTeamPJs this.setCurrentLocation = setCurrentLocation @@ -65,6 +69,7 @@ export default class InputPackets { this.setMovingTo = setMovingTo this.setRemainingFrames = setRemainingFrames this.setIsBattling = setIsBattling + this.setActionHash = setActionHash } handle (packet: Packet): void { @@ -164,6 +169,9 @@ export default class InputPackets { applyScroll() }, 10) } + if (data.available_actions !== undefined) { + this.setActionHash(data.available_actions) + } }) this.cachedArray = [infoPacket, pongPacket] } diff --git a/las_tres.example.yml b/las_tres.example.yml index dc88674..607bd3c 100644 --- a/las_tres.example.yml +++ b/las_tres.example.yml @@ -1,4 +1,3 @@ ---- secrets: - change_me_for_a_proper_secret_generated_with_pwgen database: diff --git a/lib/LasTres/Controller/Websocket/InputPacket/Init.pm b/lib/LasTres/Controller/Websocket/InputPacket/Init.pm index ef6fe96..b28eedc 100644 --- a/lib/LasTres/Controller/Websocket/InputPacket/Init.pm +++ b/lib/LasTres/Controller/Websocket/InputPacket/Init.pm @@ -74,10 +74,11 @@ sub handle ( $self, $ws, $session, $data ) { LasTres::Controller::Websocket::OutputPacket::Info->new( set_log => [ $pj->last_50_log ], $self->_location_data($pj), - team_pjs => $team->combat_members_serializable($pj), + team_pjs => $team->combat_members_serializable($pj), is_battling => defined $team->battle, $self->_enemy_team_pjs($session), clear => $JSON::true, + $self->_available_actions($pj), ); $info_packet_to_send->send($ws); my $redis = LasTres::Redis->new; @@ -155,11 +156,20 @@ sub _on_redis_event ( $self, $ws, $session, $message, $topic, $topics ) { if ( $data->{command} eq 'update-team-sprites' ) { my $team = $pj->team; LasTres::Controller::Websocket::OutputPacket::Info->new( - team_pjs => $team->combat_members_serializable($pj), + team_pjs => $team->combat_members_serializable($pj), is_battling => defined $team->battle, $self->_enemy_team_pjs($session) )->send($ws); } + if ( $data->{command} eq 'update-actions' ) { + LasTres::Controller::Websocket::OutputPacket::Info->new( + $self->_available_actions($pj) )->send($ws); + } +} + +sub _available_actions ($self, $pj) { + return ( available_actions => + { map { $_->identifier => $_->hash($pj) } $pj->actions->@* }, ); } sub _get_connected_places ( $self, $pj ) { diff --git a/lib/LasTres/Controller/Websocket/OutputPacket/Info.pm b/lib/LasTres/Controller/Websocket/OutputPacket/Info.pm index 6b14bd1..bda8fda 100644 --- a/lib/LasTres/Controller/Websocket/OutputPacket/Info.pm +++ b/lib/LasTres/Controller/Websocket/OutputPacket/Info.pm @@ -25,21 +25,23 @@ has set_log => ( is => 'rw', ); has append_log => ( is => 'rw' ); has is_battling => ( is => 'rw' ); -has remaining_frames => ( is => 'rw' ); +has remaining_frames => ( is => 'rw' ); +has available_actions => ( is => 'rw' ); sub identifier { return 'info'; } sub data ($self) { - my $clear = $self->clear; - my $team_pjs = $self->team_pjs; - my $enemy_team_pjs = $self->enemy_team_pjs; - my $location_data = $self->location_data; - my $set_log = $self->set_log; - my $append_log = $self->append_log; - my $remaining_frames = $self->remaining_frames; - my $is_battling = $self->is_battling; + my $clear = $self->clear; + my $team_pjs = $self->team_pjs; + my $enemy_team_pjs = $self->enemy_team_pjs; + my $location_data = $self->location_data; + my $set_log = $self->set_log; + my $append_log = $self->append_log; + my $remaining_frames = $self->remaining_frames; + my $is_battling = $self->is_battling; + my $available_actions = $self->available_actions; if ( defined $is_battling ) { $is_battling = $is_battling ? $JSON::true : $JSON::false; @@ -74,7 +76,13 @@ sub data ($self) { ( defined $remaining_frames ) ? ( remaining_frames => $remaining_frames ) : () - ) + ), + ( + ( defined $available_actions ) + ? ( available_actions => $available_actions ) + : () + ), + }; } diff --git a/lib/LasTres/Location.pm b/lib/LasTres/Location.pm index 0f355f7..807d3d0 100644 --- a/lib/LasTres/Location.pm +++ b/lib/LasTres/Location.pm @@ -102,8 +102,8 @@ sub on_pj_arrival ( $self, $pj ) { $pj->set_known_location($self); } $self->show_intro($pj); - $redis->publish( $redis->pj_subscription($pj), - to_json( { command => 'update-location' } ) ); + $pj->update_location; + $pj->update_actions; } ## DO NOT EXTEND NOT SUPPORTED. @@ -168,8 +168,8 @@ sub on_pj_moving ( $self, $pj ) { { color => 'green', text => $self->name($pj) }, ] ); - $redis->publish( $redis->pj_subscription($pj), - to_json( { command => 'update-location' } ) ); + $pj->update_location; + $pj->update_actions; } ## DO NOT EXTEND NOT SUPPORTED. diff --git a/lib/LasTres/PJAction.pm b/lib/LasTres/PJAction.pm index f300421..cc3195f 100644 --- a/lib/LasTres/PJAction.pm +++ b/lib/LasTres/PJAction.pm @@ -6,19 +6,31 @@ use warnings; use feature 'signatures'; +use JSON qw/to_json from_json/; use Moo::Role; requires( 'identifier', 'callback', 'name' ); +## IMPLEMENTORS MUST IMPLEMENT +# +# sub callback($self, $pj); +# What to do when this action is invoked by the PJ. +# +# sub identifier; +# The unique identifier of this action in the game. +# +# sub name($self, $pj); +# The name of the action, possibly variable for diferent PJs. + ## OVERRIDE # This should be a square icon, ideally of 400x400px # If not set the frontend should attempt to show the # action with as much dignity as possible. # -# Should return nothing or a string that is a absolute url +# Should return undef or a string that is a absolute url # to the resource. sub icon ( $self, $pj ) { - return; + return undef; } ## OVERRIDE @@ -38,20 +50,20 @@ sub is_disabled ( $self, $pj ) { # the pj cannot use this action but they still see it as # something possible to be done. # -# Should return nothing if the pj can do the action and a string +# Should return undef if the pj can do the action and a string # with the reason otherwise. sub disabled_reason ( $self, $pj ) { - return; + return undef; } ## DO NOT EXTEND NOT SUPPORTED. sub hash ( $self, $pj ) { return { identifier => $self->identifier, - icon => $self->icon, - name => $self->name, - is_disabled => $self->is_disabled, - disabled_reason => $self->disabled_reason, + icon => $self->icon($pj), + name => $self->name($pj), + is_disabled => $self->is_disabled($pj) ? $JSON::true : $JSON::false, + disabled_reason => $self->disabled_reason($pj), }; } 1; diff --git a/lib/LasTres/PJAction/DefaultHeal.pm b/lib/LasTres/PJAction/DefaultHeal.pm index 4003d05..b88f447 100644 --- a/lib/LasTres/PJAction/DefaultHeal.pm +++ b/lib/LasTres/PJAction/DefaultHeal.pm @@ -4,6 +4,7 @@ use v5.36.0; use strict; use warnings; +use utf8; use Moo; @@ -40,7 +41,7 @@ sub disabled_reason($self, $pj) { if (!$self->_check_if_someone_needs_to_restore($pj)) { return 'Todo tu equipo esta lleno de vitalidad y listo para la aventura.'; } - return; + return undef; } sub _check_if_someone_needs_to_restore($self, $pj) { diff --git a/lib/LasTres/PJAction/GolpearArbolCentralTribuDeLaLima.pm b/lib/LasTres/PJAction/GolpearArbolCentralTribuDeLaLima.pm index d7879b5..2aed226 100644 --- a/lib/LasTres/PJAction/GolpearArbolCentralTribuDeLaLima.pm +++ b/lib/LasTres/PJAction/GolpearArbolCentralTribuDeLaLima.pm @@ -1,8 +1,9 @@ -package LasTres::Action::GolpearArbolCentralTribuDeLaLima; +package LasTres::PJAction::GolpearArbolCentralTribuDeLaLima; use v5.36.0; use strict; use warnings; +use utf8; use feature 'signatures'; @@ -31,7 +32,7 @@ sub disabled_reason ( $self, $pj ) { if ($pj->health < 1) { return "Estás debilitado."; } - return; + return undef; } sub _chance_enemy ( $self, $pj ) { diff --git a/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm b/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm index 5be8f58..cd9c0b2 100644 --- a/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm +++ b/lib/LasTres/Planet/Bahdder/BosqueDelHeroe/TribuDeLaLima/ArbolCentral.pm @@ -12,6 +12,7 @@ use utf8; use Moo; use LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima; +use LasTres::PJAction::GolpearArbolCentralTribuDeLaLima; with 'LasTres::Location'; @@ -32,7 +33,9 @@ sub parent { } sub actions { - return []; + return [ + LasTres::PJAction::GolpearArbolCentralTribuDeLaLima->new + ]; } sub npcs { diff --git a/lib/LasTres/Redis.pm b/lib/LasTres/Redis.pm index f770f37..49299f2 100644 --- a/lib/LasTres/Redis.pm +++ b/lib/LasTres/Redis.pm @@ -13,8 +13,8 @@ use LasTres::Schema; our $VERSION = $LasTres::Schema::VERSION; { + my $self; sub new { - my $self; my $class = shift; if (!defined $self) { $self = $class->SUPER::new(@_); diff --git a/lib/LasTres/Schema/Result/PJ.pm b/lib/LasTres/Schema/Result/PJ.pm index dd95842..180c4ba 100644 --- a/lib/LasTres/Schema/Result/PJ.pm +++ b/lib/LasTres/Schema/Result/PJ.pm @@ -470,6 +470,13 @@ sub update_location ($self) { to_json( { command => 'update-location' } ) ); } +sub update_actions($self) { + require LasTres::Redis; + my $redis = LasTres::Redis->new; + $redis->publish( $redis->pj_subscription($self), + to_json( { command => 'update-actions' } ) ); +} + sub actions ($self) { my @actions; $self = $self->get_from_storage; @@ -477,13 +484,13 @@ sub actions ($self) { my $location = $team->location; if ( defined $team->battle ) { - # There should go the battle actions. - return; + # Here should go the battle actions. + return []; } if ( $team->is_moving ) { # Probably there should go the actions still doable when moving. - return; + return []; } # TODO: Handle explore when implemented. diff --git a/public/css/styles.css b/public/css/styles.css index 1f85848..05cc650 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -9,7 +9,7 @@ body { margin: 0px; padding: 0px; - min-height: 100%; + height: 100vh; background: ghostwhite; } body label.bar-container { width: 90%; } @@ -20,6 +20,18 @@ body { body label.bar-container div.bar div.filled { background: lightgreen; height: 100%; } + body div.action { + border: solid 1px black; + text-decoration: underline; + display: flex; + min-height: 100px; + align-content: center; + justify-content: center; + align-items: center; + flex-direction: column; + width: 100%; } + body div.action div.disabled-reason { + text-decoration: none; } body div.pj-list-item { display: flex; align-items: center; } @@ -64,7 +76,7 @@ body { body div.width-max-content { width: max-content; } body div#game-container { - min-height: 100%; } + height: 100vh; } body div#game-container nav.menu-bar { width: 100%; background: grey; diff --git a/public/css/styles.scss b/public/css/styles.scss index 0305d2f..0c53485 100644 --- a/public/css/styles.scss +++ b/public/css/styles.scss @@ -10,6 +10,10 @@ } } body { + margin: 0px; + padding: 0px; + height: 100vh; + background: ghostwhite; label.bar-container { width: 90%; div.bar { @@ -22,6 +26,20 @@ body { } } } + div.action { + border: solid 1px black; + text-decoration: underline; + display: flex; + min-height: 100px; + align-content: center; + justify-content: center; + align-items: center; + flex-direction: column; + width: 100%; + div.disabled-reason { + text-decoration: none; + } + } div.pj-list-item { display: flex; align-items: center; @@ -75,15 +93,11 @@ body { height: 50vh; } - margin: 0px; - padding: 0px; - min-height: 100%; - background: ghostwhite; div.width-max-content { width: max-content; } div#game-container { - min-height: 100%; + height: 100vh; nav.menu-bar { width: 100%; background: grey; diff --git a/public/js/bundle.js b/public/js/bundle.js index 5945942..e4c924d 100644 --- a/public/js/bundle.js +++ b/public/js/bundle.js @@ -86,7 +86,7 @@ eval("\n\nif (false) {} else {\n module.exports = __webpack_require__(/*! ./cjs \********************************************/ /***/ ((__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 */ BottomPanel)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/presentation-item */ \"./js-src/components/presentation-item.tsx\");\n/* harmony import */ var _lastres_components_presentation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @lastres/components/presentation */ \"./js-src/components/presentation.tsx\");\n\n\n\nfunction BottomPanel() {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null)));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/bottom-panel.tsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ BottomPanel)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/presentation-item */ \"./js-src/components/presentation-item.tsx\");\n/* harmony import */ var _lastres_components_presentation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @lastres/components/presentation */ \"./js-src/components/presentation.tsx\");\n\n\n\nfunction BottomPanel(props) {\n const actionHash = props.actionHash;\n function printListActions() {\n if (actionHash === null) {\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n const listOfActionKeys = Object.keys(actionHash).sort((a, b) => {\n const isDisabledComparisionValue = +actionHash[a].is_disabled - +actionHash[b].is_disabled;\n if (isDisabledComparisionValue !== 0) {\n return isDisabledComparisionValue;\n }\n if (actionHash[a].name < actionHash[b].name) {\n return -1;\n }\n if (actionHash[a].name > actionHash[b].name) {\n return 1;\n }\n return 0;\n });\n function printDisabledReason(action) {\n if (!action.is_disabled || action.disabled_reason === null) {\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", { className: \"disabled-reason\", style: { color: 'red' } }, action.disabled_reason));\n }\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", null, \"Acciones disponibles.\"),\n listOfActionKeys.map((key) => {\n const style = {};\n const action = actionHash[key];\n if (action.is_disabled) {\n style.background = 'lightgray';\n }\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"action\", style: style, key: action.identifier },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", null, action.name),\n printDisabledReason(action));\n }));\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null, printListActions()),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_presentation_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null)));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/bottom-panel.tsx?"); /***/ }), @@ -96,7 +96,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 */ Game)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/upper-panel */ \"./js-src/components/upper-panel.tsx\");\n/* harmony import */ var _lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @lastres/components/bottom-panel */ \"./js-src/components/bottom-panel.tsx\");\n/* harmony import */ var _lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @lastres/components/pj-selection-menu */ \"./js-src/components/pj-selection-menu.tsx\");\n/* harmony import */ var _lastres_output_packet_init__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @lastres/output-packet/init */ \"./js-src/output-packet/init.ts\");\n/* harmony import */ var _lastres_output_packet_ping__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @lastres/output-packet/ping */ \"./js-src/output-packet/ping.ts\");\n/* harmony import */ var _lastres_input_packets__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @lastres/input-packets */ \"./js-src/input-packets.ts\");\n\n\n\n\n\n\n\nfunction Game(props) {\n const selectedPJ = props.selectedPJ;\n if (selectedPJ === null) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__[\"default\"], { setSelectedPJ: props.setSelectedPJ, userWantsToCreatePJ: props.userWantsToCreatePJ, setUserWantsToCreatePJ: props.setUserWantsToCreatePJ, error: props.error, setError: props.setError })));\n }\n const [teamPJs, setTeamPJs] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [enemyTeamPJs, setEnemyTeamPJs] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [isBattling, setIsBattling] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [currentLocation, setCurrentLocation] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [connectedLocations, setConnectedLocations] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [logLines, setLogLines] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [error, setError] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [scrollLog, setScrollLog] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [movingTo, setMovingTo] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [remainingFrames, setRemainingFrames] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const logPresentationRef = react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n const websocket = props.websocket;\n const setWebsocket = props.setWebsocket;\n window.setTimeout(() => {\n setWebsocket((websocket) => {\n if (websocket === null) {\n console.log('Opening websocket');\n const locationProtocol = window.location.protocol;\n if (locationProtocol == null) {\n return null;\n }\n const protocol = locationProtocol.match(/https:/) != null ? 'wss' : 'ws';\n const webSocket = new WebSocket(`${protocol}://${window.location.host}/ws`);\n webSocket.onopen = () => {\n new _lastres_output_packet_init__WEBPACK_IMPORTED_MODULE_4__[\"default\"](selectedPJ.uuid).send(webSocket);\n let interval = 0;\n interval = window.setInterval(() => {\n if (webSocket.readyState === WebSocket.CONNECTING) {\n return;\n }\n if (webSocket.readyState === WebSocket.OPEN) {\n new _lastres_output_packet_ping__WEBPACK_IMPORTED_MODULE_5__[\"default\"]().send(webSocket);\n return;\n }\n window.clearInterval(interval);\n }, 1000);\n };\n const inputPackets = new _lastres_input_packets__WEBPACK_IMPORTED_MODULE_6__[\"default\"](setTeamPJs, setEnemyTeamPJs, setIsBattling, setCurrentLocation, setConnectedLocations, logLines, setLogLines, setError, setScrollLog, logPresentationRef, setMovingTo, setRemainingFrames);\n webSocket.onmessage = (event) => {\n const packet = JSON.parse(event.data);\n inputPackets.handle(packet);\n };\n webSocket.onerror = (event) => {\n console.log(event);\n };\n webSocket.onclose = (event) => {\n console.log('Websocket closed');\n setWebsocket(null);\n };\n return webSocket;\n }\n return websocket;\n });\n }, 300);\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { teamPJs: teamPJs, enemyTeamPJs: enemyTeamPJs, isBattling: isBattling, currentLocation: currentLocation, connectedLocations: connectedLocations, logLines: logLines, websocket: websocket, logPresentationRef: logPresentationRef, movingTo: movingTo, remainingFrames: remainingFrames }),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null)));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/game.tsx?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Game)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/upper-panel */ \"./js-src/components/upper-panel.tsx\");\n/* harmony import */ var _lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @lastres/components/bottom-panel */ \"./js-src/components/bottom-panel.tsx\");\n/* harmony import */ var _lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @lastres/components/pj-selection-menu */ \"./js-src/components/pj-selection-menu.tsx\");\n/* harmony import */ var _lastres_output_packet_init__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @lastres/output-packet/init */ \"./js-src/output-packet/init.ts\");\n/* harmony import */ var _lastres_output_packet_ping__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @lastres/output-packet/ping */ \"./js-src/output-packet/ping.ts\");\n/* harmony import */ var _lastres_input_packets__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @lastres/input-packets */ \"./js-src/input-packets.ts\");\n\n\n\n\n\n\n\nfunction Game(props) {\n const selectedPJ = props.selectedPJ;\n if (selectedPJ === null) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__[\"default\"], { setSelectedPJ: props.setSelectedPJ, userWantsToCreatePJ: props.userWantsToCreatePJ, setUserWantsToCreatePJ: props.setUserWantsToCreatePJ, error: props.error, setError: props.setError })));\n }\n const [teamPJs, setTeamPJs] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [enemyTeamPJs, setEnemyTeamPJs] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [isBattling, setIsBattling] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [currentLocation, setCurrentLocation] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [connectedLocations, setConnectedLocations] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [logLines, setLogLines] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [error, setError] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [scrollLog, setScrollLog] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [movingTo, setMovingTo] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [remainingFrames, setRemainingFrames] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [actionHash, setActionHash] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const logPresentationRef = react__WEBPACK_IMPORTED_MODULE_0__.useRef(null);\n const websocket = props.websocket;\n const setWebsocket = props.setWebsocket;\n window.setTimeout(() => {\n setWebsocket((websocket) => {\n if (websocket === null) {\n console.log('Opening websocket');\n const locationProtocol = window.location.protocol;\n if (locationProtocol == null) {\n return null;\n }\n const protocol = locationProtocol.match(/https:/) != null ? 'wss' : 'ws';\n const webSocket = new WebSocket(`${protocol}://${window.location.host}/ws`);\n webSocket.onopen = () => {\n new _lastres_output_packet_init__WEBPACK_IMPORTED_MODULE_4__[\"default\"](selectedPJ.uuid).send(webSocket);\n let interval = 0;\n interval = window.setInterval(() => {\n if (webSocket.readyState === WebSocket.CONNECTING) {\n return;\n }\n if (webSocket.readyState === WebSocket.OPEN) {\n new _lastres_output_packet_ping__WEBPACK_IMPORTED_MODULE_5__[\"default\"]().send(webSocket);\n return;\n }\n window.clearInterval(interval);\n }, 1000);\n };\n const inputPackets = new _lastres_input_packets__WEBPACK_IMPORTED_MODULE_6__[\"default\"](setTeamPJs, setEnemyTeamPJs, setIsBattling, setCurrentLocation, setConnectedLocations, logLines, setLogLines, setError, setScrollLog, logPresentationRef, setMovingTo, setRemainingFrames, setActionHash);\n webSocket.onmessage = (event) => {\n const packet = JSON.parse(event.data);\n inputPackets.handle(packet);\n };\n webSocket.onerror = (event) => {\n console.log(event);\n };\n webSocket.onclose = (event) => {\n console.log('Websocket closed');\n setWebsocket(null);\n };\n return webSocket;\n }\n return websocket;\n });\n }, 300);\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { teamPJs: teamPJs, enemyTeamPJs: enemyTeamPJs, isBattling: isBattling, currentLocation: currentLocation, connectedLocations: connectedLocations, logLines: logLines, websocket: websocket, logPresentationRef: logPresentationRef, movingTo: movingTo, remainingFrames: remainingFrames }),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__[\"default\"], { actionHash: actionHash, websocket: websocket })));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/game.tsx?"); /***/ }), @@ -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) {\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 }\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 });\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 = [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?"); /***/ }),