Add project files.
36
FreedomFighter/.config/dotnet-tools.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-mgcb": {
|
||||
"version": "3.8.4",
|
||||
"commands": [
|
||||
"mgcb"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor": {
|
||||
"version": "3.8.4",
|
||||
"commands": [
|
||||
"mgcb-editor"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-linux": {
|
||||
"version": "3.8.4",
|
||||
"commands": [
|
||||
"mgcb-editor-linux"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-windows": {
|
||||
"version": "3.8.4",
|
||||
"commands": [
|
||||
"mgcb-editor-windows"
|
||||
]
|
||||
},
|
||||
"dotnet-mgcb-editor-mac": {
|
||||
"version": "3.8.4",
|
||||
"commands": [
|
||||
"mgcb-editor-mac"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
FreedomFighter/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "C#: FreedomFighter Debug",
|
||||
"type": "dotnet",
|
||||
"request": "launch",
|
||||
"projectPath": "${workspaceFolder}/FreedomFighter.csproj"
|
||||
}
|
||||
],
|
||||
}
|
||||
29
FreedomFighter/Content/Content.mgcb
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
#----------------------------- Global Properties ----------------------------#
|
||||
|
||||
/outputDir:bin/$(Platform)
|
||||
/intermediateDir:obj/$(Platform)
|
||||
/platform:Windows
|
||||
/config:
|
||||
/profile:Reach
|
||||
/compress:False
|
||||
|
||||
#-------------------------------- References --------------------------------#
|
||||
|
||||
|
||||
#---------------------------------- Content ---------------------------------#
|
||||
|
||||
#begin Verdana12.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=False
|
||||
/processorParam:TextureFormat=Color
|
||||
/build:Verdana12.spritefont
|
||||
|
||||
#begin Verdana20.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=False
|
||||
/processorParam:TextureFormat=Color
|
||||
/build:Verdana20.spritefont
|
||||
|
||||
16
FreedomFighter/Content/Verdana12.spritefont
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
<FontName>Verdana</FontName>
|
||||
<Size>16</Size>
|
||||
<Spacing>0</Spacing>
|
||||
<UseKerning>true</UseKerning>
|
||||
<Style>Bold</Style>
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>~</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
||||
16
FreedomFighter/Content/Verdana20.spritefont
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
<FontName>Verdana</FontName>
|
||||
<Size>27</Size>
|
||||
<Spacing>0</Spacing>
|
||||
<UseKerning>true</UseKerning>
|
||||
<Style>Bold</Style>
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>~</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
||||
278
FreedomFighter/Content/gfx/manifest.json
Normal file
@@ -0,0 +1,278 @@
|
||||
{
|
||||
"OmegaImageList1": [
|
||||
{
|
||||
"Index": 0,
|
||||
"Name": "player",
|
||||
"File": "omegaimagelist1_00_player.png",
|
||||
"TileWidth": 64,
|
||||
"TileHeight": 64
|
||||
},
|
||||
{
|
||||
"Index": 1,
|
||||
"Name": "enemy",
|
||||
"File": "omegaimagelist1_01_enemy.png",
|
||||
"TileWidth": 34,
|
||||
"TileHeight": 34
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
"Name": "bullet",
|
||||
"File": "omegaimagelist1_02_bullet.png",
|
||||
"TileWidth": 34,
|
||||
"TileHeight": 34
|
||||
},
|
||||
{
|
||||
"Index": 3,
|
||||
"Name": "explosion",
|
||||
"File": "omegaimagelist1_03_explosion.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 34
|
||||
},
|
||||
{
|
||||
"Index": 4,
|
||||
"Name": "water",
|
||||
"File": "omegaimagelist1_04_water.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 5,
|
||||
"Name": "lives",
|
||||
"File": "omegaimagelist1_05_lives.png",
|
||||
"TileWidth": 34,
|
||||
"TileHeight": 34
|
||||
},
|
||||
{
|
||||
"Index": 6,
|
||||
"Name": "score",
|
||||
"File": "omegaimagelist1_06_score.png",
|
||||
"TileWidth": 69,
|
||||
"TileHeight": 20
|
||||
},
|
||||
{
|
||||
"Index": 7,
|
||||
"Name": "enemy2",
|
||||
"File": "omegaimagelist1_07_enemy2.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 8,
|
||||
"Name": "enemybullet",
|
||||
"File": "omegaimagelist1_08_enemybullet.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 9,
|
||||
"Name": "boss",
|
||||
"File": "omegaimagelist1_09_boss.png",
|
||||
"TileWidth": 98,
|
||||
"TileHeight": 98
|
||||
},
|
||||
{
|
||||
"Index": 10,
|
||||
"Name": "sub",
|
||||
"File": "omegaimagelist1_10_sub.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 98
|
||||
},
|
||||
{
|
||||
"Index": 11,
|
||||
"Name": "bigexplosion",
|
||||
"File": "omegaimagelist1_11_bigexplosion.png",
|
||||
"TileWidth": 65,
|
||||
"TileHeight": 65
|
||||
},
|
||||
{
|
||||
"Index": 12,
|
||||
"Name": "turret",
|
||||
"File": "omegaimagelist1_12_turret.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 13,
|
||||
"Name": "boss2",
|
||||
"File": "omegaimagelist1_13_boss2.png",
|
||||
"TileWidth": 161,
|
||||
"TileHeight": 142
|
||||
},
|
||||
{
|
||||
"Index": 14,
|
||||
"Name": "cruiser",
|
||||
"File": "omegaimagelist1_14_cruiser.png",
|
||||
"TileWidth": 41,
|
||||
"TileHeight": 197
|
||||
},
|
||||
{
|
||||
"Index": 15,
|
||||
"Name": "tactical",
|
||||
"File": "omegaimagelist1_15_tactical.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 16,
|
||||
"Name": "player2",
|
||||
"File": "omegaimagelist1_16_player2.png",
|
||||
"TileWidth": 73,
|
||||
"TileHeight": 45
|
||||
},
|
||||
{
|
||||
"Index": 17,
|
||||
"Name": "mine",
|
||||
"File": "omegaimagelist1_17_mine.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 18,
|
||||
"Name": "missile_turret",
|
||||
"File": "omegaimagelist1_18_missile_turret.png",
|
||||
"TileWidth": 64,
|
||||
"TileHeight": 64
|
||||
},
|
||||
{
|
||||
"Index": 19,
|
||||
"Name": "missile",
|
||||
"File": "omegaimagelist1_19_missile.png",
|
||||
"TileWidth": 16,
|
||||
"TileHeight": 16
|
||||
},
|
||||
{
|
||||
"Index": 20,
|
||||
"Name": "scanner",
|
||||
"File": "omegaimagelist1_20_scanner.png",
|
||||
"TileWidth": 800,
|
||||
"TileHeight": 32
|
||||
}
|
||||
],
|
||||
"OmegaImageList2": [
|
||||
{
|
||||
"Index": 0,
|
||||
"Name": "numbers",
|
||||
"File": "omegaimagelist2_00_numbers.png",
|
||||
"TileWidth": 12,
|
||||
"TileHeight": 15
|
||||
},
|
||||
{
|
||||
"Index": 1,
|
||||
"Name": "title",
|
||||
"File": "omegaimagelist2_01_title.png",
|
||||
"TileWidth": 278,
|
||||
"TileHeight": 141
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
"Name": "gameover",
|
||||
"File": "omegaimagelist2_02_gameover.png",
|
||||
"TileWidth": 97,
|
||||
"TileHeight": 16
|
||||
},
|
||||
{
|
||||
"Index": 3,
|
||||
"Name": "title2",
|
||||
"File": "omegaimagelist2_03_title2.png",
|
||||
"TileWidth": 360,
|
||||
"TileHeight": 60
|
||||
},
|
||||
{
|
||||
"Index": 4,
|
||||
"Name": "newgame",
|
||||
"File": "omegaimagelist2_04_newgame.png",
|
||||
"TileWidth": 360,
|
||||
"TileHeight": 60
|
||||
},
|
||||
{
|
||||
"Index": 5,
|
||||
"Name": "options",
|
||||
"File": "omegaimagelist2_05_options.png",
|
||||
"TileWidth": 360,
|
||||
"TileHeight": 60
|
||||
},
|
||||
{
|
||||
"Index": 6,
|
||||
"Name": "highscore",
|
||||
"File": "omegaimagelist2_06_highscore.png",
|
||||
"TileWidth": 360,
|
||||
"TileHeight": 60
|
||||
},
|
||||
{
|
||||
"Index": 7,
|
||||
"Name": "credits",
|
||||
"File": "omegaimagelist2_07_credits.png",
|
||||
"TileWidth": 360,
|
||||
"TileHeight": 60
|
||||
},
|
||||
{
|
||||
"Index": 8,
|
||||
"Name": "quit",
|
||||
"File": "omegaimagelist2_08_quit.png",
|
||||
"TileWidth": 360,
|
||||
"TileHeight": 60
|
||||
}
|
||||
],
|
||||
"OmegaImageList3": [
|
||||
{
|
||||
"Index": 0,
|
||||
"Name": "bar",
|
||||
"File": "omegaimagelist3_00_bar.png",
|
||||
"TileWidth": 130,
|
||||
"TileHeight": 14
|
||||
},
|
||||
{
|
||||
"Index": 1,
|
||||
"Name": "health",
|
||||
"File": "omegaimagelist3_01_health.png",
|
||||
"TileWidth": 7,
|
||||
"TileHeight": 10
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
"Name": "weapon",
|
||||
"File": "omegaimagelist3_02_weapon.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 3,
|
||||
"Name": "energy",
|
||||
"File": "omegaimagelist3_03_energy.png",
|
||||
"TileWidth": 7,
|
||||
"TileHeight": 10
|
||||
}
|
||||
],
|
||||
"OmegaImageList4": [
|
||||
{
|
||||
"Index": 0,
|
||||
"Name": "Tiles",
|
||||
"File": "omegaimagelist4_00_Tiles.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 1,
|
||||
"Name": "collision",
|
||||
"File": "omegaimagelist4_01_collision.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
"Name": "trigger",
|
||||
"File": "omegaimagelist4_02_trigger.png",
|
||||
"TileWidth": 32,
|
||||
"TileHeight": 32
|
||||
}
|
||||
],
|
||||
"OmegaImageList5": [
|
||||
{
|
||||
"Index": 0,
|
||||
"Name": "",
|
||||
"File": "omegaimagelist5_00_unnamed.png",
|
||||
"TileWidth": 9,
|
||||
"TileHeight": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
FreedomFighter/Content/gfx/omegaimagelist1_00_player.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_01_enemy.png
Normal file
|
After Width: | Height: | Size: 937 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_02_bullet.png
Normal file
|
After Width: | Height: | Size: 446 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_03_explosion.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_04_water.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_05_lives.png
Normal file
|
After Width: | Height: | Size: 624 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_06_score.png
Normal file
|
After Width: | Height: | Size: 687 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_07_enemy2.png
Normal file
|
After Width: | Height: | Size: 1012 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_08_enemybullet.png
Normal file
|
After Width: | Height: | Size: 271 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_09_boss.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_10_sub.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_11_bigexplosion.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_12_turret.png
Normal file
|
After Width: | Height: | Size: 440 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_13_boss2.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_14_cruiser.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_15_tactical.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_16_player2.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_17_mine.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_18_missile_turret.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_19_missile.png
Normal file
|
After Width: | Height: | Size: 962 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist1_20_scanner.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_00_numbers.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_01_title.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_02_gameover.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_03_title2.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_04_newgame.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_05_options.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_06_highscore.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_07_credits.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist2_08_quit.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist3_00_bar.png
Normal file
|
After Width: | Height: | Size: 206 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist3_01_health.png
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist3_02_weapon.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist3_03_energy.png
Normal file
|
After Width: | Height: | Size: 126 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist4_00_Tiles.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
FreedomFighter/Content/gfx/omegaimagelist4_01_collision.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist4_02_trigger.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
FreedomFighter/Content/gfx/omegaimagelist5_00_unnamed.png
Normal file
|
After Width: | Height: | Size: 663 B |
1
FreedomFighter/Content/maps/bonus.json
Normal file
1
FreedomFighter/Content/maps/intro.json
Normal file
1
FreedomFighter/Content/maps/lvl5.json
Normal file
1
FreedomFighter/Content/maps/test10.json
Normal file
1
FreedomFighter/Content/maps/test7.json
Normal file
1
FreedomFighter/Content/maps/test8.json
Normal file
BIN
FreedomFighter/Content/music/G2 Final.mp3
Normal file
BIN
FreedomFighter/Content/music/G8 Final.mp3
Normal file
BIN
FreedomFighter/Content/music/Intro.mp3
Normal file
BIN
FreedomFighter/Content/music/Main Menu 2.mp3
Normal file
BIN
FreedomFighter/Content/music/Nazi_Rap.mid
Normal file
BIN
FreedomFighter/Content/music/Thunderization.mp3
Normal file
BIN
FreedomFighter/Content/music/Track06.mp3
Normal file
BIN
FreedomFighter/Content/music/white light.mp3
Normal file
BIN
FreedomFighter/Content/sfx/beep.wav
Normal file
BIN
FreedomFighter/Content/sfx/explode2.wav
Normal file
BIN
FreedomFighter/Content/sfx/fire1.wav
Normal file
BIN
FreedomFighter/Content/sfx/hit4.wav
Normal file
BIN
FreedomFighter/Content/sfx/missile.wav
Normal file
BIN
FreedomFighter/Content/sfx/reload.wav
Normal file
BIN
FreedomFighter/Content/sfx/thump1.wav
Normal file
BIN
FreedomFighter/Content/tilesets/test5_00.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
FreedomFighter/Content/tilesets/test5_01.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
FreedomFighter/Content/tilesets/test5_02.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
FreedomFighter/Content/tilesets/test5_03.png
Normal file
|
After Width: | Height: | Size: 940 B |
BIN
FreedomFighter/Content/tilesets/test6_00.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
FreedomFighter/Content/tilesets/test6_01.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
FreedomFighter/Content/tilesets/test6_02.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
FreedomFighter/Content/tilesets/test_00.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
FreedomFighter/Content/tilesets/test_01.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
FreedomFighter/Content/tilesets/test_02.png
Normal file
|
After Width: | Height: | Size: 234 B |
570
FreedomFighter/Entities.cs
Normal file
@@ -0,0 +1,570 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
// Entity classes ported 1:1 from GameU.pas, including quirks
|
||||
// (e.g. TEnemy moves inside Draw, missiles can target stray sprites).
|
||||
|
||||
public class Player : Sprite
|
||||
{
|
||||
public int Health;
|
||||
public int FireRate;
|
||||
public int Power;
|
||||
public Weapon Weapon;
|
||||
|
||||
public Player(SpriteManager m) : base(m) { }
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
var input = G.Input;
|
||||
|
||||
if (input.Button5Clicked) { G.Music.Songs[6].Play(); Weapon = Weapon.Reg; }
|
||||
if (input.Button6Clicked) { G.Music.Songs[6].Play(); Weapon = Weapon.Super; }
|
||||
if (input.Button7Clicked) { G.Music.Songs[6].Play(); Weapon = Weapon.Multi; }
|
||||
|
||||
if (FireRate > 1)
|
||||
FireRate--;
|
||||
|
||||
if (input.Button4 && FireRate == 1 && Weapon == Weapon.Reg)
|
||||
Bullet.Spawn(X + Width / 2 - 16, Y - 34, 0, 3, 0);
|
||||
else if (input.Button4 && FireRate == 1 && Weapon == Weapon.Super)
|
||||
{
|
||||
Bullet.Spawn(X + Width / 2 - 16 - 12, Y - 34, 0, 3, 0);
|
||||
Bullet.Spawn(X + Width / 2 - 16 + 12, Y - 34, 0, 3, 0);
|
||||
}
|
||||
if (input.Button4 && FireRate == 1 && Weapon == Weapon.Multi)
|
||||
{
|
||||
Bullet.Spawn(X + Width / 2 - 16, Y - 34, 2, 3, -35);
|
||||
Bullet.Spawn(X + Width / 2 - 16, Y - 34, 0, 3, 0);
|
||||
Bullet.Spawn(X + Width / 2 - 16, Y - 34, -2, 3, 35);
|
||||
}
|
||||
|
||||
if (input.Button8 && FireRate == 1)
|
||||
{
|
||||
G.Music.Songs[13].Play();
|
||||
Missile.Spawn(X + Width / 2, Y, 2);
|
||||
FireRate = 20;
|
||||
}
|
||||
|
||||
if (input.Left && input.Up) { X -= 3; Y -= 3; }
|
||||
else if (input.Left && input.Down) { X -= 3; Y += 3; }
|
||||
else if (input.Right && input.Up) { X += 3; Y -= 3; }
|
||||
else if (input.Right && input.Down) { X += 3; Y += 3; }
|
||||
else if (input.Left) X -= 3;
|
||||
else if (input.Right) X += 3;
|
||||
else if (input.Up) Y -= 3;
|
||||
else if (input.Down) Y += 3;
|
||||
|
||||
if (X + Width > 800) X = 800 - Width;
|
||||
if (X < 0) X = 0;
|
||||
if (Y + Height > 600) Y = 600 - Height;
|
||||
if (Y < 0) Y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class DemoShip : Sprite
|
||||
{
|
||||
public DemoShip(SpriteManager m) : base(m) { }
|
||||
}
|
||||
|
||||
public class Enemy : Sprite
|
||||
{
|
||||
public float YVel;
|
||||
public int Health;
|
||||
public string MoveDir = "";
|
||||
public AI AI;
|
||||
public bool Charging;
|
||||
public int HitTimer;
|
||||
public int FireRate;
|
||||
public int ExplodeTime;
|
||||
public int AnimTicks;
|
||||
|
||||
public Enemy(SpriteManager m) : base(m) { }
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
if (HitTimer < 255)
|
||||
{
|
||||
HitTimer += 15;
|
||||
SetBlue(HitTimer);
|
||||
SetGreen(HitTimer);
|
||||
}
|
||||
|
||||
switch (AI)
|
||||
{
|
||||
case AI.Normal:
|
||||
case AI.Hunt:
|
||||
if (G.Random(1000) < 5)
|
||||
EnemyBullet.Spawn(X, Y, 3, 0);
|
||||
break;
|
||||
|
||||
case AI.Tactical:
|
||||
Y += YVel;
|
||||
if (!Charging && Y > 200)
|
||||
{
|
||||
YVel = 1;
|
||||
G.TFCounter++;
|
||||
if (G.TFCounter > 16)
|
||||
{
|
||||
G.TFCounter = 0;
|
||||
if (ImageIndex < 4)
|
||||
ImageIndex++;
|
||||
else
|
||||
{
|
||||
Charging = true;
|
||||
YVel = -2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Charging && Y < -100)
|
||||
{
|
||||
ImageIndex = 0;
|
||||
YVel = 5;
|
||||
}
|
||||
break;
|
||||
|
||||
case AI.Mine:
|
||||
if (AnimTicks > ExplodeTime)
|
||||
{
|
||||
Kill();
|
||||
Explosion.Spawn(X, Y, false, false);
|
||||
for (int angle = 360; angle >= 0; angle -= 45)
|
||||
EnemyBullet.Spawn(X, Y, 2, angle);
|
||||
}
|
||||
break;
|
||||
|
||||
case AI.Turret:
|
||||
if (FireRate < 0)
|
||||
{
|
||||
EnemyBullet.Spawn(X + 16, Y + 16, 3, Rotation);
|
||||
FireRate = 40;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Y > G.ScreenHeight)
|
||||
Kill();
|
||||
}
|
||||
|
||||
public override void OnCollision(Sprite sprite, int colX, int colY)
|
||||
{
|
||||
if (sprite is Player)
|
||||
{
|
||||
G.Music.Songs[11].Play();
|
||||
Kill();
|
||||
G.Player.Health--;
|
||||
Explosion.Spawn(X, Y, false, false);
|
||||
}
|
||||
|
||||
if (sprite is Bullet)
|
||||
{
|
||||
Health--;
|
||||
sprite.Kill();
|
||||
|
||||
if (Health < 1)
|
||||
{
|
||||
Explosion.Spawn(X, Y, false, false);
|
||||
G.Music.Songs[11].Play();
|
||||
Kill();
|
||||
G.ShipsKilled++;
|
||||
if (G.Player.Power < 18) G.Player.Power++;
|
||||
G.Score += 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
HitTimer = 0;
|
||||
G.Music.Songs[10].Volume = 60;
|
||||
G.Music.Songs[10].Play();
|
||||
}
|
||||
}
|
||||
|
||||
if (sprite is Missile)
|
||||
{
|
||||
Health -= 5;
|
||||
sprite.Kill();
|
||||
|
||||
G.Music.Songs[10].Volume = 60;
|
||||
G.Music.Songs[10].Play();
|
||||
|
||||
if (Health < 1)
|
||||
{
|
||||
Explosion.Spawn(X, Y, false, false);
|
||||
G.Music.Songs[11].Play();
|
||||
Kill();
|
||||
G.ShipsKilled++;
|
||||
if (G.Player.Power < 18) G.Player.Power++;
|
||||
G.Score += 10;
|
||||
}
|
||||
else
|
||||
HitTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// The original moved enemies inside Draw; preserved for identical behavior.
|
||||
public override void Draw(SpriteBatch sb)
|
||||
{
|
||||
base.Draw(sb);
|
||||
|
||||
switch (AI)
|
||||
{
|
||||
case AI.Normal:
|
||||
case AI.None:
|
||||
Y += YVel;
|
||||
break;
|
||||
|
||||
case AI.Hunt:
|
||||
Y += YVel;
|
||||
if (Y > 200)
|
||||
{
|
||||
Y += YVel; // double speed
|
||||
if (G.Player.X > X) X++;
|
||||
if (G.Player.X < X) X--;
|
||||
}
|
||||
break;
|
||||
|
||||
case AI.Mine:
|
||||
Y += 1.5f;
|
||||
Rotation += 0.5f;
|
||||
if (Rotation > 359) Rotation = 0;
|
||||
AnimTicks++;
|
||||
break;
|
||||
|
||||
case AI.Turret:
|
||||
Rotation = (float)(180 / Math.PI * Math.Atan2(Y - G.Player.Y, X - G.Player.X)) + 90;
|
||||
if (Rotation > 360) Rotation -= 360;
|
||||
else if (Rotation < 0) Rotation += 360;
|
||||
FireRate--;
|
||||
Y += YVel;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Y > G.ScreenHeight)
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public class Boss : Sprite
|
||||
{
|
||||
public float YVel;
|
||||
public int Health;
|
||||
public string MoveDir = "";
|
||||
public int HitTimer;
|
||||
public AI AI;
|
||||
public int Timer;
|
||||
public int RandX;
|
||||
|
||||
public Boss(SpriteManager m) : base(m) { }
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
if (HitTimer < 255)
|
||||
{
|
||||
HitTimer += 15;
|
||||
SetBlue(HitTimer);
|
||||
SetGreen(HitTimer);
|
||||
}
|
||||
|
||||
if (AI == AI.Boss)
|
||||
{
|
||||
if (G.Random(1000) < 10)
|
||||
{
|
||||
EnemyBullet.Spawn(X, Y + 40, 3, 0);
|
||||
EnemyBullet.Spawn(X + 32, Y + 40, 3, 0);
|
||||
EnemyBullet.Spawn(X + 64, Y + 40, 3, 0);
|
||||
}
|
||||
|
||||
Y += YVel;
|
||||
|
||||
if (Y > 300)
|
||||
{
|
||||
Y = 300;
|
||||
if (MoveDir == "right") X++;
|
||||
if (MoveDir == "left") X--;
|
||||
|
||||
if (X > G.ScreenWidth - 98 && MoveDir == "right")
|
||||
{
|
||||
X = G.ScreenWidth - 98;
|
||||
MoveDir = "left";
|
||||
}
|
||||
if (X < 0 && MoveDir == "left")
|
||||
{
|
||||
MoveDir = "right";
|
||||
X = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (AI == AI.Boss2)
|
||||
{
|
||||
Timer++;
|
||||
if (Timer > 200)
|
||||
{
|
||||
RandX = G.Random(10);
|
||||
Timer = 0;
|
||||
}
|
||||
|
||||
if (MoveDir == "down" && Timer % 25 == 0)
|
||||
{
|
||||
EnemyBullet.Spawn(X + 40, Y + Height, 3, 0);
|
||||
EnemyBullet.Spawn(X + 50, Y + Height, 3, 0);
|
||||
EnemyBullet.Spawn(X + 60, Y + Height, 3, 0);
|
||||
EnemyBullet.Spawn(X + 70, Y + Height, 3, 0);
|
||||
}
|
||||
|
||||
if (RandX > 7) X++;
|
||||
else if (RandX > 2) { /* hold */ }
|
||||
else X--;
|
||||
|
||||
if (MoveDir == "down") Y++;
|
||||
if (MoveDir == "up") Y -= 2;
|
||||
|
||||
if (Y > 300 && MoveDir == "down") { Y = 300; MoveDir = "up"; }
|
||||
if (Y < -100 && MoveDir == "up") { MoveDir = "down"; Y = -100; }
|
||||
|
||||
if (X > 800 - Width) { X = 800 - Width; RandX = 0; }
|
||||
if (X < 0) { X = 0; RandX = 8; }
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCollision(Sprite sprite, int colX, int colY)
|
||||
{
|
||||
if (sprite is Bullet)
|
||||
{
|
||||
sprite.Kill();
|
||||
Health--;
|
||||
HitTimer = 0;
|
||||
|
||||
if (Health == 0)
|
||||
{
|
||||
Kill();
|
||||
Explosion.Spawn(X, Y, true, true);
|
||||
G.Score += 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (sprite is Missile)
|
||||
{
|
||||
sprite.Kill();
|
||||
Health -= 5;
|
||||
HitTimer = 0;
|
||||
|
||||
if (Health < 1)
|
||||
{
|
||||
Kill();
|
||||
Explosion.Spawn(X, Y, true, true);
|
||||
G.Score += 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Bullet : Sprite
|
||||
{
|
||||
public float XVel, YVel;
|
||||
|
||||
public Bullet(SpriteManager m) : base(m) { }
|
||||
|
||||
public static void Spawn(float x, float y, float xVel, float yVel, float rotation)
|
||||
{
|
||||
var b = new Bullet(G.Sprites)
|
||||
{
|
||||
Width = 34,
|
||||
Height = 34,
|
||||
X = x,
|
||||
Y = y,
|
||||
XVel = xVel,
|
||||
YVel = yVel,
|
||||
Rotation = rotation,
|
||||
Image = G.Images.IL1[2],
|
||||
ImageIndex = 0,
|
||||
CenterX = 0.5f,
|
||||
CenterY = 0.5f,
|
||||
DoCollision = true,
|
||||
DoPixelCheck = true,
|
||||
};
|
||||
G.Player.FireRate = 20;
|
||||
}
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
X -= XVel;
|
||||
Y -= YVel;
|
||||
|
||||
if (Y > 600 || Y < 0 || X > 800 || X < 0)
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public class EnemyBullet : Sprite
|
||||
{
|
||||
public float XVel, YVel;
|
||||
|
||||
public EnemyBullet(SpriteManager m) : base(m) { }
|
||||
|
||||
public static void Spawn(float x, float y, float vel, float rotation)
|
||||
{
|
||||
double rad = Math.PI / 180 * (rotation + 90);
|
||||
_ = new EnemyBullet(G.Sprites)
|
||||
{
|
||||
Name = "EnemyBullet",
|
||||
X = x,
|
||||
Y = y,
|
||||
Rotation = rotation,
|
||||
CenterX = 0.5f,
|
||||
CenterY = 0.5f,
|
||||
XVel = vel * (float)Math.Cos(rad),
|
||||
YVel = vel * (float)Math.Sin(rad),
|
||||
Width = 32,
|
||||
Height = 32,
|
||||
Image = G.Images.IL1[8],
|
||||
ImageIndex = 0,
|
||||
DoCollision = true,
|
||||
DoPixelCheck = true,
|
||||
};
|
||||
}
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
X += XVel;
|
||||
Y += YVel;
|
||||
|
||||
if (X > G.ScreenWidth || X < 0 || Y > G.ScreenHeight || Y < 0)
|
||||
Kill();
|
||||
}
|
||||
|
||||
public override void OnCollision(Sprite sprite, int colX, int colY)
|
||||
{
|
||||
if (sprite is Player)
|
||||
{
|
||||
Kill();
|
||||
G.Player.Health--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Missile : Sprite
|
||||
{
|
||||
public float XVel, YVel;
|
||||
public Sprite? Target;
|
||||
|
||||
public Missile(SpriteManager m) : base(m) { }
|
||||
|
||||
public static void Spawn(float x, float y, float yVel)
|
||||
{
|
||||
var missile = new Missile(G.Sprites)
|
||||
{
|
||||
Name = "Missile",
|
||||
X = x,
|
||||
Y = y,
|
||||
Rotation = 180,
|
||||
YVel = yVel,
|
||||
Width = 16,
|
||||
Height = 16,
|
||||
Image = G.Images.IL1[19],
|
||||
ImageIndex = 0,
|
||||
DoCollision = true,
|
||||
DoPixelCheck = true,
|
||||
};
|
||||
missile.Target = missile.GetTarget();
|
||||
}
|
||||
|
||||
public Sprite? GetTarget()
|
||||
{
|
||||
Sprite? target = null;
|
||||
float prevDist = 800;
|
||||
|
||||
foreach (var s in G.Sprites.Items)
|
||||
{
|
||||
float dx = X - s.X;
|
||||
float dy = Y - s.Y;
|
||||
float distance = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// faithful to the original: filters by Name only, so unnamed sprites
|
||||
// (fighters, but also player bullets and explosions) are all fair game
|
||||
if (distance < prevDist && s.Name != "Player" && s.Name != "EnemyBullet" && s.Name != "Missile")
|
||||
{
|
||||
target = s;
|
||||
prevDist = distance;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
if (Target != null)
|
||||
{
|
||||
if (Y > Target.Y)
|
||||
{
|
||||
Rotation = (float)(180 / Math.PI * Math.Atan2(Y - Target.Y, X - Target.X)) + 90;
|
||||
if (Rotation > 360) Rotation -= 360;
|
||||
else if (Rotation < 0) Rotation += 360;
|
||||
}
|
||||
double rad = Math.PI / 180 * (Rotation + 90);
|
||||
X += (float)Math.Cos(rad);
|
||||
Y += YVel * (float)Math.Sin(rad);
|
||||
}
|
||||
else
|
||||
{
|
||||
double rad = Math.PI / 180 * (Rotation + 90);
|
||||
Y += YVel * (float)Math.Sin(rad);
|
||||
X += (float)Math.Cos(rad);
|
||||
Target = GetTarget();
|
||||
}
|
||||
|
||||
if (Y < 0)
|
||||
Kill();
|
||||
if (X < 0 || X > G.ScreenWidth)
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
|
||||
public class Explosion : Sprite
|
||||
{
|
||||
public int MaxFrames;
|
||||
public int ECounter;
|
||||
public bool IsBoss;
|
||||
|
||||
public Explosion(SpriteManager m) : base(m) { }
|
||||
|
||||
public static void Spawn(float x, float y, bool big, bool boss)
|
||||
{
|
||||
int frames = big ? 6 : 5;
|
||||
int imageIdx = big ? 11 : 3;
|
||||
int tileWidth = big ? 65 : 32;
|
||||
int tileHeight = big ? 65 : 34;
|
||||
|
||||
_ = new Explosion(G.Sprites)
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Width = tileWidth,
|
||||
Height = tileHeight,
|
||||
Image = G.Images.IL1[imageIdx],
|
||||
ImageIndex = 0,
|
||||
DoCollision = false,
|
||||
MaxFrames = frames,
|
||||
ECounter = 0,
|
||||
IsBoss = boss,
|
||||
};
|
||||
}
|
||||
|
||||
public override void Move(float moveCount)
|
||||
{
|
||||
Y += 1;
|
||||
ECounter++;
|
||||
|
||||
if (ECounter > 6)
|
||||
{
|
||||
ImageIndex++;
|
||||
ECounter = 0;
|
||||
}
|
||||
|
||||
if (ImageIndex > MaxFrames)
|
||||
{
|
||||
if (IsBoss)
|
||||
G.Done = true;
|
||||
Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
FreedomFighter/FreedomFighter.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RollForward>Major</RollForward>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoGame.Framework.WindowsDX" Version="3.8.*" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Content\gfx\**;Content\maps\**;Content\tilesets\**;Content\sfx\**;Content\music\**" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
<Target Name="RestoreDotnetTools" BeforeTargets="CollectPackageReferences">
|
||||
<Message Text="Restoring dotnet tools (this might take a while depending on your internet speed and should only happen upon building your project for the first time, or after upgrading MonoGame, or clearing your nuget cache)" Importance="High" />
|
||||
<Exec Command="dotnet tool restore" />
|
||||
</Target>
|
||||
</Project>
|
||||
74
FreedomFighter/G.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
public enum GameState { Intro, Idle, Intermission, Playing, Stats, Shop, GameOver, Credits, HighScore, EnterName }
|
||||
|
||||
public enum Weapon { Reg, Super, Multi }
|
||||
|
||||
public enum AI { Normal, Hunt, Tactical, Mine, Turret, Boss, Boss2, None }
|
||||
|
||||
public enum ObjectType { Fighter, Kamikaze, Tactical, Mine, MissileBase, MissileTurret, Scanner, Boss, Boss2 }
|
||||
|
||||
public struct Camera
|
||||
{
|
||||
public int X, Y, Width, Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The global variables from GameU.pas, kept global to preserve the original's structure.
|
||||
/// </summary>
|
||||
public static class G
|
||||
{
|
||||
public const int ScreenWidth = 800;
|
||||
public const int ScreenHeight = 600;
|
||||
public const string Version = "BETA 5";
|
||||
|
||||
public const int MUS_LEVEL1 = 0;
|
||||
public const int MUS_GAMEOVER = 3;
|
||||
public const int MUS_TITLE = 4;
|
||||
|
||||
public static Game1 Game = null!;
|
||||
public static SpriteManager Sprites = null!;
|
||||
public static ImageLists Images = null!;
|
||||
public static MusicManager Music = null!;
|
||||
public static GameMap Map = null!;
|
||||
public static OmegaInput Input = null!;
|
||||
public static HighScores HighScores = null!;
|
||||
public static Random Rnd = new();
|
||||
|
||||
public static GameState State;
|
||||
public static bool BossMode;
|
||||
public static bool Done;
|
||||
|
||||
public static Player Player = null!;
|
||||
|
||||
public static int Counter;
|
||||
public static int Change;
|
||||
public static int CurCount;
|
||||
|
||||
public static int Score;
|
||||
public static int Lives;
|
||||
public static int ShipsKilled;
|
||||
public static int Level;
|
||||
|
||||
public static Camera Camera;
|
||||
public static int NewCamX, NewCamY;
|
||||
|
||||
public static int TextNum;
|
||||
public static bool TextCreated;
|
||||
public static int TFCounter;
|
||||
|
||||
public static int CurSelection = 4;
|
||||
public static int[] MainMenu = new int[5];
|
||||
public static int Prev = 4;
|
||||
|
||||
public static GameText GameTextObj = null!;
|
||||
public static GameText TitleText = null!;
|
||||
public static GameText IdText = null!;
|
||||
|
||||
public static BitmapFont BitmapFont1 = null!; // numbers
|
||||
public static BitmapFont BitmapFont2 = null!; // A-Z
|
||||
|
||||
public static int Random(int max) => max <= 0 ? 0 : Rnd.Next(max);
|
||||
}
|
||||
943
FreedomFighter/Game1.cs
Normal file
@@ -0,0 +1,943 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Port of GameU.pas. The original ran all logic inside the OmegaTimer render
|
||||
/// callback, so this port keeps that structure: everything happens in Draw(),
|
||||
/// paced by a 120 Hz fixed timestep (the original's MaxFPS).
|
||||
/// </summary>
|
||||
public class Game1 : Game
|
||||
{
|
||||
readonly GraphicsDeviceManager _graphics;
|
||||
SpriteBatch _sb = null!;
|
||||
SpriteFont _font12 = null!;
|
||||
SpriteFont _font20 = null!;
|
||||
|
||||
bool _paused;
|
||||
string _enteredName = "";
|
||||
DemoShip? _demoShip;
|
||||
|
||||
// debug screenshot support (--shot)
|
||||
public string? ShotFile;
|
||||
public int ShotFrames = 30;
|
||||
public int StartLevel;
|
||||
|
||||
// FPS measurement (the original drew OmegaTimer1.FPS)
|
||||
int _fps, _fpsFrames;
|
||||
double _fpsTimer;
|
||||
|
||||
public Game1()
|
||||
{
|
||||
_graphics = new GraphicsDeviceManager(this)
|
||||
{
|
||||
PreferredBackBufferWidth = G.ScreenWidth,
|
||||
PreferredBackBufferHeight = G.ScreenHeight,
|
||||
SynchronizeWithVerticalRetrace = false,
|
||||
};
|
||||
Content.RootDirectory = "Content";
|
||||
IsMouseVisible = true;
|
||||
IsFixedTimeStep = true;
|
||||
TargetElapsedTime = TimeSpan.FromSeconds(1 / 120.0);
|
||||
Window.Title = "Freedom Fighter";
|
||||
}
|
||||
|
||||
protected override void LoadContent()
|
||||
{
|
||||
_sb = new SpriteBatch(GraphicsDevice);
|
||||
_font12 = Content.Load<SpriteFont>("Verdana12");
|
||||
_font20 = Content.Load<SpriteFont>("Verdana20");
|
||||
|
||||
string contentDir = Path.Combine(AppContext.BaseDirectory, "Content");
|
||||
|
||||
G.Game = this;
|
||||
G.Sprites = new SpriteManager();
|
||||
G.Images = new ImageLists(GraphicsDevice, contentDir);
|
||||
G.Music = new MusicManager();
|
||||
G.Music.Load(contentDir);
|
||||
G.Map = new GameMap(GraphicsDevice, contentDir);
|
||||
G.Input = new OmegaInput();
|
||||
G.HighScores = new HighScores();
|
||||
G.HighScores.Load();
|
||||
|
||||
// OmegaScreen1Init: bitmap font setup
|
||||
G.BitmapFont1 = new BitmapFont(G.Images.IL2[0], "0123456789");
|
||||
G.BitmapFont2 = new BitmapFont(G.Images.IL5[0], "ABCDEFGHIJKLMNOPQRSTUVWXYZ ");
|
||||
|
||||
// FormCreate
|
||||
G.GameTextObj = new GameText("", 60, G.BitmapFont2);
|
||||
G.BitmapFont2.Red = 0;
|
||||
G.BitmapFont2.Green = 200;
|
||||
G.BitmapFont2.Blue = 200;
|
||||
|
||||
G.State = GameState.Idle;
|
||||
G.MainMenu = [0, 0, 0, 0, 1];
|
||||
G.Prev = 4;
|
||||
|
||||
G.Camera.Width = G.ScreenWidth;
|
||||
G.Camera.Height = G.ScreenHeight;
|
||||
|
||||
G.Music.Songs[G.MUS_TITLE].Volume = 25;
|
||||
G.Music.Songs[G.MUS_TITLE].Play();
|
||||
|
||||
if (StartLevel > 0) // debug: jump straight into a level
|
||||
{
|
||||
ResetAll();
|
||||
G.Level = StartLevel;
|
||||
G.State = GameState.Intermission;
|
||||
G.Counter = 495;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(GameTime gameTime)
|
||||
{
|
||||
// all logic runs in Draw, matching the original's timer callback
|
||||
base.Update(gameTime);
|
||||
}
|
||||
|
||||
protected override void Draw(GameTime gameTime)
|
||||
{
|
||||
_fpsFrames++;
|
||||
_fpsTimer += gameTime.ElapsedGameTime.TotalSeconds;
|
||||
if (_fpsTimer >= 1) { _fps = _fpsFrames; _fpsFrames = 0; _fpsTimer -= 1; }
|
||||
|
||||
G.Input.Update();
|
||||
|
||||
if (G.Input.PauseClicked)
|
||||
_paused = !_paused;
|
||||
if (_paused)
|
||||
{
|
||||
base.Draw(gameTime);
|
||||
return;
|
||||
}
|
||||
|
||||
GraphicsDevice.Clear(Color.Black);
|
||||
_sb.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp);
|
||||
|
||||
G.Sprites.Move(0);
|
||||
|
||||
switch (G.State)
|
||||
{
|
||||
case GameState.Intro: DoIntro(); break;
|
||||
case GameState.Idle: DrawMenu(); break;
|
||||
case GameState.Intermission: DoIntermission(); break;
|
||||
case GameState.Playing: GameLoop(); break;
|
||||
case GameState.Stats: DoStats(); break;
|
||||
case GameState.Shop: DoShop(); break;
|
||||
case GameState.GameOver: DoGameOver(); break;
|
||||
case GameState.Credits: DoCredits(); break;
|
||||
case GameState.HighScore: DoHighScore(); break;
|
||||
case GameState.EnterName: DoEnterName(); break;
|
||||
}
|
||||
|
||||
G.Sprites.Collision();
|
||||
G.Sprites.RemoveDead();
|
||||
G.Sprites.Draw(_sb);
|
||||
|
||||
_sb.End();
|
||||
base.Draw(gameTime);
|
||||
|
||||
if (ShotFile != null && --ShotFrames <= 0)
|
||||
{
|
||||
SaveScreenshot(ShotFile);
|
||||
ShotFile = null;
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveScreenshot(string file)
|
||||
{
|
||||
int w = GraphicsDevice.PresentationParameters.BackBufferWidth;
|
||||
int h = GraphicsDevice.PresentationParameters.BackBufferHeight;
|
||||
var data = new Color[w * h];
|
||||
GraphicsDevice.GetBackBufferData(data);
|
||||
using var tex = new Texture2D(GraphicsDevice, w, h);
|
||||
tex.SetData(data);
|
||||
using var fs = File.Create(file);
|
||||
tex.SaveAsPng(fs, w, h);
|
||||
}
|
||||
|
||||
// ================================================================ helpers
|
||||
|
||||
void PrintFps(int x, int y) =>
|
||||
_sb.DrawString(_font12, "FPS: " + _fps, new Vector2(x, y), Color.White);
|
||||
|
||||
void ResetAll()
|
||||
{
|
||||
G.Change = 0;
|
||||
G.Score = 0;
|
||||
G.Lives = 3;
|
||||
}
|
||||
|
||||
void ResetPlayer()
|
||||
{
|
||||
var p = new Player(G.Sprites) { Name = "Player" };
|
||||
// original computed X before assigning Width (Width was still 0)
|
||||
p.X = G.ScreenWidth / 2 - p.Width;
|
||||
p.Y = G.ScreenHeight - 80;
|
||||
p.Health = 16;
|
||||
p.FireRate = 20;
|
||||
p.Width = 73;
|
||||
p.Height = 45;
|
||||
p.Image = G.Images.IL1[0];
|
||||
p.Weapon = Weapon.Reg;
|
||||
p.ImageIndex = 0;
|
||||
p.DoPixelCheck = true;
|
||||
p.DoCollision = true;
|
||||
p.CenterX = 0.5f;
|
||||
p.CenterY = 0.5f;
|
||||
G.Player = p;
|
||||
}
|
||||
|
||||
void ClipCamera(float x, float y, int width, int height)
|
||||
{
|
||||
if (x < G.Camera.Width / 2f)
|
||||
G.NewCamX = 0;
|
||||
else
|
||||
G.NewCamX = (int)(x + width / 2f - G.Camera.Width / 2f);
|
||||
|
||||
if (y < G.Camera.Height / 2f)
|
||||
G.NewCamY = 0;
|
||||
else
|
||||
G.NewCamY = (int)(y + height / 2f - G.Camera.Height / 2f);
|
||||
|
||||
// the min/max clips were commented out in the original, leaving these unconditional
|
||||
G.NewCamX = G.Map.WidthCount * 32 - G.Camera.Width;
|
||||
G.NewCamY = G.Map.HeightCount * 32 - G.Camera.Height;
|
||||
|
||||
int frameCamSpeed = 1;
|
||||
if (G.NewCamX > G.Camera.X)
|
||||
{
|
||||
G.Camera.X += frameCamSpeed;
|
||||
if (G.NewCamX < G.Camera.X) G.Camera.X = G.NewCamX;
|
||||
}
|
||||
else if (G.NewCamX < G.Camera.X)
|
||||
{
|
||||
G.Camera.X -= frameCamSpeed;
|
||||
if (G.NewCamX > G.Camera.X) G.Camera.X = G.NewCamX;
|
||||
}
|
||||
// Y seek was commented out in the original
|
||||
}
|
||||
|
||||
void DrawLayer1()
|
||||
{
|
||||
var tileset = G.Map.Tileset;
|
||||
if (tileset == null) return;
|
||||
|
||||
for (int a = 0; a <= G.Camera.Width / 32 + 1; a++)
|
||||
for (int b = 0; b <= G.Camera.Height / 32 + 1; b++)
|
||||
{
|
||||
int xid = (a * 32 + G.Camera.X) / 32;
|
||||
int yid = (b * 32 + G.Camera.Y) / 32;
|
||||
int drawX = xid * 32 - G.Camera.X;
|
||||
int drawY = yid * 32 - G.Camera.Y;
|
||||
|
||||
if (xid < G.Map.WidthCount && yid < G.Map.HeightCount && xid >= 0 && yid >= 0)
|
||||
{
|
||||
int id = G.Map.Layer1[xid][yid];
|
||||
if (id != -1)
|
||||
tileset.Draw(_sb, drawX, drawY, 0, 0.5f, 0.5f, 1, 1, 255, 255, 255, 255, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MoveSelection(int curSelect)
|
||||
{
|
||||
G.MainMenu[G.Prev] = 0;
|
||||
G.MainMenu[curSelect] = 1;
|
||||
G.Prev = curSelect;
|
||||
}
|
||||
|
||||
// ================================================================ spawning
|
||||
|
||||
void SpawnObject(ObjectType type, float xPos, float yPos)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ObjectType.Fighter:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
X = xPos, Y = yPos, YVel = 1.5f, Width = 34, Height = 34,
|
||||
Image = G.Images.IL1[1], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
AI = AI.Normal, Health = 2, HitTimer = 255,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.Kamikaze:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
X = xPos, Y = yPos, YVel = 2, Width = 32, Height = 32,
|
||||
Image = G.Images.IL1[7], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
AI = AI.Hunt, Health = 3, HitTimer = 255,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.Tactical:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
X = xPos, Y = yPos, YVel = 3, Charging = false, Width = 32, Height = 32,
|
||||
Image = G.Images.IL1[15], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
AI = AI.Tactical, Health = 5, HitTimer = 255,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.Mine:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
X = xPos, Y = yPos, CenterX = 0.5f, CenterY = 0.5f, YVel = 0.5f,
|
||||
AnimTicks = 0, ExplodeTime = G.Random(100) + 200, Width = 32, Height = 48,
|
||||
Image = G.Images.IL1[17], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
Health = 10, AI = AI.Mine, MoveDir = "right",
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.MissileBase:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
Name = "Missile Base",
|
||||
X = xPos, Y = yPos, CenterX = 0.5f, CenterY = 0.5f, YVel = 1,
|
||||
Width = 32, Height = 32,
|
||||
Image = G.Images.IL1[18], ImageIndex = 1,
|
||||
DoCollision = false, DoPixelCheck = true,
|
||||
Health = 1, AI = AI.None,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.MissileTurret:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
Name = "Missile Turret",
|
||||
X = xPos, Y = yPos, YVel = 1, CenterX = 0.5f, CenterY = 0.5f,
|
||||
Width = 32, Height = 32,
|
||||
Image = G.Images.IL1[18], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
Health = 4, FireRate = 1, AI = AI.Turret,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.Scanner:
|
||||
_ = new Enemy(G.Sprites)
|
||||
{
|
||||
Name = "Scanner",
|
||||
X = xPos, Y = yPos, YVel = 1, CenterX = 0.5f, CenterY = 0.5f,
|
||||
Width = 32, Height = 32,
|
||||
Image = G.Images.IL1[20], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
Health = 10, AI = AI.None,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.Boss:
|
||||
_ = new Boss(G.Sprites)
|
||||
{
|
||||
X = xPos, Y = yPos, YVel = 1, Width = 98, Height = 98,
|
||||
Image = G.Images.IL1[9], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
MoveDir = "right", Health = 50, HitTimer = 255, AI = AI.Boss,
|
||||
};
|
||||
break;
|
||||
|
||||
case ObjectType.Boss2:
|
||||
_ = new Boss(G.Sprites)
|
||||
{
|
||||
X = xPos, Y = yPos, YVel = 1, Width = 98, Height = 98,
|
||||
Image = G.Images.IL1[13], ImageIndex = 0,
|
||||
DoCollision = true, DoPixelCheck = true,
|
||||
MoveDir = "down", Health = 200, HitTimer = 255, AI = AI.Boss2,
|
||||
Timer = 0, RandX = 4,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateSquad(ObjectType ship, int pattern)
|
||||
{
|
||||
int randomX = G.Random(G.ScreenWidth - 140) + 64;
|
||||
|
||||
switch (pattern)
|
||||
{
|
||||
case 0:
|
||||
SpawnObject(ship, randomX, -32);
|
||||
SpawnObject(ship, randomX - 32, -64);
|
||||
SpawnObject(ship, randomX + 32, -64);
|
||||
break;
|
||||
case 1:
|
||||
SpawnObject(ship, randomX, -32);
|
||||
SpawnObject(ship, randomX - 32, -64);
|
||||
SpawnObject(ship, randomX + 32, -64);
|
||||
SpawnObject(ship, randomX + 64, -96);
|
||||
SpawnObject(ship, randomX - 64, -96);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================ states
|
||||
|
||||
void DrawMenu()
|
||||
{
|
||||
PrintFps(10, 10);
|
||||
|
||||
// water background
|
||||
for (int a = 0; a <= G.ScreenWidth / 32; a++)
|
||||
for (int b = 0; b <= G.ScreenHeight / 32; b++)
|
||||
G.Images.IL1[4].Draw(_sb, a * 32, b * 32, 0, 0, 0, 1, 1, 255, 255, 255, 200, 0);
|
||||
|
||||
G.Images.IL2[3].Draw(_sb, G.ScreenWidth / 2 - 180, 50, pattern: 0);
|
||||
G.Images.IL2[4].Draw(_sb, G.ScreenWidth / 2 - 180, 200, pattern: G.MainMenu[4]);
|
||||
G.Images.IL2[5].Draw(_sb, G.ScreenWidth / 2 - 180, 250, pattern: G.MainMenu[3]);
|
||||
G.Images.IL2[6].Draw(_sb, G.ScreenWidth / 2 - 180, 300, pattern: G.MainMenu[2]);
|
||||
G.Images.IL2[7].Draw(_sb, G.ScreenWidth / 2 - 180, 350, pattern: G.MainMenu[1]);
|
||||
G.Images.IL2[8].Draw(_sb, G.ScreenWidth / 2 - 180, 400, pattern: G.MainMenu[0]);
|
||||
|
||||
var versionSize = _font12.MeasureString(G.Version);
|
||||
_sb.DrawString(_font12, G.Version, new Vector2(G.ScreenWidth / 2 - versionSize.X / 2, 100), Color.White);
|
||||
|
||||
if (G.Input.UpClicked)
|
||||
{
|
||||
G.Music.Songs[7].Play();
|
||||
G.CurSelection++;
|
||||
if (G.CurSelection > 4) G.CurSelection = 0;
|
||||
if (G.CurSelection < 0) G.CurSelection = 4;
|
||||
MoveSelection(G.CurSelection);
|
||||
}
|
||||
|
||||
if (G.Input.DownClicked)
|
||||
{
|
||||
G.Music.Songs[7].Play();
|
||||
G.CurSelection--;
|
||||
if (G.CurSelection > 4) G.CurSelection = 0;
|
||||
if (G.CurSelection < 0) G.CurSelection = 4;
|
||||
MoveSelection(G.CurSelection);
|
||||
}
|
||||
|
||||
if (G.Input.Button3Clicked)
|
||||
{
|
||||
switch (G.CurSelection)
|
||||
{
|
||||
case 0:
|
||||
Exit();
|
||||
break;
|
||||
case 1:
|
||||
G.Music.Songs[4].Stop();
|
||||
G.Music.Songs[9].Play();
|
||||
G.Sprites.KillAll();
|
||||
G.Sprites.RemoveDead();
|
||||
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
G.Counter = 0;
|
||||
G.TextNum = 0;
|
||||
|
||||
G.TitleText = new GameText("DESIGN AND PROGRAMMING", 30, G.BitmapFont2);
|
||||
G.TitleText.SetSpeed(3);
|
||||
G.IdText = new GameText("BRIAN BICKNELL", 30, G.BitmapFont2);
|
||||
G.IdText.SetSpeed(3);
|
||||
G.State = GameState.Credits;
|
||||
break;
|
||||
case 2:
|
||||
G.State = GameState.HighScore;
|
||||
break;
|
||||
case 3:
|
||||
G.Music.Songs[8].Play();
|
||||
break;
|
||||
case 4:
|
||||
G.Sprites.KillAll();
|
||||
G.Sprites.RemoveDead();
|
||||
ResetAll();
|
||||
G.Level = 1;
|
||||
G.State = GameState.Intermission;
|
||||
G.Music.Songs[4].Stop();
|
||||
G.Music.Songs[7].Play();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (G.Input.Button6Clicked) // '2' starts the demo intro
|
||||
{
|
||||
G.Sprites.KillAll();
|
||||
G.Sprites.RemoveDead();
|
||||
ResetAll();
|
||||
|
||||
G.Map.LoadMap("intro.omap");
|
||||
G.Camera.Y = G.Map.HeightCount * 32 - G.ScreenHeight;
|
||||
|
||||
var demoShip = new DemoShip(G.Sprites) { Name = "DemoShip" };
|
||||
demoShip.X = G.ScreenWidth / 2 - demoShip.Width; // Width still 0, like the original
|
||||
demoShip.Y = G.ScreenHeight;
|
||||
demoShip.Width = 73;
|
||||
demoShip.Height = 45;
|
||||
demoShip.Image = G.Images.IL1[0];
|
||||
demoShip.ImageIndex = 0;
|
||||
demoShip.DoPixelCheck = true;
|
||||
demoShip.DoCollision = true;
|
||||
demoShip.CenterX = 0.5f;
|
||||
demoShip.CenterY = 0.5f;
|
||||
_demoShip = demoShip;
|
||||
|
||||
G.Music.Songs[4].Stop();
|
||||
G.Music.Songs[14].Play();
|
||||
|
||||
G.TitleText = new GameText("BICK SOFTWARE", 30, G.BitmapFont2);
|
||||
G.IdText = new GameText("PRESENTS", 30, G.BitmapFont2);
|
||||
G.TextCreated = false;
|
||||
G.BossMode = false;
|
||||
|
||||
G.State = GameState.Intro;
|
||||
}
|
||||
|
||||
if (G.Input.Button2Clicked)
|
||||
Exit();
|
||||
}
|
||||
|
||||
void DoIntro()
|
||||
{
|
||||
if (_demoShip != null && _demoShip.Y > 300)
|
||||
_demoShip.Y -= 0.2f;
|
||||
|
||||
ClipCamera(G.Camera.X, G.Camera.Y, 32, 32);
|
||||
DrawLayer1();
|
||||
|
||||
PrintFps(10, 20);
|
||||
|
||||
if (!G.TextCreated)
|
||||
{
|
||||
G.TitleText.SetText("BICK SOFTWARE PRESENTS");
|
||||
G.TextCreated = true;
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
}
|
||||
|
||||
if (G.Counter == 5)
|
||||
{
|
||||
G.TitleText.FadeOut(_sb, 200, 200, 500);
|
||||
if (G.BitmapFont2.Alpha == 0)
|
||||
{
|
||||
G.TitleText.SetText("OMEGA SQUADREN");
|
||||
G.Counter = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
G.TitleText.FadeIn(_sb, 200, 200, 500);
|
||||
if (G.BitmapFont2.Alpha == 255)
|
||||
G.Counter = 5;
|
||||
}
|
||||
|
||||
if (G.Music.Songs[14].Position > 36)
|
||||
{
|
||||
if (G.Random(50) == 0)
|
||||
SpawnObject(ObjectType.Fighter, G.Random(G.ScreenWidth), -32);
|
||||
}
|
||||
|
||||
if (G.Camera.Y <= 0)
|
||||
{
|
||||
G.Camera.Y = 0;
|
||||
if (!G.BossMode)
|
||||
{
|
||||
SpawnObject(ObjectType.Boss, 300, -G.ScreenHeight + 98);
|
||||
G.BossMode = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
G.Camera.Y--;
|
||||
}
|
||||
|
||||
void DoIntermission()
|
||||
{
|
||||
G.BossMode = false;
|
||||
G.Done = false;
|
||||
|
||||
if (G.Level > 5)
|
||||
{
|
||||
G.Music.Songs[12].Stop();
|
||||
G.Music.Songs[3].Play();
|
||||
G.State = GameState.GameOver;
|
||||
}
|
||||
|
||||
string label = "Level " + G.Level;
|
||||
var size = _font20.MeasureString("Level" + G.Level);
|
||||
_sb.DrawString(_font20, label, new Vector2(G.ScreenWidth / 2 - size.X / 2, G.ScreenHeight / 2), Color.White);
|
||||
|
||||
G.Counter++;
|
||||
G.ShipsKilled = 0;
|
||||
|
||||
if (G.Counter > 500)
|
||||
{
|
||||
switch (G.Level)
|
||||
{
|
||||
case 1: G.Map.LoadMap("test10.omap"); break;
|
||||
case 2: G.Map.LoadMap("test7.omap"); break;
|
||||
case 3: G.Map.LoadMap("test8.omap"); break;
|
||||
case 4: G.Map.LoadMap("bonus.omap"); break;
|
||||
case 5: G.Map.LoadMap("lvl5.omap"); break;
|
||||
}
|
||||
|
||||
G.State = GameState.Playing;
|
||||
ResetPlayer();
|
||||
G.Camera.Y = G.Map.HeightCount * 32 - G.ScreenHeight;
|
||||
|
||||
if (G.Level > 1)
|
||||
G.Music.Songs[G.Level - 2].Stop();
|
||||
|
||||
if (G.Level == 1)
|
||||
{
|
||||
G.Music.Songs[G.MUS_LEVEL1].Volume = 20;
|
||||
G.Music.Songs[G.MUS_LEVEL1].Play();
|
||||
}
|
||||
else if (G.Level == 4)
|
||||
G.Music.Songs[12].Play();
|
||||
else if (G.Level == 5)
|
||||
{
|
||||
G.Music.Songs[12].Stop();
|
||||
G.Music.Songs[4].Play();
|
||||
}
|
||||
else
|
||||
G.Music.Songs[G.Level - 1].Play();
|
||||
}
|
||||
}
|
||||
|
||||
void GameLoop()
|
||||
{
|
||||
ClipCamera(G.Camera.X, G.Camera.Y, 32, 32);
|
||||
DrawLayer1();
|
||||
|
||||
// level 4: boss chase with a wrapping camera
|
||||
if (G.Level == 4)
|
||||
{
|
||||
if (!G.BossMode)
|
||||
{
|
||||
SpawnObject(ObjectType.Boss2, 300, -G.ScreenHeight + 98);
|
||||
G.BossMode = true;
|
||||
}
|
||||
|
||||
if (G.Camera.Y <= 0)
|
||||
G.Camera.Y = G.Map.HeightCount * 32 - G.ScreenHeight;
|
||||
else
|
||||
G.Camera.Y -= 2;
|
||||
}
|
||||
|
||||
int randomNum = G.Random(500);
|
||||
if (randomNum == 0) CreateSquad(ObjectType.Fighter, 0);
|
||||
if (randomNum == 1) CreateSquad(ObjectType.Kamikaze, 1);
|
||||
if (randomNum == 5) SpawnObject(ObjectType.Mine, G.Random(G.ScreenWidth), -48);
|
||||
|
||||
if (G.Camera.Y <= 0)
|
||||
{
|
||||
G.Camera.Y = 0;
|
||||
if (!G.BossMode)
|
||||
{
|
||||
SpawnObject(ObjectType.Boss, 300, -G.ScreenHeight + 98);
|
||||
G.BossMode = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
G.Camera.Y--;
|
||||
|
||||
GetStats();
|
||||
|
||||
G.Counter++;
|
||||
if (G.Counter > 10)
|
||||
{
|
||||
G.Change++;
|
||||
if (G.Change > 2) G.Change = 0;
|
||||
G.Counter = 0;
|
||||
}
|
||||
|
||||
PrintFps(10, 10);
|
||||
_sb.DrawString(_font12, "Sprites: " + G.Sprites.Count, new Vector2(10, 30), Color.White);
|
||||
|
||||
if (G.Lives < 0)
|
||||
{
|
||||
G.Sprites.KillAll();
|
||||
G.Sprites.RemoveDead();
|
||||
G.Music.Songs[G.Level - 1].Stop();
|
||||
G.State = GameState.GameOver;
|
||||
}
|
||||
|
||||
if (G.Done)
|
||||
{
|
||||
G.Sprites.KillAll();
|
||||
G.ShipsKilled++;
|
||||
G.Level++;
|
||||
G.State = GameState.Stats;
|
||||
G.Counter = 0;
|
||||
}
|
||||
|
||||
// spawn entities from map triggers at/below the camera
|
||||
for (int a = 0; a < G.Map.WidthCount; a++)
|
||||
for (int b = Math.Max(0, G.Camera.Y / 32); b < G.Map.HeightCount; b++)
|
||||
{
|
||||
string? action = G.Map.TriggerActions[a, b];
|
||||
if (action == null) continue;
|
||||
|
||||
int tempX = a * 32;
|
||||
int tempY = b - 128; // original quirk: tile row minus 128 pixels
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case "fighter":
|
||||
SpawnObject(ObjectType.Fighter, tempX, tempY);
|
||||
G.Map.TriggerActions[a, b] = null;
|
||||
break;
|
||||
case "kamikaze":
|
||||
SpawnObject(ObjectType.Kamikaze, tempX, tempY);
|
||||
G.Map.TriggerActions[a, b] = null;
|
||||
break;
|
||||
case "tactical":
|
||||
SpawnObject(ObjectType.Tactical, tempX, tempY);
|
||||
G.Map.TriggerActions[a, b] = null;
|
||||
break;
|
||||
case "missileturret":
|
||||
SpawnObject(ObjectType.MissileTurret, tempX, tempY);
|
||||
SpawnObject(ObjectType.MissileBase, tempX, tempY);
|
||||
G.Map.TriggerActions[a, b] = null;
|
||||
break;
|
||||
case "scanner":
|
||||
SpawnObject(ObjectType.Scanner, tempX, tempY);
|
||||
G.Map.TriggerActions[a, b] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (G.Input.Button2Clicked)
|
||||
{
|
||||
G.Sprites.KillAll();
|
||||
G.Sprites.RemoveDead();
|
||||
G.Music.Songs[G.Level - 1].Stop();
|
||||
G.Music.Songs[G.MUS_TITLE].Play();
|
||||
G.State = GameState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void GetStats()
|
||||
{
|
||||
int imageIdx = G.Player.Weapon switch
|
||||
{
|
||||
Weapon.Reg => 0,
|
||||
Weapon.Super => 1,
|
||||
Weapon.Multi => 2,
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
G.Images.IL3[2].Draw(_sb, 300, 560, pattern: imageIdx);
|
||||
|
||||
int colWidth = -10;
|
||||
for (int i = 0; i < G.Lives; i++)
|
||||
{
|
||||
G.Images.IL1[0].Draw(_sb, G.ScreenWidth - 800 + colWidth, G.ScreenHeight - 50,
|
||||
0, 0.5f, 0.5f, 0.5f, 0.5f, 255, 255, 255, 255, 0);
|
||||
colWidth += 30;
|
||||
}
|
||||
|
||||
G.Images.IL1[6].Draw(_sb, 5, 50, pattern: 0);
|
||||
G.BitmapFont1.Print(_sb, 75, 50, G.Score.ToString());
|
||||
|
||||
G.BitmapFont2.Print(_sb, G.ScreenWidth - 150, G.ScreenHeight - 30, "HEALTH");
|
||||
G.Images.IL3[0].Draw(_sb, G.ScreenWidth - 150, G.ScreenHeight - 20, pattern: 0);
|
||||
|
||||
int barWidth = 0;
|
||||
for (int i = 0; i < G.Player.Health; i++)
|
||||
{
|
||||
G.Images.IL3[1].Draw(_sb, G.ScreenWidth - 148 + barWidth, G.ScreenHeight - 18, pattern: 0);
|
||||
barWidth += 8;
|
||||
}
|
||||
|
||||
G.BitmapFont2.Print(_sb, G.ScreenWidth / 6, G.ScreenHeight - 30, "POWER");
|
||||
G.Images.IL3[0].Draw(_sb, G.ScreenWidth / 6, G.ScreenHeight - 20, pattern: 0);
|
||||
|
||||
int energyWidth = 0;
|
||||
for (int i = 0; i < G.Player.Power; i++)
|
||||
{
|
||||
G.Images.IL3[3].Draw(_sb, G.ScreenWidth / 6 + 2 + energyWidth, G.ScreenHeight - 18,
|
||||
0, 0, 0, 1, 1, 0, 100 + energyWidth, 0, 255, 0);
|
||||
energyWidth += 7;
|
||||
}
|
||||
|
||||
if (G.Player.Health < 0)
|
||||
{
|
||||
G.Player.Kill();
|
||||
G.Lives--;
|
||||
ResetPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
void DoStats()
|
||||
{
|
||||
if (G.Counter > 10 && G.CurCount < G.ShipsKilled)
|
||||
{
|
||||
G.Music.Songs[5].Play();
|
||||
G.CurCount++;
|
||||
G.Counter = 0;
|
||||
}
|
||||
|
||||
if (G.Input.Button1Clicked && G.CurCount != G.ShipsKilled)
|
||||
{
|
||||
G.CurCount = G.ShipsKilled;
|
||||
G.Counter = 0;
|
||||
}
|
||||
|
||||
string text = "Ships Killed: " + G.CurCount;
|
||||
var size = _font12.MeasureString(text);
|
||||
_sb.DrawString(_font12, text, new Vector2(G.ScreenWidth / 2 - size.X / 2, G.ScreenHeight / 2), Color.White);
|
||||
|
||||
G.Counter++;
|
||||
_sb.DrawString(_font12, G.Counter.ToString(), new Vector2(10, 10), Color.White);
|
||||
|
||||
if (G.Counter > 500)
|
||||
{
|
||||
G.Counter = 0;
|
||||
G.CurCount = 0;
|
||||
G.State = GameState.Shop;
|
||||
}
|
||||
}
|
||||
|
||||
void DoShop()
|
||||
{
|
||||
_sb.DrawString(_font12, "This is the blackmarket shop", new Vector2(10, 10), Color.White);
|
||||
_sb.DrawString(_font12, "PRESS [SPACE] TO CONTINUE", new Vector2(10, 550), Color.White);
|
||||
|
||||
if (G.Input.Button1Clicked)
|
||||
{
|
||||
G.State = GameState.Intermission;
|
||||
G.Music.Songs[G.Level - 2].Stop();
|
||||
G.Music.Songs[7].Play();
|
||||
}
|
||||
}
|
||||
|
||||
void DoGameOver()
|
||||
{
|
||||
G.Images.IL2[2].Draw(_sb, G.ScreenWidth / 2 - 49, G.ScreenHeight / 2, pattern: 0);
|
||||
G.Images.IL1[6].Draw(_sb, G.ScreenWidth / 2 - 34, G.ScreenHeight / 2 + 100, pattern: 0);
|
||||
G.BitmapFont1.Print(_sb, G.ScreenWidth / 2 + 30, G.ScreenHeight / 2 + 100, G.Score.ToString());
|
||||
|
||||
if (G.Input.Button1Clicked)
|
||||
{
|
||||
G.Music.Songs[3].Stop();
|
||||
G.Music.Songs[9].Play();
|
||||
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
G.Counter = 0;
|
||||
G.TextNum = 0;
|
||||
|
||||
G.TitleText = new GameText("DESIGN AND PROGRAMMING", 30, G.BitmapFont2);
|
||||
G.TitleText.SetSpeed(3);
|
||||
G.IdText = new GameText("BRIAN BICKNELL", 30, G.BitmapFont2);
|
||||
G.IdText.SetSpeed(3);
|
||||
|
||||
if (G.HighScores.Qualifies(G.Score))
|
||||
{
|
||||
_enteredName = "";
|
||||
G.State = GameState.EnterName; // replaces the Form2 dialog
|
||||
}
|
||||
else
|
||||
G.State = GameState.Credits;
|
||||
}
|
||||
}
|
||||
|
||||
void DoCredits()
|
||||
{
|
||||
G.TitleText.FadeIn(_sb,
|
||||
G.ScreenWidth / 2 - G.TitleText.GetText().Length * G.BitmapFont2.CharWidth / 2,
|
||||
G.ScreenHeight / 2 - 15, 5);
|
||||
|
||||
if (G.TitleText.IsDone)
|
||||
G.IdText.TypePrint(_sb,
|
||||
G.ScreenWidth / 2 - G.IdText.GetText().Length * G.BitmapFont2.CharWidth / 2,
|
||||
G.ScreenHeight / 2 + 15);
|
||||
|
||||
if (G.IdText.IsDone)
|
||||
{
|
||||
G.Counter++;
|
||||
if (G.Counter > 400)
|
||||
{
|
||||
G.TextNum++;
|
||||
switch (G.TextNum)
|
||||
{
|
||||
case 1:
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
G.TitleText.SetText("GRAPHICS BY");
|
||||
G.IdText.SetText("BRIAN MAZUROWSKI");
|
||||
break;
|
||||
case 2:
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
G.TitleText.SetText("MUSIC BY");
|
||||
G.IdText.SetText("ADAM HAIRE");
|
||||
break;
|
||||
case 3:
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
G.TitleText.SetText("SPECIAL THANKS TO");
|
||||
G.IdText.SetText("THE OMEGA TEAM");
|
||||
break;
|
||||
case 4:
|
||||
G.BitmapFont2.Alpha = 0;
|
||||
G.TitleText.SetText("THANK YOU FROM");
|
||||
G.IdText.SetText("BICK SOFTWARE");
|
||||
break;
|
||||
case 5:
|
||||
G.State = GameState.HighScore;
|
||||
break;
|
||||
}
|
||||
G.Counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (G.Input.Button2Clicked)
|
||||
{
|
||||
G.Music.Songs[9].Stop();
|
||||
G.Music.Songs[G.MUS_TITLE].Play();
|
||||
G.State = GameState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
void DoHighScore()
|
||||
{
|
||||
// The original printed via OmegaBitmapFont3, which had no image list assigned;
|
||||
// here the letter and number fonts are combined so the table is actually visible.
|
||||
int delta = 0;
|
||||
int savedAlpha = G.BitmapFont2.Alpha;
|
||||
G.BitmapFont2.Alpha = 255;
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
G.BitmapFont2.Print(_sb, 10, 10 + delta, G.HighScores.List[i].Name.ToUpperInvariant());
|
||||
G.BitmapFont1.Print(_sb, 200, 10 + delta, G.HighScores.List[i].Score.ToString());
|
||||
delta += 20;
|
||||
}
|
||||
G.BitmapFont2.Alpha = savedAlpha;
|
||||
|
||||
if (G.Input.Button1Clicked)
|
||||
G.State = GameState.Idle;
|
||||
}
|
||||
|
||||
void DoEnterName()
|
||||
{
|
||||
int savedAlpha = G.BitmapFont2.Alpha;
|
||||
G.BitmapFont2.Alpha = 255;
|
||||
G.BitmapFont2.Print(_sb, 200, 250, "NEW HIGH SCORE");
|
||||
G.BitmapFont2.Print(_sb, 200, 280, "ENTER YOUR NAME");
|
||||
G.BitmapFont2.Print(_sb, 200, 320, _enteredName);
|
||||
G.BitmapFont2.Alpha = savedAlpha;
|
||||
|
||||
foreach (var key in G.Input.Current.GetPressedKeys())
|
||||
{
|
||||
if (!G.Input.Previous.IsKeyUp(key)) continue;
|
||||
|
||||
if (key >= Keys.A && key <= Keys.Z && _enteredName.Length < 20)
|
||||
_enteredName += (char)('A' + (key - Keys.A));
|
||||
else if (key == Keys.Space && _enteredName.Length < 20)
|
||||
_enteredName += ' ';
|
||||
else if (key == Keys.Back && _enteredName.Length > 0)
|
||||
_enteredName = _enteredName[..^1];
|
||||
else if (key == Keys.Enter && _enteredName.Length > 0)
|
||||
{
|
||||
G.HighScores.Enter(_enteredName, G.Score);
|
||||
G.State = GameState.Credits;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
FreedomFighter/GameMap.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Port of TOmegaMap, loading the JSON files converted from the .omap format.
|
||||
/// Only Layer1 pattern indices and triggers are used by the game.
|
||||
/// </summary>
|
||||
public class GameMap
|
||||
{
|
||||
public int WidthCount;
|
||||
public int HeightCount;
|
||||
public int TileWidth = 32;
|
||||
public int TileHeight = 32;
|
||||
public int[][] Layer1 = []; // [x][y] -> tileset pattern index, -1 = empty
|
||||
public string?[,] TriggerActions = new string?[0, 0];
|
||||
public OmegaImage? Tileset;
|
||||
|
||||
readonly GraphicsDevice _gd;
|
||||
readonly string _contentDir;
|
||||
readonly Dictionary<string, OmegaImage> _tilesetCache = new();
|
||||
|
||||
public GameMap(GraphicsDevice gd, string contentDir)
|
||||
{
|
||||
_gd = gd;
|
||||
_contentDir = contentDir;
|
||||
}
|
||||
|
||||
record TriggerJson(int x, int y, string name, string action);
|
||||
record MapJson(int widthCount, int heightCount, string oil, int tileWidth, int tileHeight,
|
||||
int[][] layer1, TriggerJson[] triggers);
|
||||
|
||||
public void LoadMap(string mapName)
|
||||
{
|
||||
string path = Path.Combine(_contentDir, "maps", Path.GetFileNameWithoutExtension(mapName) + ".json");
|
||||
var m = JsonSerializer.Deserialize<MapJson>(File.ReadAllText(path))!;
|
||||
|
||||
WidthCount = m.widthCount;
|
||||
HeightCount = m.heightCount;
|
||||
TileWidth = m.tileWidth;
|
||||
TileHeight = m.tileHeight;
|
||||
Layer1 = m.layer1;
|
||||
|
||||
TriggerActions = new string?[WidthCount, HeightCount];
|
||||
foreach (var t in m.triggers)
|
||||
TriggerActions[t.x, t.y] = t.action;
|
||||
|
||||
string tilesetFile = Path.GetFileNameWithoutExtension(m.oil).ToLowerInvariant() + "_00.png";
|
||||
if (!_tilesetCache.TryGetValue(tilesetFile, out var tileset))
|
||||
{
|
||||
tileset = OmegaImage.Load(_gd, Path.Combine(_contentDir, "tilesets", tilesetFile), TileWidth, TileHeight, m.oil);
|
||||
_tilesetCache[tilesetFile] = tileset;
|
||||
}
|
||||
Tileset = tileset;
|
||||
}
|
||||
}
|
||||
142
FreedomFighter/GameText.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Port of TOmegaBitmapFont: a fixed-cell bitmap font backed by an image list item,
|
||||
/// with mutable color/alpha state (the credits animate Alpha on the shared font).
|
||||
/// </summary>
|
||||
public class BitmapFont
|
||||
{
|
||||
public string Characters = "";
|
||||
public OmegaImage Image = null!;
|
||||
public int Red = 255, Green = 255, Blue = 255;
|
||||
public int Alpha = 255;
|
||||
|
||||
public int CharWidth => Image.TileWidth;
|
||||
public int CharHeight => Image.TileHeight;
|
||||
|
||||
public BitmapFont(OmegaImage image, string characters)
|
||||
{
|
||||
Image = image;
|
||||
Characters = characters;
|
||||
}
|
||||
|
||||
public void PrintFast(SpriteBatch sb, float x, float y, char c)
|
||||
{
|
||||
int idx = Characters.IndexOf(c);
|
||||
if (idx < 0) return;
|
||||
Image.Draw(sb, x, y, 0, 0, 0, 1, 1, Red, Green, Blue, Math.Clamp(Alpha, 0, 255), idx);
|
||||
}
|
||||
|
||||
public void Print(SpriteBatch sb, float x, float y, string text)
|
||||
{
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
PrintFast(sb, x + i * CharWidth, y, text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of the TGameText helper (GameText.pas): typewriter printing and
|
||||
/// fade in/out of text through a bitmap font's shared Alpha.
|
||||
/// </summary>
|
||||
public class GameText
|
||||
{
|
||||
string _text;
|
||||
int _length;
|
||||
int _curChar;
|
||||
int _maxLength;
|
||||
int _timer;
|
||||
int _speed = 10;
|
||||
bool _done;
|
||||
readonly BitmapFont _font;
|
||||
|
||||
public GameText(string msg, int rowLength, BitmapFont font)
|
||||
{
|
||||
_text = msg;
|
||||
_length = msg.Length;
|
||||
_maxLength = rowLength;
|
||||
_font = font;
|
||||
}
|
||||
|
||||
public void SetText(string msg)
|
||||
{
|
||||
_text = msg;
|
||||
_length = msg.Length;
|
||||
_curChar = 0;
|
||||
_timer = 0;
|
||||
}
|
||||
|
||||
public void SetSpeed(int speed) => _speed = speed;
|
||||
public bool IsDone => _done;
|
||||
public string GetText() => _text;
|
||||
public int GetLength() => _length * _font.CharWidth;
|
||||
|
||||
// The Pascal original indexed the 1-based string with i = 0..CurChar; index 0 was a no-op.
|
||||
void PrintChars(SpriteBatch sb, float x, float y, int upTo)
|
||||
{
|
||||
for (int i = 1; i <= upTo && i <= _length; i++)
|
||||
{
|
||||
char c = _text[i - 1];
|
||||
if (i <= _maxLength)
|
||||
_font.PrintFast(sb, x + i * _font.CharWidth, y, c);
|
||||
else
|
||||
{
|
||||
int tempChar = i;
|
||||
while (tempChar > _maxLength) tempChar -= _maxLength;
|
||||
_font.PrintFast(sb, x + tempChar * 12, y + (int)Math.Ceiling(i / (double)_maxLength) * 24 - 24, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void TypePrint(SpriteBatch sb, float x, float y)
|
||||
{
|
||||
_done = false;
|
||||
PrintChars(sb, x, y, _curChar);
|
||||
|
||||
if (_curChar < _length)
|
||||
{
|
||||
_timer++;
|
||||
if (_timer > _speed)
|
||||
{
|
||||
_curChar++;
|
||||
if (_curChar <= _length && _curChar >= 1 && _text[_curChar - 1] == ' ')
|
||||
_curChar++;
|
||||
_timer = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
_done = true;
|
||||
}
|
||||
|
||||
public void FadeIn(SpriteBatch sb, float x, float y, int speed)
|
||||
{
|
||||
_done = false;
|
||||
PrintChars(sb, x, y, _length);
|
||||
_timer++;
|
||||
if (_timer > speed && _font.Alpha < 255)
|
||||
_font.Alpha++;
|
||||
else
|
||||
_done = true; // original quirk: also "done" while the timer is still counting
|
||||
}
|
||||
|
||||
public void FadeOut(SpriteBatch sb, float x, float y, int speed)
|
||||
{
|
||||
_done = false;
|
||||
PrintChars(sb, x, y, _length);
|
||||
_timer++;
|
||||
if (_timer > speed && _font.Alpha > 0)
|
||||
_font.Alpha--;
|
||||
else
|
||||
_done = true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_curChar = 0;
|
||||
_timer = 0;
|
||||
_speed = 10;
|
||||
_done = false;
|
||||
}
|
||||
}
|
||||
62
FreedomFighter/HighScores.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
public record HighScoreEntry(string Name, int Score);
|
||||
|
||||
/// <summary>High score table; JSON replaces the original Highscore.dat record file.</summary>
|
||||
public class HighScores
|
||||
{
|
||||
const string FileName = "highscore.json";
|
||||
public HighScoreEntry[] List = new HighScoreEntry[10];
|
||||
|
||||
public void Load()
|
||||
{
|
||||
if (File.Exists(FileName))
|
||||
{
|
||||
try
|
||||
{
|
||||
var loaded = JsonSerializer.Deserialize<HighScoreEntry[]>(File.ReadAllText(FileName));
|
||||
if (loaded is { Length: 10 }) { List = loaded; return; }
|
||||
}
|
||||
catch { /* fall through to defaults */ }
|
||||
}
|
||||
for (int i = 0; i < 10; i++)
|
||||
List[i] = new HighScoreEntry("Nameless", 0);
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save() => File.WriteAllText(FileName, JsonSerializer.Serialize(List));
|
||||
|
||||
public bool Qualifies(int score) => score > List[9].Score;
|
||||
|
||||
public void Add(int num, string name, int score)
|
||||
{
|
||||
for (int i = 9; i >= num + 1; i--)
|
||||
List[i] = List[i - 1];
|
||||
List[num] = new HighScoreEntry(name, score);
|
||||
}
|
||||
|
||||
/// <summary>Port of EnterHighScoreList: insert from the bottom up.</summary>
|
||||
public void Enter(string name, int score)
|
||||
{
|
||||
for (int i = 8; i >= 0; i--)
|
||||
{
|
||||
if (score <= List[i].Score)
|
||||
{
|
||||
Add(i + 1, name, score);
|
||||
break;
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
Add(i, name, score);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Save();
|
||||
}
|
||||
}
|
||||
BIN
FreedomFighter/Icon.ico
Normal file
|
After Width: | Height: | Size: 144 KiB |
114
FreedomFighter/MusicManager.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework.Audio;
|
||||
using Microsoft.Xna.Framework.Media;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Replacement for TOmegaMusic: a flat list of "songs" where some entries are
|
||||
/// streamed music (mp3 via MediaPlayer) and some are sound effects (wav).
|
||||
/// Indices mirror OmegaMusic1.Songs in GameU.dfm.
|
||||
/// </summary>
|
||||
public class MusicManager
|
||||
{
|
||||
public class Entry
|
||||
{
|
||||
readonly MusicManager _mgr;
|
||||
public string Name = "";
|
||||
public Song? Song; // streamed music
|
||||
public SoundEffect? Effect; // wav sfx
|
||||
public int Volume = 100; // 0..100, Omega convention
|
||||
|
||||
public Entry(MusicManager mgr) => _mgr = mgr;
|
||||
|
||||
public void Play()
|
||||
{
|
||||
if (Song != null)
|
||||
{
|
||||
MediaPlayer.Volume = Volume / 100f;
|
||||
MediaPlayer.Play(Song);
|
||||
_mgr._current = this;
|
||||
}
|
||||
else if (Effect != null)
|
||||
{
|
||||
Effect.Play(Volume / 100f, 0, 0);
|
||||
Volume = 100; // one-shot volume, like the original's per-play setting
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (Song != null && _mgr._current == this)
|
||||
{
|
||||
MediaPlayer.Stop();
|
||||
_mgr._current = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Playback position in seconds (original used this in the intro).</summary>
|
||||
public double Position => Song != null && _mgr._current == this
|
||||
? MediaPlayer.PlayPosition.TotalSeconds : 0;
|
||||
}
|
||||
|
||||
Entry? _current;
|
||||
public Entry[] Songs = [];
|
||||
|
||||
public void Load(string contentDir)
|
||||
{
|
||||
MediaPlayer.IsRepeating = true; // OmegaMusic1.Loop was called every menu frame
|
||||
|
||||
// Index order mirrors OmegaMusic1.Songs in GameU.dfm.
|
||||
// [1] 'Level test.mp3' is lost — Track06.mp3 substitutes.
|
||||
// [3] gameover was a MIDI (Nazi_Rap.mid); MonoGame has no MIDI playback, so it is silent.
|
||||
(string name, string? file)[] entries =
|
||||
[
|
||||
("level1", @"music\white light.mp3"),
|
||||
("level2", @"music\Track06.mp3"),
|
||||
("level3", @"music\G2 Final.mp3"),
|
||||
("gameover", null),
|
||||
("title", @"music\Main Menu 2.mp3"),
|
||||
("fire1", @"sfx\fire1.wav"),
|
||||
("reload", @"sfx\reload.wav"),
|
||||
("thump", @"sfx\thump1.wav"),
|
||||
("beep", @"sfx\beep.wav"),
|
||||
("credits", @"music\G8 Final.mp3"),
|
||||
("hit", @"sfx\hit4.wav"),
|
||||
("explosion", @"sfx\explode2.wav"),
|
||||
("level4", @"music\Thunderization.mp3"),
|
||||
("missile", @"sfx\missile.wav"),
|
||||
("intro", @"music\Intro.mp3"),
|
||||
];
|
||||
|
||||
Songs = new Entry[entries.Length];
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
var e = new Entry(this) { Name = entries[i].name };
|
||||
var file = entries[i].file;
|
||||
if (file != null)
|
||||
{
|
||||
string path = Path.Combine(contentDir, file);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (path.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
using var fs = File.OpenRead(path);
|
||||
e.Effect = SoundEffect.FromStream(fs);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Song = Song.FromUri(entries[i].name, new Uri(path, UriKind.Absolute));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Audio load failed for {file}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Songs[i] = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
FreedomFighter/OmegaImage.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent of a TOmegaImageList item: a texture subdivided into equally-sized
|
||||
/// patterns (frames), drawn by pattern index. Also builds alpha masks for
|
||||
/// pixel-perfect collision (Omega's DoPixelCheck).
|
||||
/// </summary>
|
||||
public class OmegaImage
|
||||
{
|
||||
public string Name = "";
|
||||
public Texture2D Texture = null!;
|
||||
public int TileWidth;
|
||||
public int TileHeight;
|
||||
public int Columns;
|
||||
public int PatternCount;
|
||||
public bool[] OpaqueMask = null!; // per-pixel alpha>0, whole texture, row-major
|
||||
|
||||
public static OmegaImage Load(GraphicsDevice gd, string file, int tileW, int tileH, string name)
|
||||
{
|
||||
using var fs = File.OpenRead(file);
|
||||
var tex = Texture2D.FromStream(gd, fs);
|
||||
int tw = tileW > 0 ? tileW : tex.Width;
|
||||
int th = tileH > 0 ? tileH : tex.Height;
|
||||
|
||||
var pixels = new Color[tex.Width * tex.Height];
|
||||
tex.GetData(pixels);
|
||||
var mask = new bool[pixels.Length];
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
mask[i] = pixels[i].A > 0;
|
||||
|
||||
return new OmegaImage
|
||||
{
|
||||
Name = name,
|
||||
Texture = tex,
|
||||
TileWidth = tw,
|
||||
TileHeight = th,
|
||||
Columns = Math.Max(1, tex.Width / tw),
|
||||
PatternCount = Math.Max(1, tex.Width / tw) * Math.Max(1, tex.Height / th),
|
||||
OpaqueMask = mask,
|
||||
};
|
||||
}
|
||||
|
||||
public Rectangle PatternRect(int pattern)
|
||||
{
|
||||
if (pattern < 0 || pattern >= PatternCount) pattern = 0;
|
||||
return new Rectangle(pattern % Columns * TileWidth, pattern / Columns * TileHeight, TileWidth, TileHeight);
|
||||
}
|
||||
|
||||
/// <summary>Is the pixel at (px,py) within the given pattern opaque? Coordinates are pattern-local.</summary>
|
||||
public bool IsOpaque(int pattern, int px, int py)
|
||||
{
|
||||
if (px < 0 || py < 0 || px >= TileWidth || py >= TileHeight) return false;
|
||||
var r = PatternRect(pattern);
|
||||
int x = r.X + px, y = r.Y + py;
|
||||
if (x >= Texture.Width || y >= Texture.Height) return false;
|
||||
return OpaqueMask[y * Texture.Width + x];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full Omega draw. X,Y is the top-left of the pattern; rotation (degrees, clockwise)
|
||||
/// pivots around (centerX, centerY) as a fraction of the pattern size.
|
||||
/// </summary>
|
||||
public void Draw(SpriteBatch sb, float x, float y, float rotation = 0,
|
||||
float centerX = 0, float centerY = 0, float scaleX = 1, float scaleY = 1,
|
||||
int red = 255, int green = 255, int blue = 255, int alpha = 255, int pattern = 0)
|
||||
{
|
||||
var src = PatternRect(pattern);
|
||||
var origin = new Vector2(centerX * TileWidth, centerY * TileHeight);
|
||||
var pos = new Vector2(x + origin.X * scaleX, y + origin.Y * scaleY);
|
||||
var color = new Color(red, green, blue, alpha);
|
||||
sb.Draw(Texture, pos, src, color, MathHelper.ToRadians(rotation), origin,
|
||||
new Vector2(scaleX, scaleY), SpriteEffects.None, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Loads the image lists extracted from GameU.dfm via gfx/manifest.json.</summary>
|
||||
public class ImageLists
|
||||
{
|
||||
readonly Dictionary<string, List<OmegaImage>> _lists = new();
|
||||
|
||||
public List<OmegaImage> IL1 => _lists["OmegaImageList1"];
|
||||
public List<OmegaImage> IL2 => _lists["OmegaImageList2"];
|
||||
public List<OmegaImage> IL3 => _lists["OmegaImageList3"];
|
||||
public List<OmegaImage> IL4 => _lists["OmegaImageList4"];
|
||||
public List<OmegaImage> IL5 => _lists["OmegaImageList5"];
|
||||
|
||||
record ManifestItem(int Index, string Name, string File, int TileWidth, int TileHeight);
|
||||
|
||||
public ImageLists(GraphicsDevice gd, string contentDir)
|
||||
{
|
||||
string gfxDir = Path.Combine(contentDir, "gfx");
|
||||
var manifest = JsonSerializer.Deserialize<Dictionary<string, List<ManifestItem>>>(
|
||||
File.ReadAllText(Path.Combine(gfxDir, "manifest.json")))!;
|
||||
|
||||
foreach (var (listName, items) in manifest)
|
||||
{
|
||||
var list = new List<OmegaImage>();
|
||||
foreach (var item in items)
|
||||
list.Add(OmegaImage.Load(gd, Path.Combine(gfxDir, item.File), item.TileWidth, item.TileHeight, item.Name));
|
||||
_lists[listName] = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
FreedomFighter/OmegaInput.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Replacement for TOmegaInput's keyboard state sets, with the key assignments
|
||||
/// from GameU.dfm: B1=Space, B2=Esc, B3=Enter, B4=RCtrl, B5=1, B6=2, B7=3, B8=M.
|
||||
/// "States" = held; "StatesClicked" = pressed this frame.
|
||||
/// </summary>
|
||||
public class OmegaInput
|
||||
{
|
||||
KeyboardState _prev;
|
||||
KeyboardState _cur;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_prev = _cur;
|
||||
_cur = Keyboard.GetState();
|
||||
}
|
||||
|
||||
bool Held(Keys k) => _cur.IsKeyDown(k);
|
||||
bool Clicked(Keys k) => _cur.IsKeyDown(k) && _prev.IsKeyUp(k);
|
||||
|
||||
public bool Up => Held(Keys.Up);
|
||||
public bool Down => Held(Keys.Down);
|
||||
public bool Left => Held(Keys.Left);
|
||||
public bool Right => Held(Keys.Right);
|
||||
public bool Button4 => Held(Keys.RightControl) || Held(Keys.LeftControl);
|
||||
public bool Button8 => Held(Keys.M);
|
||||
|
||||
public bool UpClicked => Clicked(Keys.Up);
|
||||
public bool DownClicked => Clicked(Keys.Down);
|
||||
public bool Button1Clicked => Clicked(Keys.Space);
|
||||
public bool Button2Clicked => Clicked(Keys.Escape);
|
||||
public bool Button3Clicked => Clicked(Keys.Enter);
|
||||
public bool Button5Clicked => Clicked(Keys.D1) || Clicked(Keys.NumPad1);
|
||||
public bool Button6Clicked => Clicked(Keys.D2) || Clicked(Keys.NumPad2);
|
||||
public bool Button7Clicked => Clicked(Keys.D3) || Clicked(Keys.NumPad3);
|
||||
public bool PauseClicked => Clicked(Keys.Pause);
|
||||
|
||||
public KeyboardState Current => _cur;
|
||||
public KeyboardState Previous => _prev;
|
||||
}
|
||||
17
FreedomFighter/Program.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
using var game = new FreedomFighter.Game1();
|
||||
// debug aid: --shot <file> [frames] renders, saves the backbuffer to a PNG, and exits
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
for (int i = 1; i < cmdArgs.Length - 1; i++)
|
||||
{
|
||||
if (cmdArgs[i] == "--shot")
|
||||
{
|
||||
game.ShotFile = cmdArgs[i + 1];
|
||||
if (i + 2 < cmdArgs.Length && int.TryParse(cmdArgs[i + 2], out int frames))
|
||||
game.ShotFrames = frames;
|
||||
}
|
||||
if (cmdArgs[i] == "--level" && int.TryParse(cmdArgs[i + 1], out int level))
|
||||
game.StartLevel = level;
|
||||
}
|
||||
game.Run();
|
||||
120
FreedomFighter/Sprites.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace FreedomFighter;
|
||||
|
||||
/// <summary>
|
||||
/// Port of the Omega TSprite base class. Position is the pattern's top-left;
|
||||
/// CenterX/CenterY only affect the rotation pivot. Width/Height define the
|
||||
/// logical collision box, independent of the drawn pattern size.
|
||||
/// </summary>
|
||||
public abstract class Sprite
|
||||
{
|
||||
public string Name = "";
|
||||
public float X, Y;
|
||||
public int Width, Height;
|
||||
public float Rotation; // degrees
|
||||
public float CenterX, CenterY; // 0..1 fraction of pattern
|
||||
public float ScaleX = 1, ScaleY = 1;
|
||||
public int Red = 255, Green = 255, Blue = 255, Alpha = 255;
|
||||
public OmegaImage? Image;
|
||||
public int ImageIndex;
|
||||
public bool DoCollision;
|
||||
public bool DoPixelCheck;
|
||||
public bool IsDead;
|
||||
|
||||
protected Sprite(SpriteManager manager) => manager.Add(this);
|
||||
|
||||
public void SetGreen(int v) => Green = Math.Clamp(v, 0, 255);
|
||||
public void SetBlue(int v) => Blue = Math.Clamp(v, 0, 255);
|
||||
|
||||
/// <summary>Marks the sprite for removal (Omega's Dead).</summary>
|
||||
public void Kill() => IsDead = true;
|
||||
|
||||
public virtual void Move(float moveCount) { }
|
||||
|
||||
public virtual void Draw(SpriteBatch sb)
|
||||
{
|
||||
Image?.Draw(sb, (int)Math.Round(X), (int)Math.Round(Y), Rotation,
|
||||
CenterX, CenterY, ScaleX, ScaleY, Red, Green, Blue, Alpha, ImageIndex);
|
||||
}
|
||||
|
||||
public virtual void OnCollision(Sprite sprite, int colX, int colY) { }
|
||||
|
||||
public Rectangle Bounds => new((int)X, (int)Y, Width, Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of TOmegaSprite: owns all sprites, runs movement, collision
|
||||
/// (bounding box + optional pixel check), removal, and drawing.
|
||||
/// </summary>
|
||||
public class SpriteManager
|
||||
{
|
||||
public readonly List<Sprite> Items = new();
|
||||
|
||||
public int Count => Items.Count;
|
||||
|
||||
public void Add(Sprite s) => Items.Add(s);
|
||||
|
||||
public void KillAll()
|
||||
{
|
||||
foreach (var s in Items) s.Kill();
|
||||
}
|
||||
|
||||
public void Move(float moveCount)
|
||||
{
|
||||
// like the Pascal for-loop, sprites spawned during iteration are not moved this frame
|
||||
int n = Items.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
Items[i].Move(moveCount);
|
||||
}
|
||||
|
||||
public void Collision()
|
||||
{
|
||||
int n = Items.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var a = Items[i];
|
||||
if (!a.DoCollision || a.IsDead) continue;
|
||||
for (int j = i + 1; j < n; j++)
|
||||
{
|
||||
var b = Items[j];
|
||||
if (!b.DoCollision || b.IsDead) continue;
|
||||
|
||||
var overlap = Rectangle.Intersect(a.Bounds, b.Bounds);
|
||||
if (overlap.IsEmpty) continue;
|
||||
|
||||
if ((a.DoPixelCheck || b.DoPixelCheck) && !PixelOverlap(a, b, overlap)) continue;
|
||||
|
||||
a.OnCollision(b, overlap.X, overlap.Y);
|
||||
b.OnCollision(a, overlap.X, overlap.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Pixel test on the unrotated pattern masks (rotation ignored, like a cheap approximation).</summary>
|
||||
static bool PixelOverlap(Sprite a, Sprite b, Rectangle overlap)
|
||||
{
|
||||
if (a.Image == null || b.Image == null) return true;
|
||||
for (int y = overlap.Top; y < overlap.Bottom; y++)
|
||||
for (int x = overlap.Left; x < overlap.Right; x++)
|
||||
{
|
||||
bool pa = a.Image.IsOpaque(a.ImageIndex, x - (int)a.X, y - (int)a.Y);
|
||||
bool pb = b.Image.IsOpaque(b.ImageIndex, x - (int)b.X, y - (int)b.Y);
|
||||
if (pa && pb) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveDead() => Items.RemoveAll(s => s.IsDead);
|
||||
|
||||
public void Draw(SpriteBatch sb)
|
||||
{
|
||||
int n = Items.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (!Items[i].IsDead)
|
||||
Items[i].Draw(sb);
|
||||
}
|
||||
}
|
||||
43
FreedomFighter/app.manifest
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="FreedomFighter"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on and is
|
||||
is designed to work with. Uncomment the appropriate elements and Windows will
|
||||
automatically selected the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
</assembly>
|
||||