mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
viewer fixes
This commit is contained in:
parent
5910b2f35a
commit
e94f0474ef
9 changed files with 125 additions and 100 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -1879,6 +1879,16 @@ version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -4666,7 +4676,13 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-range-header",
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -4781,6 +4797,15 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ maud = { version = "0.25.0", features = ["axum"] }
|
||||||
axum = { version = "0.6.12", features = ["headers", "macros"] }
|
axum = { version = "0.6.12", features = ["headers", "macros"] }
|
||||||
hyper = "0.14.25"
|
hyper = "0.14.25"
|
||||||
hyperlocal = "0.8.0"
|
hyperlocal = "0.8.0"
|
||||||
tower-http = { version = "0.4.0", features = ["trace"] }
|
tower-http = { version = "0.4.0", features = ["trace", "fs"] }
|
||||||
steamid-ng = "1.0.0"
|
steamid-ng = "1.0.0"
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
const-fnv1a-hash = "1.1.0"
|
const-fnv1a-hash = "1.1.0"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {Header, WorldBoundaries} from "./Data/Parser";
|
||||||
|
|
||||||
import {AsyncParser} from "./Data/AsyncParser";
|
import {AsyncParser} from "./Data/AsyncParser";
|
||||||
import {getMapBoundaries} from "./MapBoundries";
|
import {getMapBoundaries} from "./MapBoundries";
|
||||||
import {createSignal} from "solid-js";
|
import {createEffect, createSignal} from "solid-js";
|
||||||
import {Session, StateUpdate} from "./Session";
|
import {Session, StateUpdate} from "./Session";
|
||||||
|
|
||||||
export interface AnalyseProps {
|
export interface AnalyseProps {
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ export const MapContainer = (props: ParentProps<MapContainerProps>) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (isFinite(scale())) {
|
const s = scale();
|
||||||
props.onScale && props.onScale(scale());
|
console.log(s)
|
||||||
|
if (isFinite(s)) {
|
||||||
|
props.onScale && props.onScale(s);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import {Player as PlayerDot} from './Render/Player';
|
||||||
import {Building as BuildingDot} from './Render/Building';
|
import {Building as BuildingDot} from './Render/Building';
|
||||||
import {findMapAlias} from './MapBoundries';
|
import {findMapAlias} from './MapBoundries';
|
||||||
import {PlayerState, Header, WorldBoundaries, BuildingState} from "./Data/Parser";
|
import {PlayerState, Header, WorldBoundaries, BuildingState} from "./Data/Parser";
|
||||||
import {splitProps} from "solid-js";
|
import {createEffect, Show} from "solid-js";
|
||||||
|
|
||||||
export interface MapRenderProps {
|
export interface MapRenderProps {
|
||||||
header: Header;
|
header: Header;
|
||||||
|
|
@ -18,29 +18,22 @@ export interface MapRenderProps {
|
||||||
|
|
||||||
export function MapRender(props: MapRenderProps) {
|
export function MapRender(props: MapRenderProps) {
|
||||||
const mapAlias = findMapAlias(props.header.map);
|
const mapAlias = findMapAlias(props.header.map);
|
||||||
const image = `images/leveloverview/dist/${mapAlias}.webp`;
|
const image = `/images/leveloverview/dist/${mapAlias}.webp`;
|
||||||
const background = `url(${image})`;
|
const background = `url(${image})`;
|
||||||
|
|
||||||
const playerDots = () => props.players
|
|
||||||
.filter((player: PlayerState) => player.health)
|
|
||||||
.map((player: PlayerState) => {
|
|
||||||
return <PlayerDot player={player} mapBoundary={props.world}
|
|
||||||
targetSize={props.size} scale={props.scale} />
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildingDots = () => props.buildings
|
|
||||||
.filter((building: PlayerState) => building.position.x)
|
|
||||||
.map((building: PlayerState) => {
|
|
||||||
return <BuildingDot building={building}
|
|
||||||
mapBoundary={props.world}
|
|
||||||
targetSize={props.size} scale={props.scale}/>
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg class="map-background" width={props.size.width} height={props.size.height}
|
<svg class="map-background" width={props.size.width} height={props.size.height}
|
||||||
style={{"background-image": background}}>
|
style={{"background-image": background}}>
|
||||||
{playerDots()}
|
<For each={props.players}>{(player) =>
|
||||||
{buildingDots()}
|
<Show when={player.health}>
|
||||||
|
<PlayerDot player={player} mapBoundary={props.world} targetSize={props.size} scale={props.scale} />
|
||||||
|
</Show>
|
||||||
|
}</For>
|
||||||
|
<For each={props.buildings}>{(building) =>
|
||||||
|
<Show when={building.position.x}>
|
||||||
|
<BuildingDot building={building} mapBoundary={props.world} targetSize={props.size} scale={props.scale}/>
|
||||||
|
</Show>
|
||||||
|
}</For>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,29 +40,32 @@ function getIcon(building: BuildingState) {
|
||||||
return `/images/building_icons/${icon}_${team}.png`;
|
return `/images/building_icons/${icon}_${team}.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Building({building, mapBoundary, targetSize, scale}: BuildingProp) {
|
export function Building(props: BuildingProp) {
|
||||||
const worldWidth = mapBoundary.boundary_max.x - mapBoundary.boundary_min.x;
|
const worldWidth = props.mapBoundary.boundary_max.x - props.mapBoundary.boundary_min.x;
|
||||||
const worldHeight = mapBoundary.boundary_max.y - mapBoundary.boundary_min.y;
|
const worldHeight = props.mapBoundary.boundary_max.y - props.mapBoundary.boundary_min.y;
|
||||||
const x = building.position.x - mapBoundary.boundary_min.x;
|
const x = () => props.building.position.x - props.mapBoundary.boundary_min.x;
|
||||||
const y = building.position.y - mapBoundary.boundary_min.y;
|
const y = () => props.building.position.y - props.mapBoundary.boundary_min.y;
|
||||||
const scaledX = x / worldWidth * targetSize.width;
|
const scaledX = () => x() / worldWidth * props.targetSize.width;
|
||||||
const scaledY = (worldHeight - y) / worldHeight * targetSize.height;
|
const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height;
|
||||||
const maxHealth = healthMap[building.level];
|
const maxHealth = () => healthMap[props.building.level];
|
||||||
if (!maxHealth) {
|
if (!maxHealth) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alpha = building.health / maxHealth;
|
const transform = () => `translate(${scaledX()} ${scaledY()}) scale(${1 / props.scale})`;
|
||||||
|
const rotate = () => `rotate(${270 - props.building.angle})`;
|
||||||
|
|
||||||
|
const alpha = () => props.building.health / maxHealth;
|
||||||
try {
|
try {
|
||||||
const image = getIcon(building);
|
const image = getIcon(props.building);
|
||||||
return <g transform={`translate(${scaledX} ${scaledY}) scale(${1 / scale})`}
|
return <g transform={transform()}
|
||||||
opacity={alpha}>
|
opacity={alpha()}>
|
||||||
<image href={image} className={"player-icon"} height={32}
|
<image href={image} className={"player-icon"} height={32}
|
||||||
width={32}
|
width={32}
|
||||||
transform={`translate(-16 -16)`}/>
|
transform={`translate(-16 -16)`}/>
|
||||||
<Show when={building.angle}>
|
<Show when={props.building.angle}>
|
||||||
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
|
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
|
||||||
transform={`rotate(${270 - building.angle})`}/>
|
transform={rotate()}/>
|
||||||
</Show>
|
</Show>
|
||||||
</g>
|
</g>
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,76 @@
|
||||||
import {PlayerState, WorldBoundaries, Team} from "../Data/Parser";
|
import {PlayerState, WorldBoundaries, Team} from "../Data/Parser";
|
||||||
|
import {createEffect} from "solid-js";
|
||||||
|
|
||||||
export interface PlayerProp {
|
export interface PlayerProp {
|
||||||
player: PlayerState;
|
player: PlayerState;
|
||||||
mapBoundary: WorldBoundaries;
|
mapBoundary: WorldBoundaries;
|
||||||
targetSize: {
|
targetSize: {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
scale: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const healthMap = {
|
const healthMap = {
|
||||||
0: 100, //fallback
|
0: 100, //fallback
|
||||||
1: 125,//scout
|
1: 125,//scout
|
||||||
2: 150,//sniper
|
2: 150,//sniper
|
||||||
3: 200,//soldier,
|
3: 200,//soldier,
|
||||||
4: 175,//demoman,
|
4: 175,//demoman,
|
||||||
5: 150,//medic,
|
5: 150,//medic,
|
||||||
6: 300,//heavy,
|
6: 300,//heavy,
|
||||||
7: 175,//pyro
|
7: 175,//pyro
|
||||||
8: 125,//spy
|
8: 125,//spy
|
||||||
9: 125,//engineer
|
9: 125,//engineer
|
||||||
};
|
};
|
||||||
|
|
||||||
const classMap = {
|
const classMap = {
|
||||||
0: "empty",
|
0: "empty",
|
||||||
1: "scout",
|
1: "scout",
|
||||||
2: "sniper",
|
2: "sniper",
|
||||||
3: "soldier",
|
3: "soldier",
|
||||||
4: "demoman",
|
4: "demoman",
|
||||||
5: "medic",
|
5: "medic",
|
||||||
6: "heavy",
|
6: "heavy",
|
||||||
7: "pyro",
|
7: "pyro",
|
||||||
8: "spy",
|
8: "spy",
|
||||||
9: "engineer"
|
9: "engineer"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Player({player, mapBoundary, targetSize, scale}: PlayerProp) {
|
export function Player(props: PlayerProp) {
|
||||||
const worldWidth = mapBoundary.boundary_max.x - mapBoundary.boundary_min.x;
|
const worldWidth = props.mapBoundary.boundary_max.x - props.mapBoundary.boundary_min.x;
|
||||||
const worldHeight = mapBoundary.boundary_max.y - mapBoundary.boundary_min.y;
|
const worldHeight = props.mapBoundary.boundary_max.y - props.mapBoundary.boundary_min.y;
|
||||||
const x = player.position.x - mapBoundary.boundary_min.x;
|
const x = () => props.player.position.x - props.mapBoundary.boundary_min.x;
|
||||||
const y = player.position.y - mapBoundary.boundary_min.y;
|
const y = () => props.player.position.y - props.mapBoundary.boundary_min.y;
|
||||||
const scaledX = x / worldWidth * targetSize.width;
|
const scaledX = () => x() / worldWidth * props.targetSize.width;
|
||||||
const scaledY = (worldHeight - y) / worldHeight * targetSize.height;
|
const scaledY = () => (worldHeight - y()) / worldHeight * props.targetSize.height;
|
||||||
const maxHealth = healthMap[player.playerClass];
|
const maxHealth = () => healthMap[props.player.playerClass];
|
||||||
const alpha = player.health / maxHealth;
|
const alpha = () => props.player.health / maxHealth();
|
||||||
const teamColor = (player.team === Team.Red) ? '#a75d50' : '#5b818f';
|
const teamColor = () => (props.player.team === Team.Red) ? '#a75d50' : '#5b818f';
|
||||||
const imageOpacity = player.health === 0 ? 0 : (1 + alpha) / 2;
|
const imageOpacity = () => props.player.health === 0 ? 0 : (1 + alpha()) / 2;
|
||||||
|
const transform = () => `translate(${scaledX()} ${scaledY()}) scale(${1 / props.scale})`;
|
||||||
|
const rotate = () => `rotate(${270 - props.player.angle})`;
|
||||||
|
|
||||||
return <g
|
return <g
|
||||||
transform={`translate(${scaledX} ${scaledY}) scale(${1 / scale})`}>
|
transform={transform()}>
|
||||||
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
|
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
|
||||||
opacity={imageOpacity}
|
opacity={imageOpacity()}
|
||||||
transform={`rotate(${270 - player.angle})`}/>
|
transform={rotate()}/>
|
||||||
<circle r={16} stroke-width={1} stroke="white" fill={teamColor}
|
<circle r={16} stroke-width={1} stroke="white" fill={teamColor()}
|
||||||
opacity={alpha}/>
|
opacity={alpha()}/>
|
||||||
{getClassImage(player, imageOpacity)}
|
{getClassImage(props.player, imageOpacity())}
|
||||||
</g>
|
</g>
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClassImage(player: PlayerState, imageOpacity: number) {
|
function getClassImage(player: PlayerState, imageOpacity: number) {
|
||||||
if (!classMap[player.playerClass]) {
|
if (!classMap[player.playerClass]) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const image = `/images/class_icons/${classMap[player.playerClass]}.svg`;
|
const image = `/images/class_icons/${classMap[player.playerClass]}.svg`;
|
||||||
return <image href={image}
|
return <image href={image}
|
||||||
class={"player-icon " + player.team}
|
class={"player-icon " + player.team}
|
||||||
opacity={imageOpacity}
|
opacity={imageOpacity}
|
||||||
height={32}
|
height={32}
|
||||||
width={32}
|
width={32}
|
||||||
transform={`translate(-16 -16)`}/>
|
transform={`translate(-16 -16)`}/>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ export const Panner = (props: ParentProps<PannerProps>) => {
|
||||||
startX = event.pageX;
|
startX = event.pageX;
|
||||||
startY = event.pageY;
|
startY = event.pageY;
|
||||||
|
|
||||||
setTranslateX(panner.viewport.x);
|
setTranslateX(Math.floor(panner.viewport.x));
|
||||||
setTranslateY(panner.viewport.y);
|
setTranslateY(Math.floor(panner.viewport.y));
|
||||||
setScale(panner.scale);
|
setScale(panner.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,9 +66,7 @@ export const Panner = (props: ParentProps<PannerProps>) => {
|
||||||
setTranslateY(panner.viewport.y);
|
setTranslateY(panner.viewport.y);
|
||||||
setScale(panner.scale);
|
setScale(panner.scale);
|
||||||
|
|
||||||
if (props.onScale) {
|
props.onScale(panner.scale);
|
||||||
props.onScale(panner.scale);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mouseWheel = (event) => {
|
const mouseWheel = (event) => {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ use std::net::SocketAddr;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use steam_openid::SteamOpenId;
|
use steam_openid::SteamOpenId;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::{error, info, info_span};
|
use tracing::{error, info, info_span};
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
|
|
@ -117,6 +118,7 @@ async fn main() -> Result<()> {
|
||||||
.route("/viewer", get(viewer))
|
.route("/viewer", get(viewer))
|
||||||
.route("/viewer/:id", get(viewer))
|
.route("/viewer/:id", get(viewer))
|
||||||
.route("/:id", get(demo))
|
.route("/:id", get(demo))
|
||||||
|
.nest_service("/images", ServeDir::new("images"))
|
||||||
.layer(
|
.layer(
|
||||||
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||||
let matched_path = request
|
let matched_path = request
|
||||||
|
|
@ -373,7 +375,6 @@ async fn viewer(
|
||||||
};
|
};
|
||||||
Ok(render(ViewerPage { demo }, session))
|
Ok(render(ViewerPage { demo }, session))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handler_404() -> impl IntoResponse {
|
async fn handler_404() -> impl IntoResponse {
|
||||||
Error::NotFound
|
Error::NotFound
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue