Skip to content

Commit

Permalink
Merge pull request #3 from DVDAGames/feature/pages
Browse files Browse the repository at this point in the history
Feature/pages
  • Loading branch information
ericrallen committed May 3, 2024
2 parents 43d5f99 + 5bb947e commit 95fe033
Show file tree
Hide file tree
Showing 30 changed files with 628 additions and 42 deletions.
25 changes: 25 additions & 0 deletions _games/gravedigger-dark-ritual.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: "Gravedigger: Dark Ritual"
excerpt: An evil wizard is performing a dark ritual to summon an ancient evil. Only the gravedigger, a member of a forgotten cleric order, can stop him.
coverImage: /assets/blog/gravedigger/cover.png
bannerImage: /assets/blog/gravedigger/hero.png
ogImage:
url: /assets/blog/gravedigger/cover.png
date: "TBD"
ctas:
- label: "Play the Prototype"
url: "https://dvdagames.itch.io/dark-ritual"
price: ""
---

_Gravedigger: Dark Ritual_ is an old-school adventure game that combines elements of classic adventure role-playing games (RPGs) like puzzles, traps, and combat with a modern rogue-like twist involving a repeating time loop.

The game is set in a dark fantasy world where, unknown to the nearby humble village, an evil wizard has been planning for a dark ritual that will summon an ancient evil from the abyss beyond. The ritual requires a Blood Moon, and while the townsfolk are celebrating with their Lunar Festival, dark deeds are afoot in the nearby catacombs.

Play as Caelum, the town's gravedigger, an acolyte of a long-forgotten order of clerics who protect the realm of the living from the realm of the undying. To hunt down the wizard and stop the ritual, you'll need to sacrifice everything - even your own soul - to prevent the ancient evil from being unleashed upon the world.

## Features

- **Classic RPG elements**: Explore a dark fantasy world filled with traps, puzzles, and combat with various monsters, undead, and horrors from the abyss beyond.
- **Modern roguelite twist**: Bound to the ritual by a cursed blade, embrace the power of a repeating time loop to learn from your mistakes and try new strategies to stop the wizard and prevent the ritual. Some sacred artifacts may be powerful enough to travel back with you to the start of the loop; most will not.
- **Echoes of the past**: When you die, your essence returns to the start of the ritual, but your bloodstain and the echo of your ghostly form remain. Leverage these bloodstains and echoes in subsequent loops to help you solve puzzles, avoid traps, and navigate the collapsing catacombs.
24 changes: 24 additions & 0 deletions _projects/hex-flower-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: "Hex Flower Engine"
excerpt: An interactive implementation of Goblin's Henchman's Hex Flower Engine in React, using the better random number generator from our TypeScript dice rolling library for rolls that are more fairly distributed.
coverImage: /assets/blog/hex-flower-engine/hex-flower.png
bannerImage: /assets/blog/hex-flower-engine/hex-flower.gif
ogImage:
url: /assets/blog/hex-flower-engine/hex-flower.gif
date: 2020-07-11
ctas:
- label: "Run the Engine"
url: "https://dvdagames.github.io/react-hex-flower-engine/"
price: "FREE"
---
The [Hex Flower Engine](https://goblinshenchman.wordpress.com/2018/10/25/2d6-hex-power-flower/) is an ingenious invention from [Goblin's Henchman](https://goblinshenchman.wordpress.com/) that gives Game Masters (GMs) and Dungeon Masters (DMs) a way to generate random results that are more predictable and feel more realistic than a simple table or a single die roll.

> A versatile game engine using 2D6 and a 19-Hex Flower (it’s like a random table, but with a memory).
It relies on rolling `2d6` (two six-sided dice) and using the results to decide which direction to move in a grid of 19 hexagons laid out in an even larger hexagon.

[Goblin's Henchman has written way more than I ever could describing the various use cases for and ideas behind the engine](https://goblinshenchman.wordpress.com/category/hex-flower/), so I'll leave that to them.

This particular implementation of their ideas is a simple React-based web application hosted on GitHub Pages. The code is entirely open source, so you can easily fork it to tweak to your liking, or submit an Issue or Pull Request for some feature you would like to see.

This was selfishly created for my own use as a Tempest Cleric in a D&D campaign where I wanted to know if there was ever an existing storm that I could use to power up my Call Lightning spell. The DM generously created a version of the Hex Flower Engine for us to use, so I built the campaign an interactive digital version we could rely on each session.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/assets/blog/gravedigger/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/blog/gravedigger/hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/images/turntable-4x.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/images/turntable-4x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/app/_components/cover-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const CoverImage = ({ title, src, slug }: Props) => {
})}
width={1300}
height={630}
priority
/>
);
return (
Expand Down
13 changes: 10 additions & 3 deletions src/app/_components/date-formatter.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { parseISO, format } from "date-fns";
import { format } from "date-fns/format";

type Props = {
dateString: string;
};

const DateFormatter = ({ dateString }: Props) => {
const date = parseISO(dateString);
return <time dateTime={dateString}>{format(date, "yyyy-MM-dd")}</time>;
try {
const date = format(dateString, "yyyy-MM-dd");

return <time dateTime={dateString}>{date}</time>;
} catch (e) {
console.log("ERROR: cannot parse date");
console.error(e);
return <span>{dateString.toString()}</span>;
}
};

export default DateFormatter;
5 changes: 4 additions & 1 deletion src/app/_components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ export function Footer() {
<li className="mr-3">
<a href={`https://github.com/dvdagames/`}>GitHub</a>
</li>
<li>
<li className="mr-3">
<a href={`https://twitter.com/dvdagames`}>Twitter</a>
</li>
<li>
<a href={`https://dvdagames.itch.io`}>Itch.io</a>
</li>
</ul>
</div>
</div>
Expand Down
35 changes: 35 additions & 0 deletions src/app/_components/game-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import CoverImage from "./cover-image";
import DateFormatter from "./date-formatter";
import { PostTitle } from "@/app/_components/post-title";
import { CTA } from "@/interfaces/cta";

type Props = {
title: string;
bannerImage: string;
releaseDate: string;
ctas: CTA[];
};

export function GameHeader({ title, bannerImage, releaseDate, ctas }: Props) {
return (
<>
<PostTitle>{title}</PostTitle>
<div className="mb-8 md:mb-16 sm:mx-0">
<CoverImage title={title} src={bannerImage} />
</div>
{ctas.length > 0 &&
ctas.map((cta) => (
<a key={cta.label} href={cta.url} className="btn">
{cta.label}
</a>
))}
<div className="max-w-2xl mx-auto">
{releaseDate !== "" && (
<div className="mb-6 text-lg">
Release Date: <DateFormatter dateString={releaseDate} />
</div>
)}
</div>
</>
);
}
43 changes: 43 additions & 0 deletions src/app/_components/game-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { type CTA } from "@/interfaces/cta";
import Link from "next/link";
import CoverImage from "./cover-image";
import DateFormatter from "./date-formatter";

type Props = {
title: string;
coverImage: string;
releaseDate: string;
excerpt: string;
slug: string;
price: string;
ctas: CTA[];
};

export function GamePreview({ title, coverImage, releaseDate, excerpt, slug, price, ctas }: Props) {
return (
<div className="w-[33%]">
<h3 className="text-xl mb-3 leading-snug">
<Link as={`/games/${slug}`} href="/games/[slug]" className="hover:underline">
{title}
</Link>
</h3>
<div className="mb-5">
<CoverImage slug={slug} title={title} src={coverImage} />
</div>
<div className="text-lg mb-4">
<strong>Release Date:</strong> <DateFormatter dateString={releaseDate} />
</div>
{typeof ctas !== "undefined" && (
<div className="mb-4">
{ctas.length > 0 &&
ctas.map((cta) => (
<a key={cta.label} href={cta.url} className="btn">
{cta.label}
</a>
))}
</div>
)}
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
</div>
);
}
10 changes: 3 additions & 7 deletions src/app/_components/header.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import Link from "next/link";

import Nav from "./nav";

const Header = () => {
return (
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8">
<Link href="/" className="hover:underline">
DVDA Games
</Link>
</h2>
);
return <Nav />;
};

export default Header;
7 changes: 5 additions & 2 deletions src/app/_components/intro.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import Nav from "./nav";

export function Intro() {
return (
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
<section className="flex flex-col items-start mt-16 mb-16 md:mb-12">
<h1 className="text-3xl md:text-5xl font-bold tracking-tighter leading-tight md:pr-8">
Dead Villager Dead Adventurer Games
</h1>
<Nav includeHome={false} />
</section>
);
}

export default Intro;
export default Intro;
32 changes: 32 additions & 0 deletions src/app/_components/nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from "next/link";

export interface NavProps {
includeHome?: boolean;
}

export function Nav({ includeHome = true }: NavProps): React.ReactElement {
return (
<nav className="flex flex-row mb-[20px]">
<ul className="flex flex-row">
{includeHome && (
<li>
<Link className="mr-[20px]" href="/">
DVDA Games
</Link>
</li>
)}
<li className="mr-[20px]">
<Link href="/games">Games</Link>
</li>
<li className="mr-[20px]">
<Link href="/projects">Projects</Link>
</li>
<li className="">
<Link href="/about">About</Link>
</li>
</ul>
</nav>
);
}

export default Nav;
10 changes: 5 additions & 5 deletions src/app/_components/player/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ export function Player({ tracks = [], autoplay = false, loop = false }: PlayerPr
<img
src={currentTrack.albumArt}
alt={currentTrack.title}
className="w-[64px] h-[64px] border-solid border-white border-[5px]"
className="w-[96px] h-[96px] border-solid border-white border-[5px]"
/>
<div className="flex w-full min-h-[64px] pr-[20px]">
<div className="flex w-full min-h-[96px] pr-[20px] pl-[5px]">
{currentTrack.title && (
<button
className="flex w-full min-h-[64px] items-center text-2xl apperance-none hover:underline"
className="flex w-full min-h-[96px] items-center text-2xl apperance-none hover:underline"
onClick={togglePlaylist}
title={showPlaylist ? "Hide Playlist" : "Show Playlist"}
>
Expand All @@ -68,11 +68,11 @@ export function Player({ tracks = [], autoplay = false, loop = false }: PlayerPr
</div>
<audio ref={playerRef} className="flex" autoPlay={autoplay} loop={loop} preload="metadata" src={currentTrack.url} />
<button
className="h-[64px] w-[84px] pl-[20px] appearance-none fixed right-0 bottom-0 bg-white"
className="h-[96px] w-[96px] appearance-none fixed right-0 bottom-0 bg-white"
onClick={togglePlay}
title={isPlaying ? "Pause" : "Play"}
>
<img src={`/assets/images/turntable.${isPlaying ? "gif" : "png"}`} alt="" height="64" width="64" />
<img className="mt-[-10px] ml-[10px]" src={`/assets/images/turntable-4x.${isPlaying ? "gif" : "png"}`} alt="" />
</button>
</div>
</section>
Expand Down
43 changes: 43 additions & 0 deletions src/app/_components/project-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { type CTA } from "@/interfaces/cta";
import Link from "next/link";
import CoverImage from "./cover-image";
import DateFormatter from "./date-formatter";

type Props = {
title: string;
coverImage: string;
releaseDate: string;
excerpt: string;
slug: string;
price: string;
ctas: CTA[];
};

export function ProjectPreview({ title, coverImage, releaseDate, excerpt, slug, price, ctas }: Props) {
return (
<div className="w-[33%]">
<h3 className="text-xl mb-3 leading-snug">
<Link as={`/projects/${slug}`} href="/projects/[slug]" className="hover:underline">
{title}
</Link>
</h3>
<div className="mb-5">
<CoverImage slug={slug} title={title} src={coverImage} />
</div>
<div className="text-lg mb-4">
<strong>Release Date:</strong> <DateFormatter dateString={releaseDate} />
</div>
{typeof ctas !== "undefined" && (
<div className="mb-4">
{ctas.length > 0 &&
ctas.map((cta) => (
<a key={cta.label} href={cta.url} className="btn">
{cta.label}
</a>
))}
</div>
)}
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
</div>
);
}
32 changes: 32 additions & 0 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from "next/link";

import Header from "@/app/_components/header";
import Container from "@/app/_components/container";
import { PostTitle } from "@/app/_components/post-title";

export function About(): React.ReactElement {
return (
<main>
<Container>
<Header />
<article className="mb-32">
<PostTitle>About</PostTitle>
<p>
Dead Villager Dead Adventurer Games is an indie game studio and an open source tool developer formed as a labor of
love by <a href="https://www.linkedin.com/in/allenericr/">Eric Allen</a>, a developer advocate and software developer
pursuing a life long dream of making video games.
</p>
<p className="mt-4">
DVDA Games has been a side project hacking on game <Link href="/projects">projects and open source tools</Link> to
support gamers and other developers since 2016. In 2024, after{" "}
<a href="https://ldjam.com/events/ludum-dare/55/dark-ritual">a successful Ludum Dare 55 Compo entry</a>, DVDA Games
started development on its first commercial game{" "}
<Link href="/games/gravedigger-dark-ritual">Gravedigger: Dark Ritual</Link>, a retro-inspired adventure game.
</p>
</article>
</Container>
</main>
);
}

export default About;
Loading

0 comments on commit 95fe033

Please sign in to comment.