initial commit where rendering still works
This commit is contained in:
commit
8e9a80caf0
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
gameenv/data/maps
|
||||
gameenv/data/textures
|
||||
deps
|
||||
.vs
|
||||
x64
|
||||
*/x64
|
||||
x86
|
||||
*/x86
|
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# mpfw
|
||||
## focusing on complete id control
|
||||
|
||||
## Supported file types
|
||||
- BSP29 (Quake maps)
|
5
gameenv/data/cfg/startup.cfg
Normal file
5
gameenv/data/cfg/startup.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
echo "MPFW Quake"
|
||||
echo "Test Startup Script"
|
||||
|
||||
mode 1
|
||||
map "data/maps/fullquake/start.bsp"
|
91
gameenv/data/fonts/RobotoMono/LICENSE.txt
Normal file
91
gameenv/data/fonts/RobotoMono/LICENSE.txt
Normal file
@ -0,0 +1,91 @@
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Bold.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Bold.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-BoldItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLight.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLightItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Italic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Light.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Light.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-LightItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-LightItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Medium.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Medium.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-MediumItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Regular.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBold.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBold.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBoldItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Thin.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Thin.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ThinItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ThinItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/palette.lmp
Normal file
BIN
gameenv/data/palette.lmp
Normal file
Binary file not shown.
41
mpfw.sln
Normal file
41
mpfw.sln
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35527.113
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mpfw", "mpfw\mpfw.vcxproj", "{FB36E71A-46AB-4ECE-9438-E683C4208FB1}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mpfw_server", "mpfw_server\mpfw_server.vcxproj", "{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x64.Build.0 = Debug|x64
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x86.Build.0 = Debug|Win32
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x64.ActiveCfg = Release|x64
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x64.Build.0 = Release|x64
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x86.ActiveCfg = Release|Win32
|
||||
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x86.Build.0 = Release|Win32
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x64.Build.0 = Debug|x64
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x86.Build.0 = Debug|Win32
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x64.ActiveCfg = Release|x64
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x64.Build.0 = Release|x64
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x86.ActiveCfg = Release|Win32
|
||||
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D438E333-35BA-4815-92C4-C0C289E3CD9B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
16
mpfw/MPFW_CVars.cpp
Normal file
16
mpfw/MPFW_CVars.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "MPFW_CVars.h"
|
||||
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
std::variant<std::string, int> MPFW::CVar::get() {
|
||||
try {
|
||||
int retval = std::stoi(value);
|
||||
return retval;
|
||||
}
|
||||
catch(...){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
17
mpfw/MPFW_CVars.h
Normal file
17
mpfw/MPFW_CVars.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <variant>
|
||||
|
||||
namespace MPFW {
|
||||
class CVar {
|
||||
public:
|
||||
std::string value;
|
||||
std::variant<std::string, int> get();
|
||||
};
|
||||
|
||||
|
||||
using CVarMap = std::map<std::string, CVar>;
|
||||
|
||||
}
|
||||
|
175
mpfw/MPFW_Console.cpp
Normal file
175
mpfw/MPFW_Console.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
#include "MPFW_Console.h"
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
MPFW_ConsoleCommand(echo) {
|
||||
for (int i = 0; i < cmd.size(); i++) {
|
||||
*logStr += std::format("{} ", cmd[i]);
|
||||
}
|
||||
*logStr += "\n";
|
||||
}
|
||||
|
||||
MPFW_ConsoleCommand(quit) {
|
||||
exit(0); // some day there'll be proper unloading :3
|
||||
}
|
||||
|
||||
MPFW_ConsoleCommand(mode) {
|
||||
if (cmd.size() != 1) {
|
||||
*logStr += "'mode' command usage:\n mode <0/1>\n\nModes:\n 0 - MPFW (default)\n 1 - Quake\n";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
int newMode = std::stoi(cmd[0]);
|
||||
if (newMode > 1 || newMode < 0) throw std::out_of_range("not a mode");
|
||||
ctx->mode = (MPFW::Console::OperationMode)newMode;
|
||||
*logStr += "Mode is set to '" + std::to_string(newMode) + "'\n";
|
||||
}
|
||||
catch(...){
|
||||
*logStr += "'mode' command usage:\n mode <0/1>\n\nModes:\n 0 - MPFW (default)\n 1 - Quake\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MPFW_ConsoleCommand(map_quake) {
|
||||
|
||||
if (cmd.size() != 1) {
|
||||
*logStr += "'map' command usage:\n map <path>\n";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
ctx->mapQuake->LoadBSPMap(cmd[0]);
|
||||
*logStr += std::format("{}", ctx->mapQuake->data);
|
||||
}
|
||||
catch (...) {
|
||||
*logStr += "Couldn't load map '" + cmd[0] + "'\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MPFW_ConsoleCommand(map_mpfw) {
|
||||
|
||||
*logStr += "Mode MPFW has no \"map\" command because it has no available map format.\n\nUse one of these modes instead:\n - 'mode 1' : QUAKE\n\n";
|
||||
}
|
||||
|
||||
|
||||
MPFW_ConsoleCommand(set) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
MPFW::Console::CommandHandler::CommandHandler(CommandHandlerResources* c)
|
||||
{
|
||||
|
||||
chr = c;
|
||||
|
||||
#pragma region
|
||||
|
||||
|
||||
// System commands are defined manually à la Jason Thor Hall™
|
||||
// but that's to make absolutely f*cking sure that these
|
||||
// elemental commands work. less important commands will be
|
||||
// defined with a definition instantiator or some random shit
|
||||
|
||||
CommandID mpfwEcho = { MPFW, "echo" };
|
||||
CommandID quakeEcho = { QUAKE, "echo" };
|
||||
functionMap[mpfwEcho] = echo;
|
||||
functionMap[quakeEcho] = echo;
|
||||
|
||||
|
||||
CommandID mpfwQuit = { MPFW, "quit" };
|
||||
CommandID quakeQuit = { QUAKE, "quit" };
|
||||
functionMap[mpfwQuit] = quit;
|
||||
functionMap[quakeQuit] = quit;
|
||||
|
||||
|
||||
CommandID mpfwMode = { MPFW, "mode" };
|
||||
CommandID quakeMode = { QUAKE, "mode" };
|
||||
functionMap[mpfwMode] = mode;
|
||||
functionMap[quakeMode] = mode;
|
||||
|
||||
CommandID mpfwMap = { MPFW, "map" };
|
||||
functionMap[mpfwMap] = map_mpfw;
|
||||
|
||||
CommandID quakeMap = { QUAKE, "map" };
|
||||
functionMap[quakeMap] = map_quake;
|
||||
|
||||
#pragma endregion System Commands
|
||||
}
|
||||
|
||||
std::vector<std::string> MPFW::Console::CommandHandler::parseCommand(std::string s)
|
||||
{
|
||||
|
||||
|
||||
bool inQString = false;
|
||||
|
||||
|
||||
std::vector<std::string> retval;
|
||||
|
||||
std::string cstr; // current string
|
||||
for (int i = 0; i < s.size(); i++) {
|
||||
if (s[i] == '"') {
|
||||
inQString = !inQString;
|
||||
if (!inQString) {
|
||||
retval.push_back(cstr);
|
||||
cstr = "";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!inQString && s[i] == ' ') {
|
||||
retval.push_back(cstr);
|
||||
cstr = "";
|
||||
}
|
||||
else cstr += s[i];
|
||||
}
|
||||
}
|
||||
|
||||
if(cstr != "") retval.push_back(cstr);
|
||||
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
void MPFW::Console::CommandHandler::Run(std::string command, bool verbose)
|
||||
{
|
||||
if(verbose) logStr += "\n" + std::string(chr->mode == QUAKE ? "QUAKE" : "MPFW") + "] " + command + "\n";
|
||||
std::vector<std::string> cmdParseResult = parseCommand(command);
|
||||
CommandID id;
|
||||
if (cmdParseResult.size() > 0){
|
||||
id.n = cmdParseResult[0];
|
||||
cmdParseResult.erase(cmdParseResult.begin(), cmdParseResult.begin() + 1);
|
||||
|
||||
id.m = chr->mode;
|
||||
|
||||
if (functionMap.find(id) != functionMap.end()) {
|
||||
functionMap[id](cmdParseResult, chr, &logStr);
|
||||
}
|
||||
else {
|
||||
logStr += "Unknown command " + id.n + " in mode " + (chr->mode == QUAKE ? "QUAKE" : "MPFW") + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MPFW::Console::CommandHandler::RunScript(std::string path)
|
||||
{
|
||||
std::ifstream i(path);
|
||||
if (i.good() && i.is_open()) {
|
||||
std::string line;
|
||||
|
||||
while (std::getline(i, line)) {
|
||||
Run(line, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Can't run called script " + path);
|
||||
}
|
||||
}
|
||||
|
||||
MPFW::Console::CommandHandler::~CommandHandler()
|
||||
{
|
||||
}
|
84
mpfw/MPFW_Console.h
Normal file
84
mpfw/MPFW_Console.h
Normal file
@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "MPFW_Quake.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#define MPFW_ConsoleCommand(x) void x(std::vector<std::string> cmd, MPFW::Console::CommandHandlerResources* ctx, std::string* logStr)
|
||||
|
||||
namespace MPFW {
|
||||
namespace Console {
|
||||
|
||||
enum OperationMode {
|
||||
MPFW = 0,
|
||||
QUAKE = 1
|
||||
};
|
||||
|
||||
struct CommandHandlerResources {
|
||||
Quake::Maps::MapFile* mapQuake;
|
||||
OperationMode mode = MPFW;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct CommandID {
|
||||
OperationMode m;
|
||||
std::string n;
|
||||
};
|
||||
inline bool operator==(const CommandID& left, const CommandID& right) {
|
||||
return left.m == right.m && left.n == right.n;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<MPFW::Console::CommandID>
|
||||
{
|
||||
typedef MPFW::Console::CommandID argument_type;
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(argument_type const& in) const
|
||||
{
|
||||
std::string t;
|
||||
t = in.n + (in.m == MPFW::Console::QUAKE ? "QUAKE" : "MPFW");
|
||||
std::hash<std::string> h;
|
||||
|
||||
return h(t);
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
namespace MPFW{
|
||||
namespace Console{
|
||||
|
||||
class CommandHandler {
|
||||
CommandHandlerResources* chr;
|
||||
std::unordered_map<CommandID, std::function<void(std::vector<std::string>,CommandHandlerResources*, std::string*)>> functionMap;
|
||||
public:
|
||||
|
||||
[[deprecated("Never initialize an empty command handler")]]
|
||||
CommandHandler() { throw; }
|
||||
|
||||
CommandHandler(CommandHandlerResources* chr);
|
||||
|
||||
std::vector<std::string> parseCommand(std::string s);
|
||||
|
||||
std::string logStr;
|
||||
void Run(std::string command, bool verbose = true);
|
||||
void RunScript(std::string path);
|
||||
|
||||
|
||||
|
||||
~CommandHandler();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
180
mpfw/MPFW_HL.cpp
Normal file
180
mpfw/MPFW_HL.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include "MPFW_HL.h"
|
||||
#include "MPFW_Utils.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
|
||||
MPFW::HL::WAD::WAD()
|
||||
{
|
||||
std::cout << "Warning: Empty WAD instance created.\n";
|
||||
}
|
||||
|
||||
MPFW::HL::WAD::WAD(std::string path)
|
||||
{
|
||||
LoadWAD(path);
|
||||
}
|
||||
|
||||
void MPFW::HL::WAD::LoadWAD(std::string path)
|
||||
{
|
||||
loaded = false;
|
||||
lumps.clear();
|
||||
lumpCount = 0;
|
||||
std::cout << "Loading file " << path << "...\n";
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
std::string filedata;
|
||||
std::stringstream filestream; filestream << file.rdbuf();
|
||||
filedata = filestream.str();/*
|
||||
if (
|
||||
|
||||
|
||||
MPFW::Utils::Strings::IterativeComp(filedata, 0, "IWAD")) {
|
||||
wadType = IWAD;
|
||||
std::cout << "File is an IWAD\n";
|
||||
}
|
||||
else if (MPFW::Utils::Strings::IterativeComp(filedata, 0, "PWAD")) {
|
||||
wadType = PWAD;
|
||||
std::cout << "File is a PWAD\n";
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("Invalid WAD file: Error at header start(IWAD/PWAD)\n");
|
||||
}*/
|
||||
lumpCount = MPFW::Utils::Strings::StringIndexToInteger_4b_le(filedata, 4);
|
||||
|
||||
std::cout << "File has " << lumpCount << " lumps\n";
|
||||
int directoryPointer = MPFW::Utils::Strings::StringIndexToInteger_4b_le(filedata, 8);
|
||||
|
||||
std::string directory = MPFW::Utils::Strings::IterativeStringExcerpt(filedata, directoryPointer, 16 * lumpCount);
|
||||
|
||||
for (int i = 0; i < lumpCount; i++) {
|
||||
Lump lump;
|
||||
std::string lumpIndex = MPFW::Utils::Strings::IterativeStringExcerpt(directory, i * 16, 16);
|
||||
int lumpPointer = MPFW::Utils::Strings::StringIndexToInteger_4b_le(lumpIndex, 0);
|
||||
int lumpSize = MPFW::Utils::Strings::StringIndexToInteger_4b_le(lumpIndex, 4);
|
||||
lump.name = "", lump.data = "";
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (lumpIndex.at(8 + j) != 0x0) lump.name += lumpIndex.at(8 + j);
|
||||
}
|
||||
|
||||
lump.data = MPFW::Utils::Strings::IterativeStringExcerpt(filedata, lumpPointer, lumpSize);
|
||||
|
||||
lumps.push_back(lump);
|
||||
|
||||
std::cout << "(" << i << "/" << lumpCount << ")" << "Loaded lump \"" << lump.name << "\" of size " << lumpSize << "\n";
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
MPFW::HL::WAD::~WAD()
|
||||
{
|
||||
}
|
||||
|
||||
MPFW::HL::Map::Map()
|
||||
{
|
||||
}
|
||||
|
||||
MPFW::HL::Map::Map(std::string path)
|
||||
{
|
||||
Load(path);
|
||||
}
|
||||
|
||||
void MPFW::HL::Map::Load(std::string path)
|
||||
{
|
||||
std::ifstream in(path);
|
||||
|
||||
if (!in.is_open()) throw std::runtime_error("Cannot open file " + path + "\n");
|
||||
|
||||
int scope = 0;
|
||||
|
||||
|
||||
std::string line;
|
||||
GenericUnknownClass* parsedClass = new GenericUnknownClass();
|
||||
WorldSpawnClass* worldSpawn = new WorldSpawnClass();
|
||||
bool inWorldSpawn = false;
|
||||
while (std::getline(in, line)) {
|
||||
|
||||
if (line == "{") {
|
||||
scope++;
|
||||
std::cout << "scope: " << scope << std::endl;
|
||||
}
|
||||
else if (line == "}") {
|
||||
scope--;
|
||||
std::cout << "scope: " << scope << std::endl;
|
||||
if (scope == 0) {
|
||||
if (inWorldSpawn) {
|
||||
inWorldSpawn = false;
|
||||
mapdata.push_back(worldSpawn);
|
||||
}
|
||||
else {
|
||||
mapdata.push_back(parsedClass);
|
||||
parsedClass = new GenericUnknownClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (line != "") {
|
||||
if (scope == 1) {
|
||||
std::string key = MPFW::Utils::Strings::IterativeStringExcerpt_delim(line, 1, '"');
|
||||
std::string val = MPFW::Utils::Strings::IterativeStringExcerpt_delim(line, 4 + key.size(), '"');
|
||||
std::cout << key << " : " << val << "\n";
|
||||
if (key == "classname") {
|
||||
if (val == "worldspawn") {
|
||||
inWorldSpawn = true;
|
||||
std::cout << "in world spawn\n";
|
||||
worldSpawn->classname = "worldspawn";
|
||||
}
|
||||
else {
|
||||
parsedClass->classname = val;
|
||||
}
|
||||
}
|
||||
else if (!inWorldSpawn) parsedClass->fields[key] = val;
|
||||
else {
|
||||
if (key == "defaultteam") worldSpawn->defaultteam = std::stoi(val);
|
||||
if (key == "newunit") worldSpawn->newunit = std::stoi(val);
|
||||
if (key == "gametitle") worldSpawn->gametitle = std::stoi(val);
|
||||
if (key == "startdark") worldSpawn->startdark = std::stoi(val);
|
||||
if (key == "MaxRange") worldSpawn->maxrange = std::stoi(val);
|
||||
if (key == "sounds") worldSpawn->sounds = std::stoi(val);
|
||||
if (key == "mapversion") worldSpawn->mapversion = std::stoi(val);
|
||||
if (key == "wad") worldSpawn->wad = val;
|
||||
}
|
||||
|
||||
}
|
||||
else if (scope == 2) {
|
||||
std::stringstream stream(line);
|
||||
Plane plane;
|
||||
std::string temp;
|
||||
stream >>
|
||||
temp >> plane.a.x >> plane.a.y >> plane.a.z >> temp >>
|
||||
temp >> plane.b.x >> plane.b.y >> plane.b.z >> temp >>
|
||||
temp >> plane.c.x >> plane.c.y >> plane.c.z >> temp >>
|
||||
plane.texturename >>
|
||||
temp >> plane.u.x >> plane.u.y >> plane.u.z >> plane.uOffset >> temp >>
|
||||
temp >> plane.v.x >> plane.v.y >> plane.v.z >> plane.vOffset >> temp >>
|
||||
plane.rotation >> plane.uScale >> plane.vScale;
|
||||
|
||||
worldSpawn->planes.push_back(plane);
|
||||
|
||||
}
|
||||
else {
|
||||
throw std::out_of_range("Out of scope content");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scope != 0) {
|
||||
if (scope > 1) throw std::length_error("File " + path + " is visibly incomplete by scope arithmetics.\n");
|
||||
else {
|
||||
std::cerr << "[WARNING] File " + path + " is missing a final closing bracket.\n";
|
||||
}
|
||||
}
|
||||
|
||||
in.close();
|
||||
|
||||
}
|
||||
|
||||
MPFW::HL::Map::~Map()
|
||||
{
|
||||
}
|
85
mpfw/MPFW_HL.h
Normal file
85
mpfw/MPFW_HL.h
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <raylib.h>
|
||||
#include <map>
|
||||
|
||||
namespace MPFW {
|
||||
namespace HL {
|
||||
struct Lump {
|
||||
std::string name, data;
|
||||
};
|
||||
|
||||
class WAD { // WAD3 file format
|
||||
std::vector<Lump> lumps;
|
||||
public:
|
||||
bool loaded = false;
|
||||
int lumpCount;
|
||||
WAD();
|
||||
WAD(std::string path);
|
||||
void LoadWAD(std::string path);
|
||||
std::vector<Lump> getLumps() {
|
||||
return lumps;
|
||||
}
|
||||
~WAD();
|
||||
};
|
||||
|
||||
struct Plane {
|
||||
Vector3 a, b, c, u, v;
|
||||
std::string texturename;
|
||||
float rotation, uOffset, uScale, vOffset, vScale;
|
||||
|
||||
};
|
||||
|
||||
struct Brush {
|
||||
std::vector<Plane> planes;
|
||||
};
|
||||
|
||||
class GenericClass {
|
||||
public:
|
||||
std::string classname;
|
||||
virtual void onCreate() = 0;
|
||||
virtual void onUpdate() = 0;
|
||||
virtual void onDestroy() = 0;
|
||||
};
|
||||
|
||||
class GenericOriginedClass : public GenericClass {
|
||||
public:
|
||||
Vector3 origin;
|
||||
virtual void onCreate() = 0;
|
||||
virtual void onUpdate() = 0;
|
||||
virtual void onDestroy() = 0;
|
||||
};
|
||||
|
||||
class GenericUnknownClass : public GenericClass{
|
||||
public:
|
||||
std::map<std::string, std::string> fields;
|
||||
void onCreate() {}
|
||||
void onUpdate() {}
|
||||
void onDestroy() {}
|
||||
};
|
||||
|
||||
class WorldSpawnClass : public GenericClass {
|
||||
public:
|
||||
std::string wad;
|
||||
int defaultteam, newunit, gametitle, startdark, maxrange, sounds, mapversion;
|
||||
std::vector<Plane> planes;
|
||||
void onCreate() {}
|
||||
void onUpdate() {}
|
||||
void onDestroy() {}
|
||||
};
|
||||
|
||||
struct __HLParseKeyVal {
|
||||
std::string key, val;
|
||||
};
|
||||
|
||||
class Map {
|
||||
public:
|
||||
std::vector<GenericClass*> mapdata;
|
||||
Map();
|
||||
Map(std::string path);
|
||||
void Load(std::string path);
|
||||
~Map();
|
||||
};
|
||||
}
|
||||
}
|
43
mpfw/MPFW_MPFWMF.h
Normal file
43
mpfw/MPFW_MPFWMF.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <raymath.h> // we shalln't worry ourselves with full raylib if we're going to end up networking this bad bitch
|
||||
#include <raylib.h> // listening to le colibri nécrophile made me decide otherwise
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace MPFW {
|
||||
namespace MPFW {
|
||||
namespace MF {
|
||||
|
||||
typedef enum {
|
||||
UNKNOWN,
|
||||
PLAYERSTART,
|
||||
DECORATION
|
||||
} EntityType;
|
||||
|
||||
class BaseEntity {
|
||||
public:
|
||||
EntityType entityType;
|
||||
Vector3 origin;
|
||||
virtual void Start(Map* map, int entityNum) = 0;
|
||||
virtual void Update(Map* map, int entityNum) = 0;
|
||||
virtual void Stop(Map* map, int entityNum) = 0;
|
||||
virtual void Destroy(Map* map, int entityNum) = 0;
|
||||
};
|
||||
|
||||
|
||||
class Map {
|
||||
public:
|
||||
|
||||
Model terrain;
|
||||
|
||||
|
||||
Map();
|
||||
|
||||
|
||||
~Map();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
461
mpfw/MPFW_Quake.cpp
Normal file
461
mpfw/MPFW_Quake.cpp
Normal file
@ -0,0 +1,461 @@
|
||||
|
||||
#include <raylib.h>
|
||||
#include "MPFW_Quake.h"
|
||||
#include "MPFW_Utils.h"
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <rlgl.h>
|
||||
|
||||
|
||||
void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
|
||||
{
|
||||
std::ifstream fileHandle(path, std::ios::binary); // open the file as bin
|
||||
|
||||
std::string buffer;
|
||||
ds = Debug::NONE;
|
||||
if (fileHandle.is_open() && fileHandle.good()) {
|
||||
std::stringstream sstr; // get out of working with streams
|
||||
sstr << fileHandle.rdbuf(); // as fast as possible
|
||||
buffer = sstr.str(); // 3 LINES [WR SPEEDRUN]
|
||||
|
||||
// operating on files as a string or a char array is simpler
|
||||
// than writing some fancy << fuckery (even though it is nice
|
||||
// to see some << fuckery work without any moving parts)
|
||||
|
||||
}
|
||||
else {
|
||||
fileHandle.close();
|
||||
std::print("Bad file handle.\n");
|
||||
ds = Debug::DONE;
|
||||
throw std::runtime_error("Bad file handle in Quake BSP parsing.\n");
|
||||
}
|
||||
|
||||
fileHandle.close(); // close it as soon as we can get file loaded
|
||||
// as to not disturb other programs
|
||||
|
||||
|
||||
// we can now start the parsing
|
||||
// hehehe >:3
|
||||
|
||||
|
||||
|
||||
// if file is smaller than header
|
||||
if (buffer.size() < 124) {
|
||||
std::print("Bad file content size.\n");
|
||||
throw std::runtime_error("File is smaller than header. Can't parse.");
|
||||
}
|
||||
|
||||
ds = Debug::HEADER;
|
||||
data.header.version = 0; // initialize version for addition
|
||||
|
||||
data.header.version += buffer[0];
|
||||
data.header.version += buffer[1] << 8;
|
||||
data.header.version += buffer[2] << 16;
|
||||
data.header.version += buffer[3] << 24;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::vector<DirectoryEntry> vecdir;
|
||||
|
||||
|
||||
|
||||
for (int i = 4; i < 123; i += 8) {
|
||||
|
||||
DirectoryEntry nd{0,0}; // new directory;
|
||||
|
||||
nd.offset = Utils::Strings::StringIndexToInteger_4b_le(buffer, i);
|
||||
nd.size = Utils::Strings::StringIndexToInteger_4b_le(buffer, i + 4);
|
||||
|
||||
|
||||
vecdir.push_back(nd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// redistribution of entries
|
||||
data.header.entities = vecdir[0];
|
||||
data.header.planes = vecdir[1];
|
||||
data.header.miptex = vecdir[2];
|
||||
data.header.vertices = vecdir[3];
|
||||
data.header.visilist = vecdir[4];
|
||||
data.header.nodes = vecdir[5];
|
||||
data.header.texinfo = vecdir[6];
|
||||
data.header.faces = vecdir[7];
|
||||
data.header.lightmaps = vecdir[8];
|
||||
data.header.clipnodes = vecdir[9];
|
||||
data.header.leaves = vecdir[10];
|
||||
data.header.lface = vecdir[11];
|
||||
data.header.edges = vecdir[12];
|
||||
data.header.ledges = vecdir[13];
|
||||
data.header.models = vecdir[14];
|
||||
|
||||
|
||||
data.models.clear();
|
||||
data.edges.clear();
|
||||
data.faces.clear();
|
||||
data.ledges.clear();
|
||||
data.texInfo.clear();
|
||||
data.vertices.clear();
|
||||
|
||||
|
||||
|
||||
// Loading Models
|
||||
// we're loading models first because that's
|
||||
// what the spec does <3
|
||||
|
||||
ds = Debug::MODELS;
|
||||
|
||||
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||
data.models.resize(data.header.models.size / 64);
|
||||
|
||||
modelsCDBG = 0;
|
||||
for (int i = 0; i < data.header.models.size / 64; i++) {
|
||||
int base = data.header.models.offset + 64 * i;
|
||||
|
||||
|
||||
qModel qm;
|
||||
|
||||
|
||||
qm.bound.min.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
|
||||
qm.bound.min.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
|
||||
qm.bound.min.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
|
||||
qm.bound.max.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 12);
|
||||
qm.bound.max.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 16);
|
||||
qm.bound.max.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 20);
|
||||
|
||||
|
||||
qm.origin.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 24);
|
||||
qm.origin.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 28);
|
||||
qm.origin.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 32);
|
||||
|
||||
qm.node_id0 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 36);
|
||||
qm.node_id1 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 40);
|
||||
qm.node_id2 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 44);
|
||||
qm.node_id3 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 48);
|
||||
|
||||
|
||||
qm.numleafs = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 52);
|
||||
qm.face_id = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 56);
|
||||
qm.face_num = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 60);
|
||||
|
||||
|
||||
data.models[i] = qm;
|
||||
modelsCDBG++;
|
||||
|
||||
|
||||
// std::print("Model {}/{}\n", i, data.header.models.size / 64);
|
||||
|
||||
}
|
||||
#else
|
||||
std::string modelsStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.models.offset, data.header.models.size);
|
||||
data.models.resize(data.header.models.size / 64);
|
||||
std::memcpy(data.models.data(), modelsStr.data(), data.header.models.size);
|
||||
#endif
|
||||
|
||||
|
||||
// parsing vertices
|
||||
|
||||
ds = Debug::VERTICES;
|
||||
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||
verticesCDBG = 0;
|
||||
data.vertices.resize(data.header.vertices.size / 12);
|
||||
|
||||
for (int i = 0; i < data.header.vertices.size / 12; i++) {
|
||||
int base = data.header.vertices.offset + (12 * i);
|
||||
|
||||
Vector3 v;
|
||||
|
||||
v.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
|
||||
v.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
|
||||
v.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
|
||||
|
||||
data.vertices[i] = v;
|
||||
|
||||
// std::print("Vertex {}/{}\n", i, data.header.vertices.size / 12);
|
||||
verticesCDBG++;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::string verticesStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.vertices.offset, data.header.vertices.size);
|
||||
data.vertices.resize(data.header.vertices.size / 12);
|
||||
std::memcpy(data.vertices.data(), verticesStr.data(), data.header.vertices.size);
|
||||
#endif
|
||||
// parsing edges
|
||||
|
||||
ds = Debug::EDGES;
|
||||
|
||||
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||
edgesCDBG = 0;
|
||||
data.edges.resize(data.header.edges.size / 4);
|
||||
|
||||
for (int i = 0; i < data.header.edges.size / 4; i++) {
|
||||
int base = data.header.edges.offset + (4 * i);
|
||||
Edge e;
|
||||
e.vertex0 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
|
||||
e.vertex1 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base+2);
|
||||
data.edges[i] = e;
|
||||
edgesCDBG++;
|
||||
}
|
||||
#else
|
||||
|
||||
std::string edgeStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.edges.offset, data.header.edges.size);
|
||||
data.edges.resize(data.header.edges.size / 4);
|
||||
std::memcpy(data.edges.data(), edgeStr.data(), data.header.edges.size);
|
||||
#endif
|
||||
|
||||
ds = Debug::TEXINFO;
|
||||
|
||||
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||
texInfoCDBG = 0;
|
||||
data.texInfo.resize(data.header.texinfo.size / 40);
|
||||
for (int i = 0; i < data.header.texinfo.size / 40; i++) {
|
||||
int base = data.header.texinfo.offset + (40 * i);
|
||||
Surface s;
|
||||
|
||||
s.vectorS.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
|
||||
s.vectorS.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
|
||||
s.vectorS.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
|
||||
s.distS = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 12);
|
||||
s.vectorT.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 16);
|
||||
s.vectorT.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 20);
|
||||
s.vectorT.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 24);
|
||||
s.distT = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 28);
|
||||
s.textureId = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 32);
|
||||
s.animated = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 36);
|
||||
|
||||
data.texInfo[i] = s;
|
||||
texInfoCDBG++;
|
||||
}
|
||||
#else
|
||||
|
||||
std::string texInfoStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.texinfo.offset, data.header.texinfo.size);
|
||||
data.texInfo.resize(data.header.texinfo.size / 40);
|
||||
std::memcpy(data.texInfo.data(), texInfoStr.data(), data.header.texinfo.size);
|
||||
#endif
|
||||
|
||||
ds = Debug::LEDGES;
|
||||
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||
for (int i = 0; i < data.header.ledges.size / 2; i++) {
|
||||
int base = data.header.ledges.offset + (2 * i);
|
||||
|
||||
|
||||
data.ledges.push_back(Utils::Strings::StringIndexToInteger_2b_le(buffer, base));
|
||||
|
||||
}
|
||||
#else
|
||||
|
||||
|
||||
std::string ledgesStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.ledges.offset, data.header.ledges.size);
|
||||
data.ledges.resize(data.header.ledges.size / 4);
|
||||
std::memcpy(data.ledges.data(), ledgesStr.data(), data.header.ledges.size);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// parsing faces
|
||||
|
||||
ds = Debug::FACES;
|
||||
|
||||
|
||||
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||
|
||||
for (int i = 0; i < data.header.faces.size / 20; i++) {
|
||||
int base = data.header.faces.offset + (20 * i);
|
||||
|
||||
Face f;
|
||||
|
||||
f.planeId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
|
||||
f.side = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 2);
|
||||
|
||||
f.ledgeId = (signed long)(Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 4));
|
||||
|
||||
f.ledgeNum = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 8);
|
||||
|
||||
|
||||
f.texinfoId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 10);
|
||||
|
||||
f.typelight = buffer[base + 12];
|
||||
f.baselight = buffer[base + 13];
|
||||
f.light[0] = buffer[base + 14];
|
||||
f.light[1] = buffer[base + 15];
|
||||
f.lightmap = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 16);
|
||||
|
||||
|
||||
data.faces.push_back(f);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
std::string facesStr = Utils::Strings::IterativeStringExcerpt(
|
||||
buffer,
|
||||
data.header.faces.offset,
|
||||
data.header.faces.size
|
||||
);
|
||||
data.faces.resize(data.header.faces.size / 20);
|
||||
std::memcpy(data.faces.data(), facesStr.data(), data.header.faces.size);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
std::print("Loading mip textures manually for now\n");
|
||||
|
||||
|
||||
for (int i = 0; i < data.textures.size(); i++) {
|
||||
rlUnloadTexture(data.textures[i].glTextureID);
|
||||
}
|
||||
cMipHeader cmh;
|
||||
|
||||
cmh.numtex = Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset);
|
||||
std::vector<long> texOffsets;
|
||||
// std::memcpy(texOffsets.data(), Utils::Strings::IterativeStringExcerpt(buffer, data.header.miptex.offset + 4, data.header.miptex.size - 4).data(), data.header.miptex.size - 4);
|
||||
for (int i = 0; i < cmh.numtex; i++) {
|
||||
texOffsets.push_back(Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset + 4 + 4*i));
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < cmh.numtex; i++) {
|
||||
unsigned int base = static_cast<unsigned int>(texOffsets[i]) + (unsigned)data.header.miptex.offset;
|
||||
|
||||
// this shit is ¤ undocumented! ¤
|
||||
// fuck the unofficial quake specs
|
||||
if (texOffsets[i] == -1) {
|
||||
// is a texoffset of 0xFFFFFFFF a reference to the fact that
|
||||
// it starts right after the mipheader? probably not, but it
|
||||
// makes the texture loading code happy so... :shrug:
|
||||
base = (unsigned)data.header.miptex.offset + 4 + texOffsets.size() * 4;
|
||||
}
|
||||
|
||||
cMiptex t;
|
||||
std::memcpy(&t, Utils::Strings::IterativeStringExcerpt(buffer, base, 40).data(), 40);
|
||||
|
||||
Image img = GenImageColor(t.width, t.height, BLACK);
|
||||
for (int y = 0; y < t.height; y++) {
|
||||
for (int x = 0; x < t.width; x++) {
|
||||
ImageDrawPixel(&img, x, y,
|
||||
{
|
||||
pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].r,
|
||||
pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].g,
|
||||
pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].b,
|
||||
255
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Texture2D temp = LoadTextureFromImage(img);
|
||||
|
||||
rlMipTex mt;
|
||||
mt.glTextureID = temp.id;
|
||||
mt.height = temp.height;
|
||||
mt.width = temp.width;
|
||||
mt.type = temp.format;
|
||||
|
||||
UnloadImage(img);
|
||||
data.textures.push_back(mt);
|
||||
|
||||
}
|
||||
|
||||
data.renderFaces.clear();
|
||||
|
||||
std::print("Precalculating faces and texture coordinates\n");
|
||||
|
||||
for (int i = 0; i < data.faces.size(); i++) {
|
||||
CalculatedFace cface;
|
||||
|
||||
for (int k = 0; k < data.faces[i].ledgeNum; k++) {
|
||||
if (data.ledges[(unsigned int)(data.faces[i].ledgeId) + k] < 0) {
|
||||
cface.vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
data.vertices[
|
||||
data.edges[
|
||||
abs(
|
||||
data.ledges[
|
||||
data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex0
|
||||
]
|
||||
));
|
||||
}
|
||||
else {
|
||||
cface.vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
data.vertices[
|
||||
data.edges[
|
||||
abs(
|
||||
data.ledges[
|
||||
data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex1
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
cface.glTextureId = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].glTextureID;
|
||||
cface.glTextureWidth = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width;
|
||||
cface.glTextureHeight = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height;
|
||||
|
||||
|
||||
for (int j = cface.vertices.size() - 1; j > 0; j--) {
|
||||
|
||||
|
||||
cface.texCoords.push_back(Vector2(
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[0]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[0]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
|
||||
));
|
||||
cface.texCoords.push_back(Vector2(
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
|
||||
));
|
||||
cface.texCoords.push_back(Vector2(
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j - 1]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j - 1]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
data.renderFaces.push_back(cface);
|
||||
|
||||
|
||||
}
|
||||
|
||||
ds = Debug::DONE;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Vector3 MPFW::Quake::Maps::qVec2RLVec(Vector3 q) {
|
||||
return { q.y / 2, q.z / 2, q.x / 2 };
|
||||
}
|
||||
Vector3 MPFW::Quake::Maps::RLVec2qVec(Vector3 q) {
|
||||
return { q.z * 2, q.x * 2, q.y * 2 };
|
||||
}
|
||||
|
||||
void MPFW::Quake::Maps::Palette::LoadPalette(std::string path)
|
||||
{
|
||||
std::ifstream i(path, std::ios::binary);
|
||||
if (i.is_open() && i.good()) {
|
||||
data.clear();
|
||||
data.resize(256);
|
||||
std::stringstream s;
|
||||
s << i.rdbuf();
|
||||
std::string d = s.str();
|
||||
|
||||
std::memcpy(data.data(), d.data(), 256 * 3);
|
||||
|
||||
}
|
||||
else throw std::runtime_error("can't open palette file " + path);
|
||||
}
|
235
mpfw/MPFW_Quake.h
Normal file
235
mpfw/MPFW_Quake.h
Normal file
@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
// based off of https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_4.htm
|
||||
// (mostly)
|
||||
|
||||
#include <raymath.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <print>
|
||||
|
||||
namespace MPFW {
|
||||
namespace Quake {
|
||||
namespace Maps{
|
||||
|
||||
|
||||
namespace Debug {
|
||||
enum DebugState {
|
||||
NONE,
|
||||
HEADER,
|
||||
MODELS,
|
||||
VERTICES,
|
||||
EDGES,
|
||||
TEXINFO,
|
||||
LEDGES,
|
||||
FACES,
|
||||
DONE
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Vector3 qVec2RLVec(Vector3 q);
|
||||
Vector3 RLVec2qVec(Vector3 q);
|
||||
|
||||
struct DirectoryEntry {
|
||||
long offset;
|
||||
long size;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
struct BSPHeader {
|
||||
long version; // must be 0x17
|
||||
DirectoryEntry entities; // list of entities
|
||||
DirectoryEntry planes; // planes
|
||||
DirectoryEntry miptex; // wall textures
|
||||
DirectoryEntry vertices; // map vertices
|
||||
DirectoryEntry visilist; // leaves visibility lists
|
||||
DirectoryEntry nodes; // bsp nodes
|
||||
DirectoryEntry texinfo; // texture info for faces
|
||||
DirectoryEntry faces; // faces of each surface
|
||||
DirectoryEntry lightmaps; // wall light maps
|
||||
DirectoryEntry clipnodes; // clip nodes
|
||||
DirectoryEntry leaves; // bsp leaves
|
||||
DirectoryEntry lface; // list of faces
|
||||
DirectoryEntry edges; // edges of faces
|
||||
DirectoryEntry ledges; // list of edges
|
||||
DirectoryEntry models; // list of models
|
||||
};
|
||||
|
||||
struct qBoundingBox {
|
||||
Vector3 min,max;
|
||||
};
|
||||
|
||||
struct qBoundingBoxShort {
|
||||
short minX, minY, minZ, maxX, maxY, maxZ;
|
||||
};
|
||||
|
||||
struct qModel {
|
||||
qBoundingBox bound;
|
||||
Vector3 origin;
|
||||
long node_id0, // first bsp node
|
||||
node_id1, // first clip node
|
||||
node_id2, // second clip node
|
||||
node_id3; // usually zero
|
||||
long numleafs;
|
||||
long face_id,
|
||||
face_num;
|
||||
};
|
||||
struct Edge {
|
||||
unsigned short vertex0, vertex1;
|
||||
};
|
||||
struct Surface {
|
||||
Vector3 vectorS;
|
||||
float distS;
|
||||
Vector3 vectorT;
|
||||
float distT;
|
||||
unsigned long textureId;
|
||||
unsigned long animated;
|
||||
};
|
||||
struct Face {
|
||||
unsigned short planeId;
|
||||
unsigned short side;
|
||||
long ledgeId;
|
||||
unsigned short ledgeNum;
|
||||
unsigned short texinfoId;
|
||||
|
||||
unsigned char typelight, baselight;
|
||||
unsigned char light[2];
|
||||
long lightmap;
|
||||
};
|
||||
|
||||
|
||||
struct CalculatedFace {
|
||||
std::vector<Vector3> vertices;
|
||||
std::vector<Vector2> texCoords;
|
||||
int glTextureId, glTextureWidth, glTextureHeight;
|
||||
};
|
||||
|
||||
|
||||
struct cMipHeader {
|
||||
long numtex;
|
||||
void *offset;
|
||||
};
|
||||
|
||||
using MipHeader = std::vector<long>;
|
||||
|
||||
struct cMiptex{
|
||||
char name[16];
|
||||
unsigned long width, height,
|
||||
offset1, offset2, offset4, offset8;
|
||||
};
|
||||
|
||||
struct rlMipTex {
|
||||
char name[16];
|
||||
unsigned long width, height;
|
||||
unsigned long glTextureID;
|
||||
int type;
|
||||
};
|
||||
|
||||
|
||||
struct Node {
|
||||
long planeId;
|
||||
unsigned short front, back;
|
||||
qBoundingBoxShort box;
|
||||
unsigned short faceId, faceNum;
|
||||
};
|
||||
struct Leaf {
|
||||
long type, vislist;
|
||||
qBoundingBoxShort bound;
|
||||
unsigned short lfaceId, lfaceNum;
|
||||
unsigned char sndWater, sndSky, sndSlime, sndLava;
|
||||
};
|
||||
|
||||
|
||||
struct qColor {
|
||||
unsigned char r, g, b;
|
||||
};
|
||||
|
||||
|
||||
class Palette {
|
||||
public:
|
||||
std::vector<qColor> data;
|
||||
Palette() { data = std::vector<qColor>(256); }
|
||||
Palette(std::string path) { LoadPalette(path); }
|
||||
|
||||
void LoadPalette(std::string path);
|
||||
~Palette() {}
|
||||
};
|
||||
|
||||
struct MapData {
|
||||
BSPHeader header;
|
||||
|
||||
|
||||
std::vector<Vector3> vertices;
|
||||
std::vector<Edge> edges;
|
||||
std::vector<qModel> models;
|
||||
|
||||
std::vector<Surface> texInfo;
|
||||
std::vector<Face> faces;
|
||||
std::vector<int> ledges;
|
||||
std::vector<rlMipTex> textures;
|
||||
|
||||
|
||||
|
||||
std::vector<CalculatedFace> renderFaces;
|
||||
|
||||
|
||||
};
|
||||
|
||||
class MapFile {
|
||||
public:
|
||||
Debug::DebugState ds = Debug::NONE;
|
||||
MapData data;
|
||||
Palette* pal;
|
||||
|
||||
int modelsCDBG = 0;
|
||||
int verticesCDBG = 0;
|
||||
int edgesCDBG = 0;
|
||||
int texInfoCDBG = 0;
|
||||
int facesCDBG = 0;
|
||||
|
||||
MapFile(){}
|
||||
MapFile(std::string path) {
|
||||
LoadBSPMap(path);
|
||||
}
|
||||
void LoadBSPMap(std::string path);
|
||||
~MapFile(){}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
struct std::formatter<MPFW::Quake::Maps::DirectoryEntry> {
|
||||
|
||||
|
||||
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
|
||||
auto format(const MPFW::Quake::Maps::DirectoryEntry& in, std::format_context& ctx) const {
|
||||
return std::format_to(ctx.out(), "([MPFW::Quake::Maps::DirectoryEntry]: at pos {}, {}B in size)", in.offset, in.size);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct std::formatter<MPFW::Quake::Maps::MapData> {
|
||||
|
||||
|
||||
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
|
||||
auto format(const MPFW::Quake::Maps::MapData& in, std::format_context& ctx) const {
|
||||
return std::format_to(ctx.out(),
|
||||
"([MPFW::Quake::Maps::MapFile]:\n"
|
||||
" Vertices: {}\n"
|
||||
" Edges: {}\n"
|
||||
" Models: {}\n"
|
||||
" TexInfo: {}\n"
|
||||
" Faces: {}\n"
|
||||
" Ledges: {}\n"
|
||||
")", in.vertices.size(), in.edges.size(), in.models.size(), in.texInfo.size(), in.faces.size(), in.ledges.size());
|
||||
}
|
||||
|
||||
};
|
||||
|
5
mpfw/MPFW_UI.h
Normal file
5
mpfw/MPFW_UI.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace MPFW {
|
||||
|
||||
}
|
153
mpfw/MPFW_Utils.cpp
Normal file
153
mpfw/MPFW_Utils.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#include "MPFW_Utils.h"
|
||||
#include <Windows.h>
|
||||
#include <gl/GL.h>
|
||||
#include <gl/GLU.h>
|
||||
bool MPFW::Utils::Strings::IterativeComp(std::string originalString, int startPointer, std::string toCompare)
|
||||
{
|
||||
bool retVal = true;
|
||||
for (int i = 0; i < toCompare.length(); i++) {
|
||||
retVal = originalString.at(startPointer + i) == toCompare.at(i);
|
||||
if (retVal == false) break;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
std::string MPFW::Utils::Strings::IterativeStringExcerpt(std::string originalString, int startPointer, int length)
|
||||
{
|
||||
std::string retval = "";
|
||||
if (startPointer == -1) return retval;
|
||||
for (int i = 0; i < length; i++) {
|
||||
retval += originalString[startPointer + i];
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string MPFW::Utils::Strings::IterativeStringExcerpt_delim(std::string originalString, int startPointer, char delimiter)
|
||||
{
|
||||
std::string retval = "";
|
||||
if (startPointer == -1) return retval;
|
||||
for (int i = startPointer; i < originalString.size(); i++) {
|
||||
if (originalString[i] == delimiter) break;
|
||||
retval += originalString[i];
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int32_t MPFW::Utils::Strings::StringIndexToInteger_4b_le(std::string originalString, int startPointer)
|
||||
{
|
||||
int32_t retval;
|
||||
|
||||
retval = (unsigned char)originalString.at(startPointer + 3) << 24;
|
||||
retval += (unsigned char)originalString.at(startPointer + 2) << 16;
|
||||
retval += (unsigned char)originalString.at(startPointer + 1) << 8;
|
||||
retval += (unsigned char)originalString.at(startPointer);
|
||||
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int32_t MPFW::Utils::Strings::StringIndexToInteger_2b_le(std::string originalString, int startPointer)
|
||||
{
|
||||
int32_t retval;
|
||||
|
||||
retval = (unsigned char)originalString.at(startPointer + 1) << 8;
|
||||
retval += (unsigned char)originalString.at(startPointer);
|
||||
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> MPFW::Utils::Strings::ParseCLineIntoKeyVal(std::string originalString)
|
||||
{
|
||||
std::tuple<std::string, std::string> retval;
|
||||
|
||||
int equPos = -1, valstartpos = -1, valendpos = -1;
|
||||
bool inQuotes = false;
|
||||
|
||||
for (int i = 0; i < originalString.size(); i++) {
|
||||
if (originalString[i] == '=' and equPos == -1) {
|
||||
equPos = i;
|
||||
valstartpos = i + 1;
|
||||
}
|
||||
if (originalString[i] == '"') {
|
||||
if (inQuotes) {
|
||||
valendpos = i - 1;
|
||||
break;
|
||||
}
|
||||
if (valstartpos == -1) {
|
||||
valstartpos = i + 1;
|
||||
inQuotes = true;
|
||||
}
|
||||
}
|
||||
if (originalString[i] == ';' and not inQuotes) {
|
||||
valendpos = i - 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
retval = std::make_tuple<std::string, std::string>(IterativeStringExcerpt(originalString, 0, equPos), IterativeStringExcerpt(originalString, valstartpos, valendpos - valstartpos + 1));
|
||||
}
|
||||
catch(...){
|
||||
retval = std::make_tuple<std::string, std::string>("", "");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string MPFW::Utils::Strings::SingleWordTrimmer(std::string originalString)
|
||||
{
|
||||
for (int i = 0; i < originalString.size(); i++) {
|
||||
if (originalString[i] == ' ' or originalString[i] == '/') return IterativeStringExcerpt(originalString, 0, i);
|
||||
}
|
||||
|
||||
return originalString;
|
||||
}
|
||||
|
||||
float MPFW::Utils::Strings::StringIndexToFloat_4b_le(std::string originalString, int startPointer)
|
||||
{
|
||||
|
||||
|
||||
unsigned char buf[4];
|
||||
|
||||
buf[0] = originalString[startPointer];
|
||||
buf[1] = originalString[startPointer + 1];
|
||||
buf[2] = originalString[startPointer + 2];
|
||||
buf[3] = originalString[startPointer + 3];
|
||||
|
||||
|
||||
float retval = (*(float*)buf);
|
||||
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
std::basic_string<MPFW::Utils::Geometry::Triangle2D> MPFW::Utils::Geometry::TesselateVertexString(VertexString vs)
|
||||
{
|
||||
|
||||
std::basic_string<Triangle2D> retval;
|
||||
|
||||
/*GLUtesselator* tess = gluNewTess();
|
||||
|
||||
|
||||
gluTessBeginPolygon(tess, 0);
|
||||
|
||||
for (int i = 0; i < vs.size(); i++) {
|
||||
GLdouble* coords;
|
||||
coords[0] = static_cast<double>(vs[i].x);
|
||||
coords[1] = 0;
|
||||
coords[2] = static_cast<double>(vs[i].y);
|
||||
gluTessVertex(tess, coords, coords);
|
||||
}
|
||||
gluTessEndPolygon(tess);
|
||||
|
||||
|
||||
|
||||
gluDeleteTess(tess);
|
||||
|
||||
*/
|
||||
throw;
|
||||
|
||||
return retval;
|
||||
}
|
28
mpfw/MPFW_Utils.h
Normal file
28
mpfw/MPFW_Utils.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
|
||||
namespace MPFW {
|
||||
namespace Utils {
|
||||
namespace Geometry {
|
||||
struct Vertex2D {
|
||||
float x, y;
|
||||
};
|
||||
struct Triangle2D {
|
||||
float a, b, c;
|
||||
};
|
||||
using VertexString = std::basic_string<Vertex2D>;
|
||||
|
||||
std::basic_string<Triangle2D> TesselateVertexString(VertexString vs);
|
||||
}
|
||||
namespace Strings {
|
||||
bool IterativeComp(std::string originalString, int startPointer, std::string toCompare);
|
||||
std::string IterativeStringExcerpt(std::string originalString, int startPointer, int length);
|
||||
std::string IterativeStringExcerpt_delim(std::string originalString, int startPointer, char delimiter);
|
||||
int32_t StringIndexToInteger_4b_le(std::string originalString, int startPointer);
|
||||
int32_t StringIndexToInteger_2b_le(std::string originalString, int startPointer);
|
||||
std::tuple<std::string, std::string> ParseCLineIntoKeyVal(std::string originalString);
|
||||
std::string SingleWordTrimmer(std::string originalString);
|
||||
float StringIndexToFloat_4b_le(std::string originalString, int startPointer);
|
||||
}
|
||||
}
|
||||
}
|
530
mpfw/main.cpp
Normal file
530
mpfw/main.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
#include <iostream>
|
||||
#include <raylib.h>
|
||||
|
||||
#include <raymath.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "MPFW_Quake.h"
|
||||
#include "MPFW_Console.h"
|
||||
#include <print>
|
||||
#include <rlgl.h>
|
||||
#include <thread>
|
||||
|
||||
|
||||
// turn quake miptex into rl texture
|
||||
Texture2D RLMT_QUAKE(MPFW::Quake::Maps::rlMipTex rlmt) {
|
||||
Texture2D retval;
|
||||
|
||||
|
||||
retval.id = rlmt.glTextureID;
|
||||
retval.width = rlmt.width;
|
||||
retval.height = rlmt.height;
|
||||
retval.format = rlmt.type;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
Camera camera = { 0 };
|
||||
|
||||
Vector3 rotation = { 1,0,0 };
|
||||
Vector3 hRotation = { 1,0,0 };
|
||||
Vector3 velocity = { 0,0,0 };
|
||||
float accelFactor = 300.0f;
|
||||
|
||||
struct ConsoleSettings {
|
||||
int height;
|
||||
};
|
||||
|
||||
// this is grossly imprecise because of a lack of direct interfaces
|
||||
// with the loop, but it's simple and should be performant in a
|
||||
// thread
|
||||
//static void __DebugCounter_Quake(MPFW::Quake::Maps::MapFile* mapFile) {
|
||||
// while (mapFile->ds != MPFW::Quake::Maps::Debug::DONE) {
|
||||
// switch (mapFile->ds) {
|
||||
// case MPFW::Quake::Maps::Debug::HEADER:
|
||||
// std::print("Reading header\n");
|
||||
// break;
|
||||
// case MPFW::Quake::Maps::Debug::MODELS:
|
||||
// std::print("Model {}/{}\n", mapFile->modelsCDBG, mapFile->data.header.models.size / 64);
|
||||
// break;
|
||||
// case MPFW::Quake::Maps::Debug::VERTICES:
|
||||
// std::print("Vertex {}/{}\n", mapFile->verticesCDBG, mapFile->data.header.vertices.size / 12);
|
||||
// break;
|
||||
// case MPFW::Quake::Maps::Debug::EDGES:
|
||||
// std::print("Edge {}/{}\n", mapFile->edgesCDBG, mapFile->data.header.edges.size / 4);
|
||||
// break;
|
||||
// case MPFW::Quake::Maps::Debug::TEXINFO:
|
||||
// std::print("Surface {}/{}\n", mapFile->texInfoCDBG, mapFile->data.header.texinfo.size / 40);
|
||||
// break;
|
||||
// case MPFW::Quake::Maps::Debug::LEDGES:
|
||||
// std::print("Ledge {}/{}\n", mapFile->data.ledges.size(), mapFile->data.header.ledges.size / 2);
|
||||
// break;
|
||||
// case MPFW::Quake::Maps::Debug::FACES:
|
||||
// std::print("Face {}/{}\n", mapFile->data.faces.size(), mapFile->data.header.faces.size / 20);
|
||||
// break;
|
||||
// }
|
||||
//#pragma warning(suppress : 4996)
|
||||
// _sleep(250);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
void Look() {
|
||||
|
||||
hRotation = Vector3RotateByAxisAngle(hRotation, { 0,1,0 }, -GetMouseDelta().x * GetFrameTime());
|
||||
rotation = Vector3RotateByAxisAngle(rotation, { 0,1,0 }, -GetMouseDelta().x * GetFrameTime());
|
||||
rotation = Vector3Normalize({ rotation.x, Clamp(rotation.y - GetMouseDelta().y * GetFrameTime(), -0.99, 0.99), rotation.z });
|
||||
}
|
||||
|
||||
void AirAccelerate(bool inAir) {
|
||||
if (IsKeyDown(KEY_W)) {
|
||||
velocity += hRotation * GetFrameTime() * accelFactor * (IsKeyDown(KEY_LEFT_SHIFT) ? 2 : 1);
|
||||
}
|
||||
if (IsKeyDown(KEY_S)) {
|
||||
velocity -= hRotation * GetFrameTime() * accelFactor;
|
||||
}
|
||||
|
||||
Vector3 sideRotation = Vector3RotateByAxisAngle(hRotation, { 0,1,0 }, DEG2RAD * 90);
|
||||
|
||||
if (IsKeyDown(KEY_A)) {
|
||||
velocity += sideRotation * GetFrameTime() * accelFactor * (IsKeyDown(KEY_LEFT_SHIFT) ? 2 : 1);
|
||||
}
|
||||
if (IsKeyDown(KEY_D)) {
|
||||
velocity -= sideRotation * GetFrameTime() * accelFactor * (IsKeyDown(KEY_LEFT_SHIFT) ? 2 : 1);
|
||||
}
|
||||
if (inAir)velocity.y -= GetFrameTime();
|
||||
velocity.x *= 16 * GetFrameTime();
|
||||
velocity.z *= 16 * GetFrameTime();
|
||||
}
|
||||
|
||||
void Accelerate() {
|
||||
if (IsKeyDown(KEY_SPACE))velocity.y += accelFactor*0.01;
|
||||
AirAccelerate(false);
|
||||
AirAccelerate(false);
|
||||
|
||||
if (velocity.y < 0) velocity.y = 0;
|
||||
velocity *= 16*GetFrameTime();
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
template <typename T>
|
||||
void ___test___(T a, T b) {
|
||||
if (a != b) throw;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
std::print("COMPILED IN DEBUG MODE\n----------------------\n\n");
|
||||
|
||||
std::print("Unit testing\n\n\n");
|
||||
|
||||
{ // UT01 : formatting a quake map directory entry
|
||||
std::print("UT01 : formatting a quake map directory entry\n");
|
||||
MPFW::Quake::Maps::DirectoryEntry dirent;
|
||||
|
||||
dirent.offset = 1000;
|
||||
dirent.size = 2000;
|
||||
|
||||
std::string retval = std::format("{}", dirent);
|
||||
std::print("{}", retval);
|
||||
___test___(retval, std::string("([MPFW::Quake::Maps::DirectoryEntry]: at pos 1000, 2000B in size)"));
|
||||
|
||||
std::println();
|
||||
std::print("UT01 Success\n");
|
||||
|
||||
}
|
||||
|
||||
std::print("\nEnd of unit testing\n\n\n");
|
||||
#endif
|
||||
|
||||
MPFW::Quake::Maps::MapFile map;
|
||||
// std::thread t(__DebugCounter_Quake, &map);
|
||||
|
||||
// t.detach();
|
||||
|
||||
std::vector<Color> colors;
|
||||
|
||||
for (int i = 0; i < 3000000; i++) {
|
||||
colors.push_back({ (unsigned char)GetRandomValue(0, 255), (unsigned char)GetRandomValue(0, 255), (unsigned char)GetRandomValue(0, 255), 255 });
|
||||
}
|
||||
|
||||
std::print("MAP DATA:\n---------\n\n");
|
||||
std::print("Version: {}\n", map.data.header.version);
|
||||
std::print("\n---\nDIRENTS\n-------\n");
|
||||
std::print("Entities: {}\n", map.data.header.entities);
|
||||
std::print("Planes: {}\n", map.data.header.planes);
|
||||
std::print("Wall Textures (miptex): {}\n", map.data.header.miptex);
|
||||
std::print("Map Vertices: {}\n", map.data.header.vertices);
|
||||
std::print("Leaves Visibility Lists: {}\n", map.data.header.visilist);
|
||||
std::print("BSP Nodes: {}\n", map.data.header.nodes);
|
||||
std::print("Texture Info for Faces: {}\n", map.data.header.texinfo);
|
||||
std::print("Faces of each surface: {}\n", map.data.header.faces);
|
||||
std::print("Wall Lightmaps: {}\n", map.data.header.lightmaps);
|
||||
std::print("Clip Nodes: {}\n", map.data.header.clipnodes);
|
||||
std::print("BSP Leaves: {}\n", map.data.header.leaves);
|
||||
std::print("List of Faces: {}\n", map.data.header.lface);
|
||||
std::print("Edges of Faces: {}\n", map.data.header.edges);
|
||||
std::print("List of Edges: {}\n", map.data.header.ledges);
|
||||
std::print("Models: {}\n", map.data.header.models);
|
||||
|
||||
std::print("---\n\n");
|
||||
|
||||
std::print("Vertex count: {} (on {} in header)\n", map.data.vertices.size(), map.data.header.vertices.size / sizeof(Vector3));
|
||||
std::print("Edge count: {} (on {} in header)\n", map.data.edges.size(), map.data.header.edges.size / 4);
|
||||
|
||||
std::print("Model count: {} (on {} in header)\n", map.data.models.size(), map.data.header.models.size / sizeof(MPFW::Quake::Maps::qModel));
|
||||
for (int i = 0; i < map.data.models.size(); i++) {
|
||||
if (map.data.models[i].node_id3 != 0) {
|
||||
std::print("node 3 on {} isn't 0 ({})\n", i, map.data.models[i].node_id3);
|
||||
}
|
||||
}
|
||||
|
||||
map.pal = new MPFW::Quake::Maps::Palette("data/palette.lmp");
|
||||
|
||||
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
||||
|
||||
InitWindow(1280, 720, TextFormat("mpfw"));
|
||||
SetTargetFPS(60);
|
||||
SetExitKey(0);
|
||||
DisableCursor();
|
||||
|
||||
|
||||
MPFW::Console::CommandHandlerResources chr;
|
||||
chr.mapQuake = ↦
|
||||
|
||||
MPFW::Console::CommandHandler cmdH(&chr);
|
||||
|
||||
Font robotoMonoRegular = LoadFontEx("data/fonts/RobotoMono/RobotoMono-Regular.ttf", 30, NULL, 6000);
|
||||
int robotoMonoHeight = MeasureTextEx(robotoMonoRegular, "X", 30, 0).y;
|
||||
|
||||
|
||||
cmdH.RunScript("data/cfg/startup.cfg");
|
||||
|
||||
|
||||
|
||||
// rlSetClipPlanes(1, INFINITY);
|
||||
camera.position = { 0,5,0 };
|
||||
camera.target = { 1,0,0 };
|
||||
camera.fovy = 120;
|
||||
camera.up = { 0,1,0 };
|
||||
camera.projection = CAMERA_PERSPECTIVE;
|
||||
|
||||
bool indivFaceMode = false;
|
||||
int indivFace = 0;
|
||||
bool consoleOn = false;
|
||||
std::string cmdBuf = "";
|
||||
while (!WindowShouldClose()) {
|
||||
if (IsKeyPressed(KEY_APOSTROPHE)) {
|
||||
consoleOn = !consoleOn;
|
||||
}
|
||||
if(!consoleOn){
|
||||
Look();
|
||||
if (camera.position.y > 6) AirAccelerate(true);
|
||||
else Accelerate();
|
||||
|
||||
|
||||
if (IsKeyPressed(KEY_LEFT) && indivFace > 0) indivFace--;
|
||||
if (IsKeyPressed(KEY_RIGHT) && indivFace < map.data.faces.size() - 1) indivFace++;
|
||||
if (IsKeyPressed(KEY_F)) indivFaceMode = !indivFaceMode;
|
||||
|
||||
}
|
||||
|
||||
camera.position += velocity;
|
||||
camera.target = camera.position + rotation;
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
DrawRectangleGradientV(0, 0, GetScreenWidth(), GetScreenHeight(), WHITE, LIGHTGRAY);
|
||||
|
||||
BeginMode3D(camera);
|
||||
// DrawGrid(1000, 10);
|
||||
if (!indivFaceMode) {
|
||||
for (int i = 0; i < map.data.vertices.size(); i++) {
|
||||
// DrawPoint3D(MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[i]), BLACK);
|
||||
// if (i != 0) DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[i]), MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[i - 1]), PURPLE);
|
||||
}
|
||||
for (int i = 0; i < map.data.edges.size(); i++) {
|
||||
// DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[i].vertex0]), MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[i].vertex1]), PURPLE);
|
||||
}
|
||||
|
||||
for (int i = 0; i < map.data.faces.size(); i++) {
|
||||
|
||||
std::vector<Vector3> vertices;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Vector3 a = MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[abs(map.data.ledges[map.data.faces[i].ledgeId])].vertex0]);
|
||||
|
||||
|
||||
for (int k = 0; k < map.data.faces[i].ledgeNum; k++) {
|
||||
if (map.data.ledges[(unsigned int)(map.data.faces[i].ledgeId) + k] < 0) {
|
||||
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex0
|
||||
]
|
||||
));
|
||||
}
|
||||
else {
|
||||
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex1
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/*DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex0
|
||||
]
|
||||
),
|
||||
|
||||
MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex1
|
||||
]
|
||||
), RED);*/
|
||||
|
||||
}
|
||||
|
||||
for(int j = vertices.size() - 1; j > 0; j--){
|
||||
// rlColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||
rlColor4ub(255, 255, 255, 255);
|
||||
rlSetTexture(map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].glTextureID);
|
||||
rlBegin(RL_QUADS); // textures don't work well with triangles in rlgl for some reason
|
||||
|
||||
// rlTexCoord2f(0, 0);
|
||||
rlTexCoord2f(
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[0]), map.data.texInfo[map.data.faces[i].texinfoId].vectorS) + map.data.texInfo[map.data.faces[i].texinfoId].distS) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].width,
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[0]), map.data.texInfo[map.data.faces[i].texinfoId].vectorT) + map.data.texInfo[map.data.faces[i].texinfoId].distT) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].height
|
||||
);
|
||||
rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z);
|
||||
// rlTexCoord2f(0, 1);
|
||||
rlTexCoord2f(
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j]), map.data.texInfo[map.data.faces[i].texinfoId].vectorS) + map.data.texInfo[map.data.faces[i].texinfoId].distS) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].width,
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j]), map.data.texInfo[map.data.faces[i].texinfoId].vectorT) + map.data.texInfo[map.data.faces[i].texinfoId].distT) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].height
|
||||
);
|
||||
rlVertex3f(vertices[j].x, vertices[j].y, vertices[j].z);
|
||||
// rlTexCoord2f(1, 1);
|
||||
rlTexCoord2f(
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j - 1]), map.data.texInfo[map.data.faces[i].texinfoId].vectorS) + map.data.texInfo[map.data.faces[i].texinfoId].distS) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].width,
|
||||
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j - 1]), map.data.texInfo[map.data.faces[i].texinfoId].vectorT) + map.data.texInfo[map.data.faces[i].texinfoId].distT) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].height
|
||||
);
|
||||
rlVertex3f(vertices[j - 1].x, vertices[j - 1].y, vertices[j - 1].z);
|
||||
rlVertex3f(vertices[j - 1].x, vertices[j - 1].y, vertices[j - 1].z);
|
||||
|
||||
rlEnd();
|
||||
// rlColor4ub(255, 255, 255, 255);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
int i = indivFace;
|
||||
|
||||
std::vector<Vector3> vertices;
|
||||
|
||||
rlColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||
rlBegin(RL_TRIANGLES);
|
||||
|
||||
|
||||
|
||||
Vector3 a = MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[abs(map.data.ledges[map.data.faces[i].ledgeId])].vertex0]);
|
||||
|
||||
|
||||
for (int k = 0; k < map.data.faces[i].ledgeNum; k++) {
|
||||
if (map.data.ledges[map.data.faces[i].ledgeId + k] < 0) {
|
||||
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex1
|
||||
]
|
||||
));
|
||||
}
|
||||
else {
|
||||
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex0
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex0
|
||||
]
|
||||
),
|
||||
|
||||
MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex1
|
||||
]
|
||||
), RED);
|
||||
|
||||
}
|
||||
|
||||
|
||||
rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z);
|
||||
|
||||
rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z);
|
||||
rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z);
|
||||
|
||||
|
||||
|
||||
|
||||
rlEnd();
|
||||
rlColor4f(255, 255, 255, 255);
|
||||
}
|
||||
EndMode3D();
|
||||
DrawFPS(0, 0);
|
||||
if (indivFaceMode) {
|
||||
|
||||
|
||||
std::vector<Vector3> vertices;
|
||||
{
|
||||
int i = indivFace;
|
||||
|
||||
|
||||
rlColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||
rlBegin(RL_TRIANGLES);
|
||||
|
||||
|
||||
|
||||
Vector3 a = MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[abs(map.data.ledges[map.data.faces[i].ledgeId])].vertex0]);
|
||||
|
||||
|
||||
for (int k = 0; k < map.data.faces[i].ledgeNum; k++) {
|
||||
if (map.data.ledges[map.data.faces[i].ledgeId + k] < 0) {
|
||||
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex1
|
||||
]
|
||||
));
|
||||
}
|
||||
else {
|
||||
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||
map.data.vertices[
|
||||
map.data.edges[
|
||||
abs(
|
||||
map.data.ledges[
|
||||
map.data.faces[i].ledgeId + k
|
||||
]
|
||||
)
|
||||
].vertex0
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
DrawText(TextFormat("Current face: %i", indivFace), 0, 30, 30, BLACK);
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
DrawText(TextFormat("Vertex %i : %f %f %f", i, vertices[i].x, vertices[i].y, vertices[i].z), 0, 60 + 10 * i, 10, BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
if (consoleOn) {
|
||||
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight() / 2, {0,0,0,200});
|
||||
DrawTextEx(robotoMonoRegular, cmdH.logStr.c_str(), { 0, GetScreenHeight() / 2 - MeasureTextEx(robotoMonoRegular, cmdH.logStr.c_str(), 20, 0).y }, 20, 0, WHITE);
|
||||
|
||||
DrawRectangle(0, GetScreenHeight() / 2, GetScreenWidth(), 20, { 0,0,0,200 });
|
||||
DrawLine(0, GetScreenHeight() / 2, GetScreenWidth(), GetScreenHeight() / 2, GOLD);
|
||||
DrawTextEx(robotoMonoRegular, cmdBuf.c_str(), { 0, (float)GetScreenHeight() / 2 }, 20, 0, GRAY);
|
||||
|
||||
int key = GetCharPressed();
|
||||
while (key > 0) {
|
||||
if ((key >= 32) && (key <= 126)) {
|
||||
cmdBuf += (char)key;
|
||||
|
||||
}
|
||||
key = GetCharPressed();
|
||||
}
|
||||
if (IsKeyPressed(KEY_BACKSPACE) && cmdBuf != "") cmdBuf.pop_back();
|
||||
|
||||
if (IsKeyPressed(KEY_ENTER)) {
|
||||
cmdH.Run(cmdBuf);
|
||||
cmdBuf = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
CloseWindow();
|
||||
}
|
159
mpfw/mpfw.vcxproj
Normal file
159
mpfw/mpfw.vcxproj
Normal file
@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{fb36e71a-46ab-4ece-9438-e683c4208fb1}</ProjectGuid>
|
||||
<RootNamespace>mpfw</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>raylib.lib;winmm.lib;gdi32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>raylib.lib;winmm.lib;gdi32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="MPFW_Console.cpp" />
|
||||
<ClCompile Include="MPFW_CVars.cpp" />
|
||||
<ClCompile Include="MPFW_HL.cpp" />
|
||||
<ClCompile Include="MPFW_Quake.cpp" />
|
||||
<ClCompile Include="MPFW_Utils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MPFW_Console.h" />
|
||||
<ClInclude Include="MPFW_CVars.h" />
|
||||
<ClInclude Include="MPFW_HL.h" />
|
||||
<ClInclude Include="MPFW_MPFWMF.h" />
|
||||
<ClInclude Include="MPFW_Quake.h" />
|
||||
<ClInclude Include="MPFW_UI.h" />
|
||||
<ClInclude Include="MPFW_Utils.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
60
mpfw/mpfw.vcxproj.filters
Normal file
60
mpfw/mpfw.vcxproj.filters
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MPFW_Utils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MPFW_HL.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MPFW_Quake.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MPFW_Console.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MPFW_CVars.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MPFW_HL.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MPFW_Utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MPFW_MPFWMF.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MPFW_Quake.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MPFW_Console.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MPFW_CVars.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MPFW_UI.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
11
mpfw/mpfw.vcxproj.user
Normal file
11
mpfw/mpfw.vcxproj.user
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LocalDebuggerWorkingDirectory>$(SolutionDir)/gameenv</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LocalDebuggerWorkingDirectory>$(SolutionDir)/gameenv</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
23
mpfw_server/Config.h
Normal file
23
mpfw_server/Config.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Config {
|
||||
|
||||
|
||||
struct Config {
|
||||
unsigned char maxPlayers = 16; // maximum players that can fit on the server.
|
||||
// original quake specs say up to 16, glquake slist
|
||||
// can show up to 255 (standard calls for uchar).
|
||||
|
||||
std::string serverName = "MPFWTESTSRV"; // left column in slist
|
||||
std::string serverBanner = "oh yeah this a mfing server!";
|
||||
std::string ip = "192.168.56.1";
|
||||
char unknownMagic[2] = {'x', 'D'}; // unknown short sent at the end of accepted connection request replies
|
||||
|
||||
int udpControl = 26000; // port to which clients will connect for control
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
534
mpfw_server/NetQuake.cpp
Normal file
534
mpfw_server/NetQuake.cpp
Normal file
@ -0,0 +1,534 @@
|
||||
#include "NetQuake.h"
|
||||
#include <iostream> // basic io
|
||||
#include <vector> // dynamic arrays
|
||||
#include <map> // keyval maps
|
||||
#include <asio.hpp>
|
||||
#include <asio/ts/buffer.hpp>
|
||||
#include <asio/ts/internet.hpp>
|
||||
#include <print>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <excpt.h>
|
||||
|
||||
NetQuake::SerializedGenericPacket NetQuake::Serialize(NetworkGenericPacket ngp)
|
||||
{
|
||||
SerializedGenericPacket sgp;
|
||||
|
||||
|
||||
unsigned short typeAsShort;
|
||||
|
||||
typeAsShort = (ngp.type[0] << 8) + ngp.type[1];
|
||||
sgp.type = (PacketType)typeAsShort;
|
||||
sgp.length = (ngp.length[0] << 8) + ngp.length[1];
|
||||
|
||||
sgp.remainder = ngp.remainder;
|
||||
|
||||
return sgp;
|
||||
}
|
||||
|
||||
NetQuake::GenericGamePacket NetQuake::toGenericGamePacket(SerializedGenericPacket sgp)
|
||||
{
|
||||
GenericGamePacket retval;
|
||||
try {
|
||||
|
||||
int currentPacketLength = 0;
|
||||
int currentPacketAt = 0;
|
||||
bool inString = false;
|
||||
for (int i = 0; i < sgp.remainder.size(); i++) {
|
||||
if (currentPacketLength == 0 && !inString) {
|
||||
if (sgp.remainder[i] == CLIENT_MESSAGE_NOOP) {
|
||||
ClientMessage msg;
|
||||
msg.type = CLIENT_MESSAGE_NOOP;
|
||||
retval.messageBlock.messages.push_back(msg);
|
||||
}
|
||||
if (sgp.remainder[i] == CLIENT_MESSAGE_KEEPALIVE) {
|
||||
ClientMessage msg;
|
||||
msg.type = CLIENT_MESSAGE_NOOP;
|
||||
retval.messageBlock.messages.push_back(msg);
|
||||
}
|
||||
if (sgp.remainder[i] == CLIENT_MESSAGE_MOVEMENT) {
|
||||
ClientMessage msg;
|
||||
msg.type = CLIENT_MESSAGE_MOVEMENT;
|
||||
retval.messageBlock.messages.push_back(msg);
|
||||
currentPacketLength = 15;
|
||||
}
|
||||
if (sgp.remainder[i] == CLIENT_MESSAGE_CONSOLE) {
|
||||
ClientMessage msg;
|
||||
msg.type = CLIENT_MESSAGE_CONSOLE;
|
||||
retval.messageBlock.messages.push_back(msg);
|
||||
|
||||
}
|
||||
}
|
||||
else if (inString) {
|
||||
if (sgp.remainder[i] == 0x00) inString = false;
|
||||
else {
|
||||
retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
|
||||
currentPacketAt++;
|
||||
if (currentPacketAt == currentPacketLength) {
|
||||
currentPacketLength = 0;
|
||||
currentPacketAt = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retval.valid = true;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
std::println("Something went wrong in serialized generic packet to generic game packet conversion: {}", e.what());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string NetQuake::Serialize(ServerInfoReply sirp)
|
||||
{
|
||||
std::string retval = "";
|
||||
|
||||
using namespace std::string_literals;
|
||||
retval += CONTROL_SERVER_INFO_REPLY;
|
||||
retval += sirp.address.ip + ":";
|
||||
retval += std::to_string(sirp.address.port) + "\0"s;
|
||||
retval += sirp.hostname + "\0"s;
|
||||
retval += sirp.levelname + "\0"s;
|
||||
retval += sirp.currentPlayers;
|
||||
retval += sirp.maxPlayers;
|
||||
retval += sirp.netVersion;
|
||||
|
||||
short retvalsize = retval.size() + 4;
|
||||
unsigned char retvalsizechar[2];
|
||||
|
||||
retvalsizechar[0] = retvalsize & 0xff;
|
||||
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||
|
||||
|
||||
std::string fretval = "\x80";
|
||||
fretval += "\0"s;
|
||||
fretval += retvalsizechar[1];
|
||||
fretval += retvalsizechar[0];
|
||||
fretval += retval;
|
||||
return fretval;
|
||||
}
|
||||
|
||||
std::string NetQuake::Serialize(ConnectionRequestReply crr)
|
||||
{
|
||||
std::string retval = "";
|
||||
using namespace std::string_literals;
|
||||
|
||||
|
||||
if (crr.reject) {
|
||||
retval = "\x82" + crr.reason + "\0"s;
|
||||
}
|
||||
else {
|
||||
|
||||
unsigned char portchar[2];
|
||||
portchar[1] = crr.port & 0xff;
|
||||
portchar[0] = (crr.port >> 8) & 0xff;
|
||||
retval = "\x81"s;
|
||||
retval += portchar[1];
|
||||
retval += portchar[0];
|
||||
retval += (unsigned char)crr.unknown[0];
|
||||
retval += (unsigned char)crr.unknown[1];
|
||||
}
|
||||
|
||||
|
||||
short retvalsize = retval.size() + 4;
|
||||
unsigned char retvalsizechar[2];
|
||||
|
||||
retvalsizechar[0] = retvalsize & 0xff;
|
||||
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||
|
||||
std::string fretval = "\x80";
|
||||
fretval += "\0"s;
|
||||
fretval += retvalsizechar[1];
|
||||
fretval += retvalsizechar[0];
|
||||
fretval += retval;
|
||||
return fretval;
|
||||
}
|
||||
|
||||
std::string NetQuake::AcknowledgePacket(int packet)
|
||||
{
|
||||
std::string retval;
|
||||
using namespace std::string_literals;
|
||||
retval += reinterpret_cast<char*>(packet);
|
||||
short retvalsize = retval.size() + 4;
|
||||
unsigned char retvalsizechar[2];
|
||||
|
||||
retvalsizechar[0] = retvalsize & 0xff;
|
||||
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||
|
||||
std::string fretval = "\0"s;
|
||||
fretval += "\x10"s;
|
||||
fretval += retvalsizechar[1];
|
||||
fretval += retvalsizechar[0];
|
||||
fretval += retval;
|
||||
return fretval;
|
||||
}
|
||||
|
||||
void NetQuake::NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning)
|
||||
{
|
||||
|
||||
asio::io_context io_context;
|
||||
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), config->udpControl));
|
||||
std::print("Started control server on {}\n", config->udpControl);
|
||||
|
||||
NetQuake::ServerInfoReply sir;
|
||||
sir.address.ip = config->ip;
|
||||
sir.address.port = config->udpControl;
|
||||
sir.currentPlayers = 0;
|
||||
sir.maxPlayers = config->maxPlayers;
|
||||
|
||||
sir.hostname = config->serverName;
|
||||
sir.levelname = "start";
|
||||
sir.netVersion = NetQuake::QGOLD;
|
||||
|
||||
|
||||
while (true) {
|
||||
asio::ip::udp::endpoint remote_endpoint;
|
||||
std::array<char, 1024> recv_buf;
|
||||
|
||||
|
||||
socket.receive_from(asio::buffer(recv_buf), remote_endpoint);
|
||||
|
||||
NetQuake::NetworkGenericPacket gpac;
|
||||
|
||||
gpac.type[0] = recv_buf[0];
|
||||
gpac.type[1] = recv_buf[1];
|
||||
gpac.length[0] = recv_buf[2];
|
||||
gpac.length[1] = recv_buf[3];
|
||||
for (int i = 4; i < recv_buf.size(); i++) {
|
||||
gpac.remainder += recv_buf[i];
|
||||
}
|
||||
|
||||
NetQuake::SerializedGenericPacket sgpac = NetQuake::Serialize(gpac);
|
||||
|
||||
std::cout << "new packet\n";
|
||||
switch (sgpac.type) {
|
||||
|
||||
case NetQuake::NQ_CONTROL_PACKET:
|
||||
{
|
||||
NetQuake::GenericControlPacket gcpac(sgpac);
|
||||
|
||||
std::cout << "GenericControlPacket: " << gcpac.opcode << "\n";
|
||||
|
||||
// the 0x01 i had so much trouble getting through
|
||||
if (gcpac.opcode == NetQuake::CONTROL_CONNECTION_REQUEST) {
|
||||
std::cout << "Connection Request\n";
|
||||
|
||||
int endpointRequestId = internalRequestConsole->newEndpointRequest(remote_endpoint);
|
||||
|
||||
while (!internalRequestConsole->getEndpointRequest(endpointRequestId).ready) {
|
||||
// waiting for endpoint request to go through
|
||||
}
|
||||
|
||||
Internal::NewEndpointRequestReply nerr = internalRequestConsole->getEndpointRequest(endpointRequestId);
|
||||
|
||||
NetQuake::ConnectionRequestReply crr;
|
||||
|
||||
crr.reject = nerr.error;
|
||||
crr.reason = nerr.reason;
|
||||
crr.port = nerr.port;
|
||||
|
||||
crr.unknown[0] = config->unknownMagic[0];
|
||||
crr.unknown[1] = config->unknownMagic[1];
|
||||
|
||||
asio::error_code e;
|
||||
socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
|
||||
/*NetQuake::ConnectionRequestReply crr;
|
||||
|
||||
crr.reject = false;
|
||||
crr.port = 26000;
|
||||
crr.unknown[0] = config->unknownMagic[0];
|
||||
crr.unknown[1] = config->unknownMagic[1];
|
||||
|
||||
asio::error_code e;
|
||||
socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
|
||||
*/
|
||||
}
|
||||
if (gcpac.opcode == NetQuake::CONTROL_SERVER_INFO_REQUEST) {
|
||||
NetQuake::ServerInfoRequest sirqpac(gcpac);
|
||||
|
||||
std::cout << "ServerInfoRequest: server info request for game \"" << sirqpac.gameName << "\", netversion " << sirqpac.netVersion << "\n";
|
||||
|
||||
std::string serializedTemplate = NetQuake::Serialize(sir);
|
||||
|
||||
std::ofstream templatestream("template.txt", std::ios::binary);
|
||||
templatestream << serializedTemplate;
|
||||
templatestream.close();
|
||||
asio::error_code e;
|
||||
socket.send_to(asio::buffer(serializedTemplate), remote_endpoint, 0, e);
|
||||
std::cout << e.message() << "\n";
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << "[WARNING] Unknown or unsupported packet came through.\n";
|
||||
break;
|
||||
}
|
||||
// std::cout << gcpac.opcode << "\n";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NetQuake::NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
|
||||
{
|
||||
asio::io_context io_context;
|
||||
asio::error_code e;
|
||||
|
||||
|
||||
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), port));
|
||||
|
||||
socket.connect(startRemote);
|
||||
|
||||
std::print("New endpoint server started on {}.\nClient Start Info: {} | {}\n", port, startRemote.address().to_string(), startRemote.port());
|
||||
|
||||
{ // startup block
|
||||
ServerMessageBlock smb;
|
||||
smb.type = NQ_MESSAGE_BLOCK_END;
|
||||
smb.order = 100;
|
||||
ServerMessages::Print serverBanner;
|
||||
|
||||
serverBanner.text = config->serverBanner;
|
||||
smb.serverMessages.push_back(&serverBanner);
|
||||
|
||||
|
||||
ServerMessages::ServerInfo serverInfo;
|
||||
|
||||
serverInfo.mapName = "start";
|
||||
serverInfo.maxClients = config->maxPlayers;
|
||||
serverInfo.multi = 1;
|
||||
serverInfo.numModels = 0;
|
||||
serverInfo.numSounds = 0;
|
||||
serverInfo.serverVersion = 15;
|
||||
|
||||
smb.serverMessages.push_back(&serverInfo);
|
||||
|
||||
ServerMessages::SignOn signOn;
|
||||
signOn.signon = 1; // prespawn(2=light,3=render)
|
||||
smb.serverMessages.push_back(&signOn);
|
||||
|
||||
std::print("Tried sending server banner to client.\n");
|
||||
|
||||
socket.send_to(asio::buffer(smb.Serialize()), startRemote, 0, e);
|
||||
std::ofstream smbattempt("smb.txt");
|
||||
smbattempt << smb.Serialize();
|
||||
smbattempt.close();
|
||||
|
||||
|
||||
std::print("Server banner should be sent.\n");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
asio::ip::udp::endpoint rec;
|
||||
std::string recv_buf;
|
||||
socket.receive_from(asio::buffer(recv_buf), rec);
|
||||
std::cout << "packet on secondary line " << port << "\n";
|
||||
std::cout << "received packet size: " << recv_buf.size() << "\n";
|
||||
if(recv_buf.size() > 4) {
|
||||
try {
|
||||
NetQuake::NetworkGenericPacket ngp;
|
||||
ngp.type[0] = recv_buf[0];
|
||||
ngp.type[1] = recv_buf[1];
|
||||
ngp.length[0] = recv_buf[2];
|
||||
ngp.length[1] = recv_buf[3];
|
||||
for (int i = 4; i < recv_buf.size(); i++) {
|
||||
ngp.remainder += recv_buf[i];
|
||||
}
|
||||
SerializedGenericPacket sgpac = Serialize(ngp);
|
||||
GenericGamePacket ggp = toGenericGamePacket(sgpac);
|
||||
|
||||
|
||||
if (ggp.type == NQ_MESSAGE_BLOCK_CHUNK || ggp.type == NQ_MESSAGE_BLOCK_END) {
|
||||
socket.send_to(asio::buffer(AcknowledgePacket(ggp.packetNumber)), rec);
|
||||
std::println("Server acknowledged packet {}", ggp.packetNumber);
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
std::println("Error on packet on {}", port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::println("Weird packet received... :shrug:");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetQuake::GenericControlPacket::GenericControlPacket(SerializedGenericPacket sgp)
|
||||
{
|
||||
|
||||
if (sgp.type != NQ_CONTROL_PACKET) {
|
||||
throw std::runtime_error("Wrong packet type given to generic control packet constructor.");
|
||||
}
|
||||
|
||||
opcode = (Control_OpCode)sgp.remainder[0];
|
||||
|
||||
remainder = sgp.remainder;
|
||||
remainder.erase(0, 1);
|
||||
|
||||
|
||||
}
|
||||
|
||||
NetQuake::ServerInfoRequest::ServerInfoRequest(GenericControlPacket gcp)
|
||||
{
|
||||
if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
|
||||
throw std::runtime_error("Wrong packet type given to server info request constructor.");
|
||||
}
|
||||
|
||||
std::string toParse = gcp.remainder;
|
||||
|
||||
int i = 0;
|
||||
for (; toParse[i] != '\0'; i++) {
|
||||
gameName += toParse[i];
|
||||
}
|
||||
netVersion = (NetProtocolVersion) toParse[i+1];
|
||||
|
||||
}
|
||||
|
||||
NetQuake::ConnectionRequest::ConnectionRequest(GenericControlPacket gcp)
|
||||
{
|
||||
if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
|
||||
throw std::runtime_error("Wrong packet type given to server info request constructor.");
|
||||
}
|
||||
|
||||
std::string toParse = gcp.remainder;
|
||||
|
||||
int i = 0;
|
||||
for (; toParse[i] != '\0'; i++) {
|
||||
gameName += toParse[i];
|
||||
}
|
||||
netVersion = (NetProtocolVersion)toParse[i + 1];
|
||||
|
||||
}
|
||||
|
||||
NetQuake::Internal::InternalRequestConsole::InternalRequestConsole()
|
||||
{
|
||||
std::print("New Internal Request Console Created.\n");
|
||||
}
|
||||
|
||||
int NetQuake::Internal::InternalRequestConsole::newEndpointRequest(asio::ip::udp::endpoint ep)
|
||||
{
|
||||
NewEndpointRequestReply nerr;
|
||||
nerr.ready = false;
|
||||
nerr.startRemote = ep;
|
||||
newEndpointRequests.push_back(nerr);
|
||||
return newEndpointRequests.size() - 1;
|
||||
}
|
||||
|
||||
NetQuake::Internal::NewEndpointRequestReply NetQuake::Internal::InternalRequestConsole::getEndpointRequest(int id)
|
||||
{
|
||||
return newEndpointRequests[id];
|
||||
}
|
||||
|
||||
void NetQuake::Internal::InternalRequestConsole::Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector)
|
||||
{
|
||||
|
||||
for (int i = lastEndpointRequestTreated; i < newEndpointRequests.size(); i++) {
|
||||
if (!newEndpointRequests[i].ready) {
|
||||
if(gameEndpointsVector->size() < config->maxPlayers){
|
||||
GameEndpoint* endpoint = new GameEndpoint(config, 1000+i, newEndpointRequests[i].startRemote);
|
||||
newEndpointRequests[i].port = 1000 + i;
|
||||
}
|
||||
else {
|
||||
newEndpointRequests[i].error = true;
|
||||
newEndpointRequests[i].reason = "too many players";
|
||||
}
|
||||
|
||||
newEndpointRequests[i].ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NetQuake::Internal::InternalRequestConsole::~InternalRequestConsole()
|
||||
{
|
||||
newEndpointRequests.clear();
|
||||
std::print("Internal Request Console Destroyed.\n");
|
||||
}
|
||||
|
||||
NetQuake::GameEndpoint::GameEndpoint()
|
||||
{
|
||||
throw; // constructor musn't be empty
|
||||
}
|
||||
|
||||
NetQuake::GameEndpoint::GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
|
||||
{
|
||||
std::thread internalInternalThread(NetQuake_EndpointServer, config, port, startRemote);
|
||||
internalThread.swap(internalInternalThread);
|
||||
}
|
||||
|
||||
NetQuake::GameEndpoint::~GameEndpoint()
|
||||
{
|
||||
internalThread.~thread();
|
||||
}
|
||||
|
||||
std::string NetQuake::ServerMessages::Print::Serialize() {
|
||||
using namespace std::string_literals;
|
||||
std::string retval = text;
|
||||
retval += "\0"s;
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string NetQuake::ServerMessages::ServerInfo::Serialize() {
|
||||
std::string retval = "";
|
||||
using namespace std::string_literals;
|
||||
retval += serverVersion;
|
||||
retval += maxClients;
|
||||
retval += mapName + "\0"s;
|
||||
for (int i = 0; i < precacheModels.size(); i++) {
|
||||
retval += precacheModels[i] + "\0"s;
|
||||
}
|
||||
retval += numModels;
|
||||
for (int i = 0; i < precacheSounds.size(); i++) {
|
||||
retval += precacheSounds[i] + "\0"s;
|
||||
}
|
||||
|
||||
retval += numSounds;
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
std::string NetQuake::ServerMessages::SignOn::Serialize() {
|
||||
std::string retval = "";
|
||||
|
||||
retval += signon;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string NetQuake::ServerMessageBlock::Serialize()
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
for (int i = 0; i < serverMessages.size(); i++) {
|
||||
retval += serverMessages[i]->type;
|
||||
retval += serverMessages[i]->Serialize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
short retvalsize = retval.size() + 4;
|
||||
unsigned char retvalsizechar[2];
|
||||
unsigned char typechar[2];
|
||||
|
||||
retvalsizechar[0] = retvalsize & 0xff;
|
||||
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||
typechar[1] = this->type & 0xff;
|
||||
typechar[0] = (this->type >> 8) & 0xff;
|
||||
|
||||
std::string fretval = std::string({(char)typechar[0]});
|
||||
fretval += typechar[1];
|
||||
fretval += retvalsizechar[1];
|
||||
fretval += (char)retvalsizechar[0];
|
||||
fretval += retval;
|
||||
return fretval;
|
||||
}
|
259
mpfw_server/NetQuake.h
Normal file
259
mpfw_server/NetQuake.h
Normal file
@ -0,0 +1,259 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include "Config.h"
|
||||
#include <asio/ts/internet.hpp>
|
||||
#include <asio.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace NetQuake {
|
||||
|
||||
|
||||
|
||||
struct NetworkGenericPacket { // generic packet straight from server
|
||||
char type[2];
|
||||
char length[2];
|
||||
std::string remainder;
|
||||
};
|
||||
|
||||
|
||||
enum PacketType {
|
||||
NQ_CONTROL_PACKET = 0x8000,
|
||||
NQ_MESSAGE_BLOCK_CHUNK = 0x0001,
|
||||
NQ_MESSAGE_BLOCK_END = 0x0009,
|
||||
NQ_ACK = 0x0002,
|
||||
NQ_UNRELIABLE = 0x0010,
|
||||
NQ_UNKNOWN_PACKET
|
||||
};
|
||||
|
||||
struct SerializedGenericPacket {
|
||||
PacketType type;
|
||||
int length;
|
||||
std::string remainder;
|
||||
};
|
||||
|
||||
SerializedGenericPacket Serialize(NetworkGenericPacket ngp);
|
||||
|
||||
|
||||
enum ClientMessageType {
|
||||
CLIENT_MESSAGE_NOOP = 0x00,
|
||||
CLIENT_MESSAGE_KEEPALIVE = 0x01,
|
||||
CLIENT_MESSAGE_DISCONNECT = 0x02,
|
||||
CLIENT_MESSAGE_MOVEMENT = 0x03,
|
||||
CLIENT_MESSAGE_CONSOLE = 0x04,
|
||||
CLIENT_MESSAGE_UNKNOWN
|
||||
};
|
||||
|
||||
struct ClientMessage {
|
||||
ClientMessageType type;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
struct ClientMessageBlock {
|
||||
std::vector<ClientMessage> messages;
|
||||
};
|
||||
|
||||
struct GenericGamePacket {
|
||||
PacketType type;
|
||||
int length;
|
||||
long packetNumber;
|
||||
ClientMessageBlock messageBlock;
|
||||
bool valid = false;
|
||||
};
|
||||
GenericGamePacket toGenericGamePacket(SerializedGenericPacket sgp);
|
||||
|
||||
enum DemoMessageType {
|
||||
DEM_BAD = 0x00,
|
||||
DEM_NOP = 0x01,
|
||||
DEM_DISCONNECT = 0x02,
|
||||
DEM_UPDATESTAT = 0x03,
|
||||
DEM_VERSION = 0x04,
|
||||
DEM_SETVIEW = 0x05,
|
||||
DEM_SOUND = 0x06,
|
||||
DEM_TIME = 0x07,
|
||||
DEM_PRINT = 0x08,
|
||||
DEM_STUFFTEXT = 0x09,
|
||||
DEM_SETANGLE = 0x0A,
|
||||
DEM_SERVERINFO = 0x0B,
|
||||
DEM_LIGHTSTYLE = 0x0C,
|
||||
DEM_UPDATENAME = 0x0D,
|
||||
DEM_UPDATEFRAGS = 0x0E,
|
||||
DEM_CLIENTDATA = 0x0F,
|
||||
DEM_STOPSOUND = 0x10,
|
||||
DEM_UPDATECOLORS = 0x11,
|
||||
DEM_PARTICLE = 0x12,
|
||||
DEM_DAMAGE = 0x13,
|
||||
DEM_SPAWNSTATIC = 0x14,
|
||||
DEM_SPAWNBINARY = 0x15,
|
||||
DEM_SPAWNBASELINE = 0x16,
|
||||
DEM_TEMP_ENTITY = 0x17,
|
||||
DEM_SETPAUSE = 0x18,
|
||||
DEM_SIGNONUM = 0x19,
|
||||
DEM_CENTERPRINT = 0x1A,
|
||||
DEM_KILLEDMONSTER = 0x1B,
|
||||
DEM_FOUNDSECRET = 0x1C,
|
||||
DEM_SPAWNSTATICSOUND = 0x1D,
|
||||
DEM_INTERMISSION = 0x1E,
|
||||
DEM_FINALE = 0x1F,
|
||||
DEM_CDTRACK = 0x20,
|
||||
DEM_SELLSCREEN = 0x21,
|
||||
DEM_CUTSCENE = 0x22,
|
||||
DEM_UPDATEENTITY = 0x80
|
||||
};
|
||||
|
||||
class GenericServerMessage {
|
||||
public:
|
||||
DemoMessageType type;
|
||||
virtual std::string Serialize() = 0;
|
||||
};
|
||||
|
||||
namespace ServerMessages {
|
||||
class Print : public GenericServerMessage { // 0x08
|
||||
public:
|
||||
std::string text = "";
|
||||
Print() { this->type = DEM_PRINT; }
|
||||
std::string Serialize();
|
||||
};
|
||||
class ServerInfo : public GenericServerMessage { // 0x0B
|
||||
public:
|
||||
long serverVersion = 15;
|
||||
long maxClients;
|
||||
long multi;
|
||||
std::string mapName;
|
||||
std::vector<std::string> precacheModels;
|
||||
long numModels;
|
||||
std::vector<std::string> precacheSounds;
|
||||
long numSounds;
|
||||
ServerInfo() {
|
||||
this->type = DEM_SERVERINFO;
|
||||
precacheModels = std::vector<std::string>(256);
|
||||
precacheSounds = std::vector<std::string>(256);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string Serialize();
|
||||
};
|
||||
|
||||
class SignOn : public GenericServerMessage {
|
||||
public:
|
||||
long signon;
|
||||
SignOn() { type = DEM_SIGNONUM; }
|
||||
std::string Serialize();
|
||||
};
|
||||
}
|
||||
|
||||
class ServerMessageBlock{
|
||||
public:
|
||||
PacketType type;
|
||||
int order;
|
||||
std::vector<GenericServerMessage*> serverMessages;
|
||||
std::string Serialize();
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum Control_OpCode {
|
||||
CONTROL_CONNECTION_REQUEST = 0x01,
|
||||
CONTROL_SERVER_INFO_REQUEST = 0x02,
|
||||
CONTROL_PLAYER_INFO_REQUEST = 0x03,
|
||||
CONTROL_RULE_INFO_REQUEST = 0x04,
|
||||
CONTROL_SERVER_INFO_REPLY = 0x83
|
||||
};
|
||||
|
||||
struct GenericControlPacket {
|
||||
Control_OpCode opcode;
|
||||
std::string remainder;
|
||||
GenericControlPacket(SerializedGenericPacket sgp);
|
||||
};
|
||||
|
||||
enum NetProtocolVersion {
|
||||
QTEST1 = 0x01,
|
||||
QUNKNOWN = 0x02,
|
||||
QGOLD = 0x03
|
||||
};
|
||||
|
||||
struct ServerInfoRequest {
|
||||
std::string gameName;
|
||||
NetProtocolVersion netVersion;
|
||||
ServerInfoRequest(GenericControlPacket gcp);
|
||||
};
|
||||
|
||||
struct Address {
|
||||
std::string ip;
|
||||
int port;
|
||||
};
|
||||
|
||||
struct ServerInfoReply {
|
||||
Address address;
|
||||
std::string hostname;
|
||||
std::string levelname;
|
||||
char currentPlayers;
|
||||
char maxPlayers;
|
||||
NetProtocolVersion netVersion;
|
||||
};
|
||||
|
||||
std::string Serialize(ServerInfoReply sirp);
|
||||
|
||||
struct ConnectionRequest {
|
||||
std::string gameName;
|
||||
NetProtocolVersion netVersion;
|
||||
ConnectionRequest(GenericControlPacket gcp);
|
||||
};
|
||||
|
||||
struct ConnectionRequestReply {
|
||||
bool reject;
|
||||
short port; // LITTLE-ENDIAN (!)(!)(!)(!)(!)
|
||||
char unknown[2]; // [ have fun :) ]
|
||||
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
::std::string Serialize(ConnectionRequestReply crr);
|
||||
|
||||
class GameEndpoint {
|
||||
std::thread internalThread;
|
||||
public:
|
||||
GameEndpoint();
|
||||
GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote);
|
||||
|
||||
~GameEndpoint();
|
||||
};
|
||||
|
||||
namespace Internal {
|
||||
|
||||
struct NewEndpointRequestReply {
|
||||
bool ready = false;
|
||||
bool error = false;
|
||||
std::string reason;
|
||||
int port;
|
||||
asio::ip::udp::endpoint startRemote;
|
||||
};
|
||||
|
||||
class InternalRequestConsole {
|
||||
std::vector<NewEndpointRequestReply> newEndpointRequests;
|
||||
int lastEndpointRequestTreated = 0;
|
||||
public:
|
||||
InternalRequestConsole();
|
||||
|
||||
int newEndpointRequest(asio::ip::udp::endpoint ep);
|
||||
NewEndpointRequestReply getEndpointRequest(int id);
|
||||
void Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector);
|
||||
~InternalRequestConsole();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
std::string AcknowledgePacket(int packet);
|
||||
|
||||
|
||||
void NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning);
|
||||
void NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote);
|
||||
|
||||
|
||||
}
|
85
mpfw_server/main.cpp
Normal file
85
mpfw_server/main.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
// // MPFW SERVER
|
||||
// //
|
||||
// * * //
|
||||
// * . * // implemented
|
||||
// *I* // |
|
||||
// * // v
|
||||
// // (Net)Quake(World)-compatible game server
|
||||
// // ^
|
||||
// // |
|
||||
// // eventually?
|
||||
// //
|
||||
// //
|
||||
//// github.com/safariminer
|
||||
/// tilde.town/~safariminer
|
||||
//
|
||||
|
||||
//
|
||||
// Notes relative to netquake
|
||||
// - based on : https://www.gamers.org/dEngine/quake/QDP/qnp.html
|
||||
// - Netquake = UDP
|
||||
// - all numbers = big endian unless exception
|
||||
// - don't blindly follow the spec:
|
||||
// | game endpoints in quake don't have auth
|
||||
// | do not use basic range, randomize ports throughout the available range
|
||||
// | verify ip and port
|
||||
// | even though the spec calls for it, don't display IP/port info in playerinfo control
|
||||
//
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define MPFW_SERVER_VERSION std::string(std::string("Debug Build ") + std::string(__DATE__))
|
||||
#else
|
||||
#define MPFW_SERVER_VERSION "Release build"
|
||||
#endif
|
||||
|
||||
#include <iostream> // basic io
|
||||
#include <vector> // dynamic arrays
|
||||
#include <map> // keyval maps
|
||||
#include <asio.hpp>
|
||||
#include <asio/ts/buffer.hpp>
|
||||
#include <asio/ts/internet.hpp>
|
||||
#include <print>
|
||||
|
||||
#include "Config.h"
|
||||
#include "NetQuake.h"
|
||||
#include <thread>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
bool _serverRunning = true;
|
||||
|
||||
|
||||
int rcp = 0;
|
||||
|
||||
Config::Config *config = new Config::Config;
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
|
||||
std::print("MPFW Server\nBuild Version: {}\n--------------------\n", MPFW_SERVER_VERSION);
|
||||
|
||||
std::vector<NetQuake::GameEndpoint> gameEndpoints;
|
||||
NetQuake::Internal::InternalRequestConsole* internalRequestConsole = new NetQuake::Internal::InternalRequestConsole();
|
||||
|
||||
asio::io_context io_context;
|
||||
|
||||
std::print("Starting control server thread...\n");
|
||||
|
||||
std::thread controlServer(NetQuake::NetQuake_ControlServer, internalRequestConsole, config, &_serverRunning);
|
||||
|
||||
controlServer.detach();
|
||||
std::print("Started control server thread\n");
|
||||
|
||||
|
||||
|
||||
while (_serverRunning) {
|
||||
internalRequestConsole->Update(config, &gameEndpoints);
|
||||
}
|
||||
|
||||
delete internalRequestConsole;
|
||||
return 0;
|
||||
}
|
144
mpfw_server/mpfw_server.vcxproj
Normal file
144
mpfw_server/mpfw_server.vcxproj
Normal file
@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{67ceac9d-b0f3-4ab3-b3ff-0a9adad98e45}</ProjectGuid>
|
||||
<RootNamespace>mpfwserver</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>winmm.lib;ws2_32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>winmm.lib;ws2_32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="NetQuake.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Config.h" />
|
||||
<ClInclude Include="NetQuake.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
33
mpfw_server/mpfw_server.vcxproj.filters
Normal file
33
mpfw_server/mpfw_server.vcxproj.filters
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NetQuake.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="NetQuake.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Config.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
11
mpfw_server/mpfw_server.vcxproj.user
Normal file
11
mpfw_server/mpfw_server.vcxproj.user
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LocalDebuggerWorkingDirectory>$(SOLUTIONDIR)gameenv/</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LocalDebuggerWorkingDirectory>$(SOLUTIONDIR)gameenv/</LocalDebuggerWorkingDirectory>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user