Attackers

This commit is contained in:
LittleSheep 2024-02-16 00:50:03 +08:00
parent 046f33b68e
commit 901c01af2f
5 changed files with 148 additions and 4 deletions

View File

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cyberattack</title> <title>Cloudflare Simulator</title>
<style> <style>
body, html { body, html {

View File

@ -4,6 +4,7 @@ import { vector2d } from "./vector";
import Player from "./objects/player"; import Player from "./objects/player";
import Crystal from "./objects/crystal"; import Crystal from "./objects/crystal";
import Attacker from "./objects/enemies/attacker";
import TimerProvider from "./providers/timer"; import TimerProvider from "./providers/timer";
import CooldownProvider from "./providers/cooldown"; import CooldownProvider from "./providers/cooldown";
import CooldownUI from "./ui/cooldown"; import CooldownUI from "./ui/cooldown";
@ -23,8 +24,17 @@ export default function Stage() {
let centerPosition = new vector2d(0, 0); let centerPosition = new vector2d(0, 0);
let playerPosition = new vector2d(0, 0); let playerPosition = new vector2d(0, 0);
let randomPosition = new vector2d(0, 0);
let [ready, setReady] = createSignal(false); let [ready, setReady] = createSignal(false);
function calcRandomPosition(maxX: number, maxY: number) {
return new vector2d(
Math.floor(Math.random() * maxX),
Math.floor(Math.random() * maxY)
);
}
function initializeStage(element: HTMLDivElement) { function initializeStage(element: HTMLDivElement) {
centerPosition = new vector2d( centerPosition = new vector2d(
element.clientWidth / 2, element.clientWidth / 2,
@ -32,6 +42,12 @@ export default function Stage() {
); );
playerPosition = centerPosition.clone(); playerPosition = centerPosition.clone();
randomPosition = calcRandomPosition(
element.clientWidth,
element.clientHeight
);
console.log("example pos:", randomPosition);
setReady(true); setReady(true);
} }
@ -40,12 +56,14 @@ export default function Stage() {
return ( return (
<TimerProvider> <TimerProvider>
<CooldownProvider> <CooldownProvider>
<StageContainer ref={container}> <StageContainer class="stage" ref={container}>
<Show when={ready()}> <Show when={ready()}>
{/* Objects */} {/* Objects */}
<Crystal size={128} position={centerPosition} /> <Crystal size={128} position={centerPosition} />
<Player size={64} position={playerPosition} /> <Player size={64} position={playerPosition} />
<Attacker size={32} position={randomPosition} />
{/* UI Elements */} {/* UI Elements */}
<CooldownUI /> <CooldownUI />
</Show> </Show>

View File

@ -0,0 +1,115 @@
import { styled } from "solid-styled-components";
import { vector2d } from "../../vector";
import { createSignal, onCleanup, onMount } from "solid-js";
import { getFacingAngle } from "../raycast";
import { useTimer } from "../../providers/timer";
export default function Attacker(props: {
size?: number;
speed?: number;
position?: vector2d;
}) {
const width = props.size ? props.size / 6 : 2;
const height = props.size ?? 20;
// Just some random comment
// https://youtube.com/ishowspeed
//
// But I promise
// Attackers' speed will slower than player(4u)
let speed = props.speed ?? 2;
// Position & Movement
const [x, setX] = createSignal(
props.position?.x ? props.position?.x - width / 2 : 0
);
const [y, setY] = createSignal(
props.position?.y ? props.position?.y - height / 2 : 0
);
// Those code below are copied from player.tsx
// I don't know why I didn't make a base class to impl the movement
// We are using the function so that we cannot use the extend keyword
// Howdy!
let velocity = new vector2d(0, 0);
function move() {
setX((x) => (x += velocity.x));
setY((y) => (y += velocity.y));
}
// These two variables should be the "Renderer" part
// But we need use them to calculate the movement
// So whatever
//
// It just works —— Mr. Todd
let element: any;
let target: any;
function calcTrackingVelocity() {
let rectSource = element.getBoundingClientRect();
let rectTarget = target.getBoundingClientRect();
// If we are higher than target, then move down
if (rectSource.top < rectTarget.top) velocity.y = speed;
// If we are lower than target, then move up
else velocity.y = -speed;
// etc...
if (rectSource.left < rectTarget.left) velocity.x = speed;
// etc...
else velocity.x = -speed;
// I really don't know what is the relationship between them.
// But it just works.
//
// We use inline if and else to save more space to write those useless comments...
}
// Every tick move a little bit
const [{ subscribe }] = useTimer();
subscribe(() => {
if (element && target) {
calcTrackingVelocity();
}
});
onMount(() => {
const loop = setInterval(() => move(), 10);
onCleanup(() => clearInterval(loop));
});
// Renderer
const [facingAngle, setFacingAngle] = createSignal(0);
function calcFacing() {
target = document.querySelector<HTMLElement>(".crystal");
if (element && target) {
setFacingAngle(getFacingAngle(element, target) + 90);
}
}
subscribe(() => calcFacing());
const RequestElement = styled("svg")`
width: ${width.toString()}px;
height: ${height.toString()}px;
transform: rotate(${facingAngle}deg);
transition-property: transform;
transition-timing-function: linear;
transition-duration: 150ms;
position: absolute;
`;
return (
<RequestElement
ref={element}
class="hostile-request"
style={{ left: `${x()}px`, top: `${y()}px` }}
>
<rect width={width} height={height} fill="#a71c1c" />
</RequestElement>
);
}

View File

@ -0,0 +1,11 @@
export function getFacingAngle(
source: HTMLElement,
target: HTMLElement
): number {
let rectSource = source.getBoundingClientRect();
let rectTarget = target.getBoundingClientRect();
const deltaX = rectTarget.left - rectSource.left;
const deltaY = rectTarget.top - rectSource.top;
const angleInDegrees = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
return angleInDegrees;
}

View File

@ -20,7 +20,7 @@ export default function CooldownUI() {
}); });
return ( return (
<UIContainer> <UIContainer class="cooldown-promote">
<For each={stack()}> <For each={stack()}>
{(item) => ( {(item) => (
<div> <div>