Back
INTO THE SHADOWS
Explored 0%
Into the Shadows main menu in Windows Terminal Canteen dialogue scene with Officer NPC Full annotated Into the Shadows prison map
C# · Solo · University

Into the Shadows

A C# console horror adventure: escape a cult-controlled prison by hunting keycards, restoring power and surviving the guards and creatures in between — with a 45-track NAudio soundtrack and full save/load.

Type
University Assignment
Module
GAME30514
Language
C#
Year
2023
Team
Solo
Classes
0 C# files
Source Size
~831 MB
C# Visual Studio Spectre Console NAudio Threading .NET Console App Text RPG Horror Save & Load StreamWriter StreamReader
Final Grade 0%
0
Lines of Code
0
Audio Tracks
0
Unique enemy types
0
Rooms
Overview

What is Into the Shadows?

Into the Shadows is a C# console horror adventure game built for a Level 3 Foundations of Programming assignment at Staffordshire University — the direct successor to No Hope, rebuilt from scratch with a completely different scope. It runs entirely in the terminal using Spectre Console for styled output, arrow-key menu navigation, animated status spinners, and coloured dialogue.

You are a prisoner in a cult-controlled prison. The goal is simple: escape. The route is not. Getting out requires navigating a multi-room facility, finding Clearance Keycards at three access levels, restoring power via replacement wires, solving a keypad combination, and surviving whatever guards and creatures are between you and the exit.

Inspiration was drawn from Silent Hill Homecoming, Resident Evil 3, and Fallout 4 — three games that balance combat tension with world exploration. Awarded 100% for the assignment.

Into the Shadows title screen // Into the Shadows — title screen
World Map

Prison Layout

The game takes place across a fully mapped prison. Each room is a distinct location with its own ambient BGM track, encounter table, items, and story beats. The map below is the full annotated export, charting all keycards, enemy spawn points, story triggers, puzzles, and win conditions.

Full annotated Into the Shadows prison map // Prison layout — full level map

// Click to expand — annotated map of the full prison layout

Key locations include: Entrance (randomised enemy spawn, exit goal), Laundry Room (three sub-sections with escalating ambience), Canteen (Officer NPC dialogue, keycode 79175), Solitary, Shower Room, Cell Block A & B with Control Rooms, Weapons Room, Execution Room, The Torture Room, Visitor Centre, Guard Post, and The Rec Room.

Combat

Battle System & Enemies

Combat is turn-based with a full ATK/DEF/HP stat system, critical hit RNG, bleed status, daze, and sneak attack. Each enemy has unique HP, attack rating, defence rating, and a bespoke attack pool. The player can punch, swing a metal pipe, or give "death stares" when unarmed — and equip a shotgun, pistol, or AR when they're found.

Canteen dialogue scene with Officer NPC // Canteen — live dialogue sequence

Engorged Beetles

65 HP · ATK 2.35
  • Bite
  • Fly and Hiss
  • Sprinkle Some Poop

Slitherman

Variable · Agile
  • Scratch
  • Lunge
  • Wail

The Woodchopper

High HP · Heavy
  • Single Slice
  • Double Slice
  • Grapple

Cult Guard

175 HP · DEF 3.75
  • Melee with a rusty pipe
  • Electrocute
  • Shoot with a 9mm pistol
  • Eat some junk food
  • Radio in some motivation

Bleed status causes ongoing damage each turn for a randomised duration. The Fear stat — a second resource alongside HP — rises during encounters and passively degrades performance when high. Sneak attacks deal bonus damage at the start of a fight when the player approaches undetected.

Inventory — Consumables

Seven consumable items exist in the game world, each with mechanical effects beyond simple HP restoration:

ItemEffect
Expired Pack of Malteasers+25 HP. Starts in inventory.
Bo'le of Aged Wa'er+15 HP, −5 Fear.
Bo'le of Holy WaterClears all Fear, +35 Max HP, +2.5 ATK, +2.5 DEF.
Box of Ferrero Rocher+45 HP, +10 Max HP, +1.5 ATK, +1.5 DEF. Contains Deez Nuts.
Box of Doughnuts+75 HP, +15 Max HP. Risk of Lactose Intolerance.
Crunchy Chick+25 HP, +5 Max HP, +0.5 ATK, +0.5 DEF. +25 Fear. Morally questionable.
Bandages+25 HP, removes Bleed status.
Audio System

45-Track Soundtrack via NAudio

Audio was the most technically demanding part of the project. Console apps do not have default access to SoundPlayer, and after researching NuGet packages, NAudio was chosen — despite its unfamiliar implementation approach. Two core problems had to be solved:

Exception on overlap

NAudio throws when a new file is played while another is active. A public CheckBGMPlayer() method was written, called before any audio transition, to safely stop any running stream first.

No loop function

NAudio has no built-in audio looping. A community solution was found and integrated, allowing BGM tracks to loop seamlessly.

Simultaneous playback

Threading was used to queue and play background music alongside SFX concurrently without either track cutting the other.

CategoryCountNotes
Battle BGM13Randomised per battle — three BGM sets (BattleBGM1, 2, 3)
Story BGM11Room-specific ambient tracks — main menu, basement, laundry, canteen, finale…
SFX18Heartbeat, keycard beeps, screaming, bone-cracking, alarm, whispering…
Total~45~831 MB source audio assets
Technical Breakdown

Architecture & Code Strategy

The codebase spans ~8,882 lines across 7 C# files, each a dedicated class: MainGame (4,871 lines — story progression), BattleSystem (1,076), ExtFunctions (1,084 — reusable utility methods), MainMenuOptions (805), Player (647), SoundSystem (359), and PlayerJournal (33).

CreateMenuPrompt()

A parameterised public static method that encapsulates Spectre Console's selection prompt. Without it, the same 5-line menu block would have been duplicated ~100 times throughout the codebase. One method call, one switch statement — eliminates bad-input exceptions entirely.

Save & Load

Player state is written to a .txt file via StreamWriter, with a directory existence check before every save. Loading uses StreamReader to parse each line, strip formatting characters, and map values back to their correct game variables.

Level-Up System

Max level 10. Each level grants 1 stat point. At multiples of 5, boosts are stronger (HP +15, ATK/DEF +0.75 vs HP +10, ATK/DEF +0.5). The player allocates points via a menu prompt between combat encounters.

Stopwatch

A System.Diagnostics.Stopwatch instance runs from game start, displaying elapsed time in the console window title bar in HH:MM:SS format.

Fear System

A second stat alongside HP. Rises during combat, via certain consumables (Crunchy Chick, +25), and from Slitherman encounters. At max Fear (100), the PlayerFeared boolean triggers combat penalties.

Dialogue system

Each character is colour-coded via Spectre Console markup. Dialogue progresses on keypress, preserving the pacing of voice line delivery.

Acknowledged limitationInheritance was not used where it should have been. The codebase relies heavily on static methods and global variables rather than an OOP class hierarchy. Noted explicitly in the old portfolio page as the one structural regret from the project.
Extras — FMV Installer & Debug Menu

A standalone Remastered FMVs Installer tool was built as a PreAlpha companion utility — a separate console application that lists all connected storage devices in a table, offers QuickSFV integrity checking, and guides the player to locate the game executable. It's styled to match the game's aesthetic: Spectre Console tables in cyan, branded with "Created by: NomNom" and a Discord server link.

The game also includes a Debug Menu accessible from the main menu, allowing direct testing of game states, location jumping, and variable inspection — built during development and intentionally left in the release build as a transparency gesture.

FMV Installer tool — PreAlpha build // Remastered FMVs Installer (PreAlpha)
Challenges & Lessons

What Was Hard (And What Was Earned)

The save/load system was the most technically challenging feature relative to prior experience. Writing variables to a text file was manageable — but parsing them back correctly on load required careful StreamReader logic to strip formatting characters and map each line to the right game variable without breaking the game state.

Key lessonThreading solves audio layering in console apps — but only if you handle the exception boundary first. CheckBGMPlayer() being a public static method callable from anywhere in the codebase was the architectural decision that made the whole audio system stable.

The CreateMenuPrompt() refactor was the single biggest quality-of-life improvement mid-project. Recognising that the same code was being written ~100 times and taking the time to abstract it into a parameterised method reduced both line count and bugs significantly — and made the codebase readable.

The project received 100%. It is the highest-graded programming project in the portfolio.

Milestones

Trophies Earned

Perfect ScoreAwarded 100% — perfect score for the Level 3 assignment
Wall of Code8,882 lines of C# across 7 source files, all written solo
Jailbreak20+ named rooms across a fully explorable cult prison
Mixing Desk45 audio tracks with simultaneous playback via NAudio threading
Save PointFull save and load system using StreamWriter / StreamReader
CellmatesFour enemy types with distinct stats, weapons, and attack pools
Level TenLevel-up system: max level 10, allocatable stat points on level up
DRY RunCreateMenuPrompt() refactor eliminated ~100 blocks of duplicate code
Bonus DiscCompanion FMV Installer tool built as a separate console application