Back
INTO THE SHADOWS
100% · First Class

What is Into the Shadows?

Into the Shadows title screen // Into the Shadows — title screen

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.

  • Awarded 100% — perfect score for the Level 3 assignment
  • 8,882 lines of C# across 7 source files, all written solo
  • 20+ named rooms across a fully explorable cult prison
  • 45 audio tracks with simultaneous playback via NAudio threading
  • Full save and load system using StreamWriter / StreamReader
  • Four enemy types with distinct stats, weapons, and attack pools
  • Level-up system: max level 10, allocatable stat points on level up
  • CreateMenuPrompt() refactor eliminated ~100 blocks of duplicate code
  • Companion FMV Installer tool built as a separate console application

Prison Layout

Prison layout map // Prison layout — full level map

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
// 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.

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.

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.

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.

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 BGM 13 Randomised per battle — three BGM sets (BattleBGM1, 2, 3)
Story BGM 11 Room-specific ambient tracks — main menu, basement, laundry, canteen, finale…
SFX 18 Heartbeat, keycard beeps, screaming, bone-cracking, alarm, whispering…
Total ~45 ~831 MB source audio assets

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 limitation Inheritance 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.

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.

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 lesson Threading 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.