Adding initial actions and some documentation.
This commit is contained in:
parent
f8020241cb
commit
01a098d926
289
README.md
289
README.md
|
@ -1,2 +1,291 @@
|
||||||
# LasTres
|
# 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
|
||||||
|
```
|
||||||
|
|
|
@ -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<string, Action>
|
|
@ -1,18 +1,72 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import type { Action, ActionHash } from '@lastres/action'
|
||||||
|
|
||||||
import PresentationItem from '@lastres/components/presentation-item'
|
import PresentationItem from '@lastres/components/presentation-item'
|
||||||
import Presentation from '@lastres/components/presentation'
|
import Presentation from '@lastres/components/presentation'
|
||||||
|
|
||||||
export interface BottomPanelProps {
|
export interface BottomPanelProps {
|
||||||
websocket: WebSocket | null
|
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 (
|
||||||
|
<p className="disabled-reason" style={{ color: 'red' }}>{action.disabled_reason}</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
<p>Acciones disponibles.</p>
|
||||||
|
{
|
||||||
|
listOfActionKeys.map((key) => {
|
||||||
|
const style: Style = {}
|
||||||
|
const action = actionHash[key]
|
||||||
|
if (action.is_disabled) {
|
||||||
|
style.background = 'lightgray'
|
||||||
|
}
|
||||||
|
return <div className="action" style={style} key={action.identifier}>
|
||||||
|
<p>{action.name}</p>
|
||||||
|
{
|
||||||
|
printDisabledReason(action)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Presentation>
|
<Presentation>
|
||||||
<PresentationItem>
|
<PresentationItem>
|
||||||
</PresentationItem>
|
{
|
||||||
<PresentationItem>
|
printListActions()
|
||||||
|
}
|
||||||
</PresentationItem>
|
</PresentationItem>
|
||||||
<PresentationItem>
|
<PresentationItem>
|
||||||
</PresentationItem>
|
</PresentationItem>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react'
|
||||||
import type { PJ } from '@lastres/pj'
|
import type { PJ } from '@lastres/pj'
|
||||||
import type { Location } from '@lastres/location'
|
import type { Location } from '@lastres/location'
|
||||||
import type { LogLine } from '@lastres/log-line'
|
import type { LogLine } from '@lastres/log-line'
|
||||||
|
import type { ActionHash } from '@lastres/action'
|
||||||
|
|
||||||
import UpperPanel from '@lastres/components/upper-panel'
|
import UpperPanel from '@lastres/components/upper-panel'
|
||||||
import BottomPanel from '@lastres/components/bottom-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<number | null>(null)
|
const [scrollLog, setScrollLog] = React.useState<number | null>(null)
|
||||||
const [movingTo, setMovingTo] = React.useState<Location | null>(null)
|
const [movingTo, setMovingTo] = React.useState<Location | null>(null)
|
||||||
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 logPresentationRef = React.useRef<HTMLDivElement>(null)
|
const logPresentationRef = React.useRef<HTMLDivElement>(null)
|
||||||
const websocket = props.websocket
|
const websocket = props.websocket
|
||||||
const setWebsocket = props.setWebsocket
|
const setWebsocket = props.setWebsocket
|
||||||
|
@ -79,7 +81,8 @@ export default function Game (props: GameProps): JSX.Element {
|
||||||
setCurrentLocation, setConnectedLocations,
|
setCurrentLocation, setConnectedLocations,
|
||||||
logLines, setLogLines, setError,
|
logLines, setLogLines, setError,
|
||||||
setScrollLog, logPresentationRef,
|
setScrollLog, logPresentationRef,
|
||||||
setMovingTo, setRemainingFrames)
|
setMovingTo, setRemainingFrames,
|
||||||
|
setActionHash)
|
||||||
webSocket.onmessage = (event) => {
|
webSocket.onmessage = (event) => {
|
||||||
const packet = JSON.parse(event.data)
|
const packet = JSON.parse(event.data)
|
||||||
inputPackets.handle(packet)
|
inputPackets.handle(packet)
|
||||||
|
@ -108,7 +111,7 @@ export default function Game (props: GameProps): JSX.Element {
|
||||||
logPresentationRef={logPresentationRef}
|
logPresentationRef={logPresentationRef}
|
||||||
movingTo={movingTo}
|
movingTo={movingTo}
|
||||||
remainingFrames={remainingFrames}/>
|
remainingFrames={remainingFrames}/>
|
||||||
<BottomPanel/>
|
<BottomPanel actionHash={actionHash} websocket={websocket}/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { PJ } from '@lastres/pj'
|
||||||
import type { Location } from '@lastres/location'
|
import type { Location } from '@lastres/location'
|
||||||
import type InputPacket from '@lastres/input-packet'
|
import type InputPacket from '@lastres/input-packet'
|
||||||
import type { LogLine } from '@lastres/log-line'
|
import type { LogLine } from '@lastres/log-line'
|
||||||
|
import type { ActionHash } from '@lastres/action'
|
||||||
import InputPacketInfo from '@lastres/input-packet/info'
|
import InputPacketInfo from '@lastres/input-packet/info'
|
||||||
import InputPacketPong from '@lastres/input-packet/pong'
|
import InputPacketPong from '@lastres/input-packet/pong'
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ type SetScrollLog = (set: number | null | SetScrollLogCallback) => void
|
||||||
type LogPresentationRef = React.RefObject<HTMLDivElement>
|
type LogPresentationRef = React.RefObject<HTMLDivElement>
|
||||||
type SetMovingTo = (set: Location | null) => void
|
type SetMovingTo = (set: Location | null) => void
|
||||||
type SetRemainingFrames = (set: number | null) => void
|
type SetRemainingFrames = (set: number | null) => void
|
||||||
|
type SetActionHash = (set: ActionHash | null) => void
|
||||||
|
|
||||||
interface Packet {
|
interface Packet {
|
||||||
command: string
|
command: string
|
||||||
|
@ -41,6 +43,7 @@ export default class InputPackets {
|
||||||
logPresentationRef: LogPresentationRef
|
logPresentationRef: LogPresentationRef
|
||||||
setMovingTo: SetMovingTo
|
setMovingTo: SetMovingTo
|
||||||
setRemainingFrames: SetRemainingFrames
|
setRemainingFrames: SetRemainingFrames
|
||||||
|
setActionHash: SetActionHash
|
||||||
constructor (setTeamPJs: SetTeamPJs,
|
constructor (setTeamPJs: SetTeamPJs,
|
||||||
setEnemyTeamPJs: SetEnemyTeamPJs,
|
setEnemyTeamPJs: SetEnemyTeamPJs,
|
||||||
setIsBattling: SetIsBattling,
|
setIsBattling: SetIsBattling,
|
||||||
|
@ -52,7 +55,8 @@ export default class InputPackets {
|
||||||
setScrollLog: SetScrollLog,
|
setScrollLog: SetScrollLog,
|
||||||
logPresentationRef: LogPresentationRef,
|
logPresentationRef: LogPresentationRef,
|
||||||
setMovingTo: SetMovingTo,
|
setMovingTo: SetMovingTo,
|
||||||
setRemainingFrames: SetRemainingFrames) {
|
setRemainingFrames: SetRemainingFrames,
|
||||||
|
setActionHash: SetActionHash) {
|
||||||
this.setTeamPJs = setTeamPJs
|
this.setTeamPJs = setTeamPJs
|
||||||
this.setEnemyTeamPJs = setEnemyTeamPJs
|
this.setEnemyTeamPJs = setEnemyTeamPJs
|
||||||
this.setCurrentLocation = setCurrentLocation
|
this.setCurrentLocation = setCurrentLocation
|
||||||
|
@ -65,6 +69,7 @@ export default class InputPackets {
|
||||||
this.setMovingTo = setMovingTo
|
this.setMovingTo = setMovingTo
|
||||||
this.setRemainingFrames = setRemainingFrames
|
this.setRemainingFrames = setRemainingFrames
|
||||||
this.setIsBattling = setIsBattling
|
this.setIsBattling = setIsBattling
|
||||||
|
this.setActionHash = setActionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
handle (packet: Packet): void {
|
handle (packet: Packet): void {
|
||||||
|
@ -164,6 +169,9 @@ export default class InputPackets {
|
||||||
applyScroll()
|
applyScroll()
|
||||||
}, 10)
|
}, 10)
|
||||||
}
|
}
|
||||||
|
if (data.available_actions !== undefined) {
|
||||||
|
this.setActionHash(data.available_actions)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
this.cachedArray = [infoPacket, pongPacket]
|
this.cachedArray = [infoPacket, pongPacket]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
---
|
|
||||||
secrets:
|
secrets:
|
||||||
- change_me_for_a_proper_secret_generated_with_pwgen
|
- change_me_for_a_proper_secret_generated_with_pwgen
|
||||||
database:
|
database:
|
||||||
|
|
|
@ -74,10 +74,11 @@ sub handle ( $self, $ws, $session, $data ) {
|
||||||
LasTres::Controller::Websocket::OutputPacket::Info->new(
|
LasTres::Controller::Websocket::OutputPacket::Info->new(
|
||||||
set_log => [ $pj->last_50_log ],
|
set_log => [ $pj->last_50_log ],
|
||||||
$self->_location_data($pj),
|
$self->_location_data($pj),
|
||||||
team_pjs => $team->combat_members_serializable($pj),
|
team_pjs => $team->combat_members_serializable($pj),
|
||||||
is_battling => defined $team->battle,
|
is_battling => defined $team->battle,
|
||||||
$self->_enemy_team_pjs($session),
|
$self->_enemy_team_pjs($session),
|
||||||
clear => $JSON::true,
|
clear => $JSON::true,
|
||||||
|
$self->_available_actions($pj),
|
||||||
);
|
);
|
||||||
$info_packet_to_send->send($ws);
|
$info_packet_to_send->send($ws);
|
||||||
my $redis = LasTres::Redis->new;
|
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' ) {
|
if ( $data->{command} eq 'update-team-sprites' ) {
|
||||||
my $team = $pj->team;
|
my $team = $pj->team;
|
||||||
LasTres::Controller::Websocket::OutputPacket::Info->new(
|
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,
|
is_battling => defined $team->battle,
|
||||||
$self->_enemy_team_pjs($session)
|
$self->_enemy_team_pjs($session)
|
||||||
)->send($ws);
|
)->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 ) {
|
sub _get_connected_places ( $self, $pj ) {
|
||||||
|
|
|
@ -25,21 +25,23 @@ has set_log => ( is => 'rw', );
|
||||||
has append_log => ( is => 'rw' );
|
has append_log => ( is => 'rw' );
|
||||||
has is_battling => ( is => 'rw' );
|
has is_battling => ( is => 'rw' );
|
||||||
|
|
||||||
has remaining_frames => ( is => 'rw' );
|
has remaining_frames => ( is => 'rw' );
|
||||||
|
has available_actions => ( is => 'rw' );
|
||||||
|
|
||||||
sub identifier {
|
sub identifier {
|
||||||
return 'info';
|
return 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
sub data ($self) {
|
sub data ($self) {
|
||||||
my $clear = $self->clear;
|
my $clear = $self->clear;
|
||||||
my $team_pjs = $self->team_pjs;
|
my $team_pjs = $self->team_pjs;
|
||||||
my $enemy_team_pjs = $self->enemy_team_pjs;
|
my $enemy_team_pjs = $self->enemy_team_pjs;
|
||||||
my $location_data = $self->location_data;
|
my $location_data = $self->location_data;
|
||||||
my $set_log = $self->set_log;
|
my $set_log = $self->set_log;
|
||||||
my $append_log = $self->append_log;
|
my $append_log = $self->append_log;
|
||||||
my $remaining_frames = $self->remaining_frames;
|
my $remaining_frames = $self->remaining_frames;
|
||||||
my $is_battling = $self->is_battling;
|
my $is_battling = $self->is_battling;
|
||||||
|
my $available_actions = $self->available_actions;
|
||||||
|
|
||||||
if ( defined $is_battling ) {
|
if ( defined $is_battling ) {
|
||||||
$is_battling = $is_battling ? $JSON::true : $JSON::false;
|
$is_battling = $is_battling ? $JSON::true : $JSON::false;
|
||||||
|
@ -74,7 +76,13 @@ sub data ($self) {
|
||||||
( defined $remaining_frames )
|
( defined $remaining_frames )
|
||||||
? ( remaining_frames => $remaining_frames )
|
? ( remaining_frames => $remaining_frames )
|
||||||
: ()
|
: ()
|
||||||
)
|
),
|
||||||
|
(
|
||||||
|
( defined $available_actions )
|
||||||
|
? ( available_actions => $available_actions )
|
||||||
|
: ()
|
||||||
|
),
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,8 +102,8 @@ sub on_pj_arrival ( $self, $pj ) {
|
||||||
$pj->set_known_location($self);
|
$pj->set_known_location($self);
|
||||||
}
|
}
|
||||||
$self->show_intro($pj);
|
$self->show_intro($pj);
|
||||||
$redis->publish( $redis->pj_subscription($pj),
|
$pj->update_location;
|
||||||
to_json( { command => 'update-location' } ) );
|
$pj->update_actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
## DO NOT EXTEND NOT SUPPORTED.
|
## DO NOT EXTEND NOT SUPPORTED.
|
||||||
|
@ -168,8 +168,8 @@ sub on_pj_moving ( $self, $pj ) {
|
||||||
{ color => 'green', text => $self->name($pj) },
|
{ color => 'green', text => $self->name($pj) },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$redis->publish( $redis->pj_subscription($pj),
|
$pj->update_location;
|
||||||
to_json( { command => 'update-location' } ) );
|
$pj->update_actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
## DO NOT EXTEND NOT SUPPORTED.
|
## DO NOT EXTEND NOT SUPPORTED.
|
||||||
|
|
|
@ -6,19 +6,31 @@ use warnings;
|
||||||
|
|
||||||
use feature 'signatures';
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use JSON qw/to_json from_json/;
|
||||||
use Moo::Role;
|
use Moo::Role;
|
||||||
|
|
||||||
requires( 'identifier', 'callback', 'name' );
|
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
|
## OVERRIDE
|
||||||
# This should be a square icon, ideally of 400x400px
|
# This should be a square icon, ideally of 400x400px
|
||||||
# If not set the frontend should attempt to show the
|
# If not set the frontend should attempt to show the
|
||||||
# action with as much dignity as possible.
|
# 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.
|
# to the resource.
|
||||||
sub icon ( $self, $pj ) {
|
sub icon ( $self, $pj ) {
|
||||||
return;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
## OVERRIDE
|
## OVERRIDE
|
||||||
|
@ -38,20 +50,20 @@ sub is_disabled ( $self, $pj ) {
|
||||||
# the pj cannot use this action but they still see it as
|
# the pj cannot use this action but they still see it as
|
||||||
# something possible to be done.
|
# 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.
|
# with the reason otherwise.
|
||||||
sub disabled_reason ( $self, $pj ) {
|
sub disabled_reason ( $self, $pj ) {
|
||||||
return;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
## DO NOT EXTEND NOT SUPPORTED.
|
## DO NOT EXTEND NOT SUPPORTED.
|
||||||
sub hash ( $self, $pj ) {
|
sub hash ( $self, $pj ) {
|
||||||
return {
|
return {
|
||||||
identifier => $self->identifier,
|
identifier => $self->identifier,
|
||||||
icon => $self->icon,
|
icon => $self->icon($pj),
|
||||||
name => $self->name,
|
name => $self->name($pj),
|
||||||
is_disabled => $self->is_disabled,
|
is_disabled => $self->is_disabled($pj) ? $JSON::true : $JSON::false,
|
||||||
disabled_reason => $self->disabled_reason,
|
disabled_reason => $self->disabled_reason($pj),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -4,6 +4,7 @@ use v5.36.0;
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
use utf8;
|
||||||
|
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ sub disabled_reason($self, $pj) {
|
||||||
if (!$self->_check_if_someone_needs_to_restore($pj)) {
|
if (!$self->_check_if_someone_needs_to_restore($pj)) {
|
||||||
return 'Todo tu equipo esta lleno de vitalidad y listo para la aventura.';
|
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) {
|
sub _check_if_someone_needs_to_restore($self, $pj) {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package LasTres::Action::GolpearArbolCentralTribuDeLaLima;
|
package LasTres::PJAction::GolpearArbolCentralTribuDeLaLima;
|
||||||
|
|
||||||
use v5.36.0;
|
use v5.36.0;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
use utf8;
|
||||||
|
|
||||||
use feature 'signatures';
|
use feature 'signatures';
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ sub disabled_reason ( $self, $pj ) {
|
||||||
if ($pj->health < 1) {
|
if ($pj->health < 1) {
|
||||||
return "Estás debilitado.";
|
return "Estás debilitado.";
|
||||||
}
|
}
|
||||||
return;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _chance_enemy ( $self, $pj ) {
|
sub _chance_enemy ( $self, $pj ) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ use utf8;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
use LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima;
|
use LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima;
|
||||||
|
use LasTres::PJAction::GolpearArbolCentralTribuDeLaLima;
|
||||||
|
|
||||||
with 'LasTres::Location';
|
with 'LasTres::Location';
|
||||||
|
|
||||||
|
@ -32,7 +33,9 @@ sub parent {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub actions {
|
sub actions {
|
||||||
return [];
|
return [
|
||||||
|
LasTres::PJAction::GolpearArbolCentralTribuDeLaLima->new
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
sub npcs {
|
sub npcs {
|
||||||
|
|
|
@ -13,8 +13,8 @@ use LasTres::Schema;
|
||||||
our $VERSION = $LasTres::Schema::VERSION;
|
our $VERSION = $LasTres::Schema::VERSION;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
my $self;
|
||||||
sub new {
|
sub new {
|
||||||
my $self;
|
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
if (!defined $self) {
|
if (!defined $self) {
|
||||||
$self = $class->SUPER::new(@_);
|
$self = $class->SUPER::new(@_);
|
||||||
|
|
|
@ -470,6 +470,13 @@ sub update_location ($self) {
|
||||||
to_json( { command => 'update-location' } ) );
|
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) {
|
sub actions ($self) {
|
||||||
my @actions;
|
my @actions;
|
||||||
$self = $self->get_from_storage;
|
$self = $self->get_from_storage;
|
||||||
|
@ -477,13 +484,13 @@ sub actions ($self) {
|
||||||
my $location = $team->location;
|
my $location = $team->location;
|
||||||
if ( defined $team->battle ) {
|
if ( defined $team->battle ) {
|
||||||
|
|
||||||
# There should go the battle actions.
|
# Here should go the battle actions.
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
if ( $team->is_moving ) {
|
if ( $team->is_moving ) {
|
||||||
|
|
||||||
# Probably there should go the actions still doable when moving.
|
# Probably there should go the actions still doable when moving.
|
||||||
return;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Handle explore when implemented.
|
# TODO: Handle explore when implemented.
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
min-height: 100%;
|
height: 100vh;
|
||||||
background: ghostwhite; }
|
background: ghostwhite; }
|
||||||
body label.bar-container {
|
body label.bar-container {
|
||||||
width: 90%; }
|
width: 90%; }
|
||||||
|
@ -20,6 +20,18 @@ body {
|
||||||
body label.bar-container div.bar div.filled {
|
body label.bar-container div.bar div.filled {
|
||||||
background: lightgreen;
|
background: lightgreen;
|
||||||
height: 100%; }
|
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 {
|
body div.pj-list-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center; }
|
align-items: center; }
|
||||||
|
@ -64,7 +76,7 @@ body {
|
||||||
body div.width-max-content {
|
body div.width-max-content {
|
||||||
width: max-content; }
|
width: max-content; }
|
||||||
body div#game-container {
|
body div#game-container {
|
||||||
min-height: 100%; }
|
height: 100vh; }
|
||||||
body div#game-container nav.menu-bar {
|
body div#game-container nav.menu-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: grey;
|
background: grey;
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
height: 100vh;
|
||||||
|
background: ghostwhite;
|
||||||
label.bar-container {
|
label.bar-container {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
div.bar {
|
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 {
|
div.pj-list-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -75,15 +93,11 @@ body {
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
min-height: 100%;
|
|
||||||
background: ghostwhite;
|
|
||||||
div.width-max-content {
|
div.width-max-content {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
div#game-container {
|
div#game-container {
|
||||||
min-height: 100%;
|
height: 100vh;
|
||||||
nav.menu-bar {
|
nav.menu-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: grey;
|
background: grey;
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue